[IIS]ASP.NET CoreでDB接続文字列をチームで共有せずにテスト時と本番時で切り替える

# 概要

ASP.NET Coreアプリケーションを本番環境のIISにデプロイする際、DB接続文字列などの秘匿情報をどのように管理しているだろうか。

一般的には、appsetting.Development.json と appsettings.Production.json を切り替える方法が取られると思うが、これだと appsettings.Production.json の取り扱い（Gitに含めるか、含めないならだれがどのように管理してデプロイするのか、等）が難しくなる。

そこで、ApplicationHost.configに環境変数を設定する方法で、秘匿情報をチームで共有せず、デプロイとも切り離して、IIS内部に閉じ込める手法を紹介する。

まずは、問題のある方法から順に説明する。

# appsetting.*.jsonをEnvironmentによって切り替える方法（秘匿情報管理に問題がある）

ASP.NET CoreアプリケーションにおいてDB接続文字列をテスト時と本番時で切り替えるには、appsetting.Development.json と appsettings.Production.json を用意し、それぞれに必要な設定を行う、というのが良く説明されている方法である。

例えば、Environmentが```"Development"```の時、appsettings.jsonの値はappsettings.Development.jsonの値で上書きされる。
このEnvironmentは 環境変数```ASPNETCORE_ENVIRONMENT```によって決定されるが、特に指定しなければ自動的に```"Production"```になる為、appsettings.Production.jsonが上書きする値として使用される（appsettgins.Production.jsonがなければ何も上書きされない）。

よって、テスト環境では```ASPNETCORE_ENVIRONMENT```にDevelopmentを指定して実行し、本番環境では何も指定しなければ（もしくはProductionを指定すれば）よいことになる。

ASPNETCORE_ENVIRONMENT は主に以下の方法で設定できる。
個々の詳しい方法は公式サイトで説明されている。

https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/environments?view=aspnetcore-7.0

## launchSettings.json でEnvironmentを設定する

launchSettings.jsonには、開発用の起動プロファイルを複数登録できる。この起動プロファイルにenvironmentVariables::ASPNETCORE_ENVIRONMENTを設定すれば、その起動プロファイルを使って起動されたWebアプリケーションのASPNETCORE_ENVIRONMENTが変更される。

但しこの方法は開発中、しかもローカル環境での実行時にしか使えない為、本番環境へのデプロイでは利用できない。

## web.config でEnvironmentを設定する

IISの構成ファイルであるweb.configの下記の記述を追加すると、ASPNETCORE_ENVIRONMENTを変更できる。変更時にはWebサイトの再起動が必要である。

```xml
  <environmentVariables>
    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
  </environmentVariables>
```

## 発行プロファイル（pubxml）でEnvironmentを設定する

web.configは、pubxmlによるWeb発行を行った際に上書きされてしまう為、web.configによるEnvironment切替を行いたいのであれば、ここに指定しておくと便利である。

発行プロファイルに次の要素を追加すると、web.configに自動的にenvironmentVariableを追加してくれる。

```xml
<PropertyGroup>
  <EnvironmentName>Development</EnvironmentName>
</PropertyGroup>
```

## dotnet publishコマンドでEnvironmentを設定する

dotnet publishコマンドを使うと、コマンドラインから以下のようにEnvironmentを指定できる。（内部的にはMSBuildを使用するので、MSBuildでも利用可能と思われる）

発行プロファイルのEnvironmentNameプロパティをこのパラメータで上書きすることができる。pubxmlにEnvironmentNameを指定したのと同じなので、この値は最終的にはweb.configに反映される。

```
dotnet publish MyProj1/MyProj1.csproj -c Debug /p:EnvironmentName=Development /p:PublishProfile=publish_development
```


## おまけ：Azureのアプリ設定でEnvironmentを設定する

Azure上で動かしている場合、アプリ設定からASPNETCORE_ENVIRONMENTを設定できる。
参考：https://ryuichi111std.hatenablog.com/entry/2016/06/08/121833


# appsettings.Production.json の機密をどう保つか

これまで説明した方法で、テスト環境と本番環境のEnvironmentを切り替える方法は分かったが、問題は**appsettings.Production.jsonの機密性**についてである。
appsettings.Production.jsonをプロジェクトに含めてしまうと、**本番環境用のパスワードが開発者全員に漏れてしまう**。
それは避けなければならない。

しかし、appsettings.Production.jsonをgit管理対象から外してしまうと、デプロイ時にどこからかappsettings.Production.jsonを用意しなければならなくなる。それもまた管理が面倒であるし、少なくともデプロイを行う人にはパスワードが漏れてしまう。

そうなってくると、**そもそもEnvironmentを利用したappsettings.jsonの切り替えという方法自体に、機密性の高い設定値を任せるのは難しい**ということになる。


# 環境変数を利用する

そこで、Environmentによるappsettings.jsonの切り替えではなく、環境変数を用いて直接appsettings.jsonの値を上書きする方法を提案する。

実は、ASP.NET Coreがappsettings.jsonから値を取得する際には次の順序で読み取られていき、同じキーは後で読み込まれたもので「値の上書き」が行われる。

> 1. appsettings.json
> 2. appsettings.{Environment}.json
> 3. ユーザー シークレット
> 4. 環境変数構成プロバイダーを使用する環境変数。
> 5. コマンドライン構成プロバイダーを使用するコマンドライン引数。

参考: https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/configuration/?view=aspnetcore-7.0#configuration-providers

ちなみに「ユーザーシークレット」は開発ユーザー毎に保存される値で、プロジェクトとは別の場所に保存される為GitHubなどで間違えて共有されてしまう可能性が低いものの、暗号化はされておらず、使用は開発中に限定される為、本番環境へのデプロイ時には使えず、注意が必要である。ユーザーシークレットは、あくまでも「個人用のパスワード」をプロジェクトから外す為の仕組みである。尚、ユーザーシークレットをプロジェクトに追加するとcsprojにユーザーシークレットを表すGUIDが追加されるが、これは個人とは結びついていない為、GitHub等で公開しても大丈夫だ。このプロジェクトをクローンした別の人がユーザーシークレットを有効にすると、同じGUIDでユーザーシークレットが生成されるだけである。

なので、今回は「4」の環境変数に直接設定する方法を使う。

## 環境変数とappsettings.jsonのキーの対応付け

appsettings.jsonに以下の設定値があったとする。

```json
{
	"ConnectionStrings": {
		"MyPgsql": "Host=localhost;Database=testdb;Username=testuser;Password=testpass"
	}
}
```

上記のMyPgsqlを上書きする環境変数名は、```ConnectionStrings__MyPgsql```である。（キーの階層をアンダーバー2個でつなぐ。"::"でも良い場合があるが、一部の環境で動作しない。アンダーバー2個にしておいた方が安全である）

上記の名前で値を設定すると、appsettings.jsonの値より優先して読み込まれるようになる為、デプロイ先の環境で一度設定してしまえば後は何も考えずデプロイを継続できるようになる。


## 方法1:Windowsのグローバルな環境変数に設定する（×）

Windowsのグローバルな環境変数に上記の変数を設定すれば、appsettings.jsonの値を無視してこちらに上書きできる。

|環境変数名|値|
|-|-|
|ConnectionStrings__MyPgsql|Host=honbansv;Database=honbandb;Username=honbanuser;Password=honbanpass|

これで動作するが、この方法には問題がある。グローバルな環境変数は、このマシン上で動作する全てのプロセス・ユーザーから簡単に見れてしまう為である。
もう少し機密性の高い方法をとりたい。


## 方法2:IISのApplicationHost.configに環境変数を設定する（○）

IISのApplicationHost.config（メインのIIS構成ファイル）に環境変数を設定すると、サイト単位で環境変数を適用することができる。
この方法は対象となるWebアプリケーションのプロセスにのみ影響する為、比較的安全と思われる。

ApplicationHost.configは、```%systemroot%\System32\inetsrv\config```に存在するが、直接編集するよりIISマネージャーから対象のWebサイトを選択し、「構成エディター」を開くのが便利だ。

構成エディターではweb.configなども編集できるが、今回の対象はApplicationHost.configなので、「場所」ドロップダウンリストより「ApplicationHost.config」を選択し、「セクション」から「system.webServer/aspNetCore」を選択する。

リストの中に「environmentVariables」があるので、この「(Count=0)」になっている場所の「...」ボタンを押下してコレクションエディタを開き、右側のメニューの「追加」を押して

|キー|値|
|-|-|
|name|ConnectionStrings__MyPgsql|
|value|Host=honbansv;Database=honbandb;Username=honbanuser;Password=honbanpass|

を入力してコレクションエディタを閉じた後、構成エディタの右側のメニューから「適用」をすればOKである。

ちなみに、同様の設定を web.config に対して行っても同じ効果があるが、web.config はデプロイ時に上書きされてしまうので、あまり意味がないことに注意したい。

AppCmd.exeというツールを使ってコマンドラインからもApplicationHost.configを編集できそうなので、これを使ってコンテナの自動化を実現できそうだが、試したことはない。
https://learn.microsoft.com/ja-jp/iis/get-started/getting-started-with-iis/getting-started-with-appcmdexe

## おまけ：launchSettings.jsonでも同じ手法が使えるよ！

ローカルデバッグでしか使えないが、この「環境変数によるappsettings.jsonの値の上書き」は、launchSettings.jsonでも利用できる。
各起動プロファイル中に、以下の設定をすればよい。

```json
      "environmentVariables": {
        "ConnectionStrings__MyPgsql": "Host=localhost;Database=testdb2;Username=testuser;Password=testpass",
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
```

ただ、launchSettings.jsonをgit管理下においてチーム内で共有している人も割と多いと思われ、その場合にはユーザー毎の個人設定値を上記の方法で個々に変更すると差分検出されてしまう。

個人毎の設定値はユーザーシークレットを使って管理せよということかもしれないが、正直ちょっと使いにくい。
「launchSettings.json.user」みたいなものが使えると非常に便利なのになぁ、と思う。


# まとめと注意

Environmentを切り替えることでappsettings.jsonの値を切り替える方法と、機密性の高い設定値についてはその方法を使わず環境変数によって環境毎に設定値を切り替える方法について説明した。

注意点として、環境変数に設定した値は、設定した人以外からは意識の外になりがちである。作業を引き継いだ人が、それを知らずにappsettings.jsonの値を切り替えたりEnvironmentを切り替えたりしても一向にDB接続文字列の値が切り替わらないことで混乱する可能性が非常に高い為、この設定を行っていることを引き継ぎ資料、もしくはappsettings.jsonの中にコメントとして書いておくとよいだろう。

```javascript
{
	"ConnectionStrings": {
		// この設定値は本番環境のIISのApplicationHost.configで上書きされています
		"MyPgsql": "Host=localhost;Database=testdb;Username=testuser;Password=testpass"
	}
}
```


## メモ（失敗例）

実は最初、dotnet publish コマンドでEnvironmentNameを指定する方法を用いて次のようなことを考えていた。

- appsettings.Production.json に本番環境用のDB接続文字列を設定し、本番環境のIISアプリケーションフォルダに設置する
- 上記ファイルはプロジェクトには含めず、もちろんgit管理対象にもしない
- その上で、dotnet publishコマンドで /p:EnvironmentName=Production を指定してpublishする
- 本番環境でのみ、事前に設置した appsettings.Production.json が適用される

しかしこれはうまくいかなかった。理由はわからないが、dotnet publish コマンドにて、appsettings.Production.jsonが自動的にappsetting.jsonを複写して生成され、デプロイ先のファイルを上書きしてしまうのである。

pubxml側でデプロイ対象からappsettings.Production.jsonを外せば対処できる可能性もあるが、逆に言うと、その設定がもし何らかの原因で外れてしまうと本番環境がテスト環境に置き換わってしまうという恐怖を考えると、怖くてこの方法はとれなかった。

メモとして残しておく。

## 余談：appsettings.Development.json.user という設定を可能にして欲しい

余談になるが、開発中にローカル環境にデプロイしてテストを行っている時、一時的に別の設定値に変更したい時がある。
しかし、appsettings.Development.jsonは、共有の開発環境用の設定ファイルであり、勝手に変更することはできないし、一時的に変更するとgitに変更として検出されてしまい、面倒である。

自分専用のEnvironmentとしてMyDevというものを作り、ローカル環境デプロイ用のpubxmlにpubxml.userを作ってEnvironmentNameにMyDevを設定、appsettings.MyDev.json を作って使用する、という方法が考えられるが、結局 appsettings.MyDev.json はgitに変更として検出されてしまうので間違ってコミットしてしまう恐れがある。

そんな時こそユーザーシークレットを使えということかもしれないが、ユーザーシークレットに設定すると全てのEnvironmentに影響を与えてしまうので、影響が大きすぎることがあるし、そもそもユーザーシークレットは扱いがちょっと面倒だ。

今回の記事のように、ローカルのIIS Express上でApplicationHost.configの環境変数設定を行う方法はあるが、この設定は自分でも「設定したことを忘れてしまいがち」なので、一時的な変更にはあまり向かない。

appsettings.Development.json.user というファイルを作るとそちらが優先されるようになっていれば、ちょっとした変更をgitを汚さずに（*.userというファイルはgit管理対象外とすることが多い）行えるし、不要になれば削除すれば良いので楽である。

ともかくも、ASP.NET Coreのappsettings.json 周りは、秘匿情報管理と開発中の設定値の柔軟な切替についてもう少し検討してもらえると嬉しい。