[ASP.NET Core]DB接続文字列をappsettings.*.jsonに設定しない

# やりたいこと

ASP.NET Coreアプリケーションで、DB接続文字列などの秘匿情報をappsettings.jsonやappsettings.*.jsonに保存するのではなく、サイト毎の環境変数(ApplicationHost.config)に設定するようにする。

この記事からの抜粋記事となる。
https://qiita.com/jun1s/items/a350877da40910923153

デプロイ先別の設定情報をプロジェクトに含める必要がなくなることで、以下の利点がある。

- デプロイ手順の簡略化
- 秘匿情報管理の向上
- デプロイミスによる事故の低減

# DB接続文字列をappsettings.*.jsonに保存することの問題点

**※方法にだけ興味がある人はこの章を飛ばして「方法」から読んでください。**

本番環境へのデプロイファイルに「本番環境のDBパスワードが記述されたappsettings.*.json」を含めるのにはセキュリティ上の問題がある。
デプロイ、又はデプロイの準備を行う担当者が必ずしも「システムの所有者」ではない可能性がある為である。

別の言い方をすると、生の業務データにアクセスできる「鍵」を持っている責任者と、デプロイ作業の準備をする役割の技術者は別である場合も多い。
その時に、本番環境のDBパスワードが記述されたappsettings.*.jsonがデプロイに必要だと、デプロイ担当者にパスワードが筒抜けになってしまう。デプロイ担当者でなくとも、デプロイ用のファイルを格納しているフォルダが誰でも見れるようになっていたりするとまずいことになる。

そんな時、デプロイと秘匿情報の設定を個別に行えれば便利である。

一つ考えられるのが、appsettings.Production.json をデプロイファイルとは別に用意する方法だ。appsettings.jsonには基本的な設定を記述し、秘匿情報のみをappsettings.Production.jsonに分離させる。本番環境ではASPNETCORE_ENVIRONMENTが「Production」になる為、自動的にappsettings.Production.jsonが追加で読み込まれる。

手順としては、以下のようになる。

- デプロイファイルにappsettings.Production.jsonを含めない（技術者が行う）
- 本番環境に事前にappsettings.Production.jsonを配置しておく（責任者が行う）
- デプロイする（技術者が行う）

しかしこの方法は、意外と面倒なことも多い。
私の環境では、ビルド設定に「EnvironmentName=Production」を付けたら、自動的にappsettings.Production.jsonが生成されてデプロイされてしまった。既存のappsettings.Production.jsonは上書きされてしまい、期待通りの動作をしなくなってしまったのである。

そのような事が起こる可能性を考えると、「デプロイを行った後に責任者が改めて手元のappsettings.Production.jsonだけデプロイする」のような作業が必要になってくるが、これでは本当の意味でデプロイと秘匿情報設定を個別に行っているとは言えない。

そこでこの記事では、DB接続文字列のような秘匿情報を環境変数で指定し、appsettings.*.jsonの値を置き換える方法について説明する。
この方法を使うことで、その環境で最初に一回設定すれば、後はデプロイの際に秘匿情報に悩まされることはなくなる。

# 方法

現在、以下の設定がある。

```json:appsettings.jsonの一部
{
	"ConnectionStrings": {
		"MyPgsql": "Host=localhost;Database=testdb;Username=testuser;Password=testpass"
	}
}
```

上記の設定は、環境変数で上書き（appsettings.jsonより優先度の高い構成情報として設定）できる。

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

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

この設定は、コンテナ等で動作させている場合にはWindowsのグローバル環境変数に設定してもよいが、グローバル環境変数は全てのプロセスから見れてしまう為セキュリティ的に良くないし、複数のWebアプリケーションを動作させている場合には問題がある。
IISのApplicationHost.configにウェブサイト毎の環境を設定することで、この問題を回避できる


## IISのApplicationHost.configに環境変数を設定する

ApplicationHost.configは、```%systemroot%\System32\inetsrv\config```に存在するIIS用の設定ファイルである。
ApplicationHost.config（メインのIIS構成ファイル）に環境変数を設定すると、サイト単位で環境変数を適用することができる。

直接編集してもよいが、IISマネージャーから対象のWebサイトを選択し、「構成エディター」を開くのが便利だ。

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

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

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

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

コマンドラインからApplicationHost.configを操作する方法はこちらに書かれているが、あまり詳しくはドキュメント化されていない。
https://learn.microsoft.com/ja-jp/iis/get-started/getting-started-with-iis/getting-started-with-appcmdexe

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


## Azureのアプリ設定でEnvironmentを設定する

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

上記のサイトを参考に、環境変数として「ConnectionStrings__MyPgsql」を設定すればOKである。

## launchSettings.json で利用する

ローカルデバッグでしか使えないが、launchSettings.jsonの各起動プロファイル中に以下の設定をすることで環境変数を指定できる。開発時のDB接続文字列などはここで定義してもよい。

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

# 注意

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

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


## おまけ：ユーザーシークレット

完全に個人用の設定値を優先させたい場合には、環境変数ではなくユーザーシークレットを作成する方法もある。

https://learn.microsoft.com/ja-jp/aspnet/core/security/app-secrets?view=aspnetcore-7.0

但し「ユーザーシークレット」は開発ユーザー毎に保存される値で、プロジェクトとは別の場所に保存される為GitHubなどで間違えて共有されてしまう可能性が低いものの、暗号化はされておらず、使用は開発中に限定される為、本番環境へのデプロイ時には使えず、注意が必要である。

また、ユーザーシークレットは、「個人用のパスワード」をプロジェクトから外す為の仕組みであり、実行環境別の設定を切り替える為のものではない。DB接続文字列のようなものをここで管理しようとすると、逆に面倒なことになるかもしれない。

尚、ユーザーシークレットをプロジェクトに追加するとcsprojにユーザーシークレットを表すGUIDが追加されるが、この値はランダムの生成されたもので、個人とは結びついていない為、GitHub等で公開しても大丈夫だ。このプロジェクトをクローンした別の人がユーザーシークレットを有効にすると、同じGUIDでユーザーシークレットが生成されるだけである。

## 補足：AppCmd.exe で環境変数を設定する方法（うまくいかない）

いろいろ調べた結果、Default Web Site/MyWebApp1 の環境変数に ASPNETCORE_ENVIRONMENT を追加するコマンドは以下でいけそうなのだが…

```shell
%systemroot%\system32\inetsrv\appcmd.exe set config "Default Web Site/MyWebApp1" -section:system.webServer/aspNetCore/environmentVariables /+"/environmentVariable.[name='ASPNETCORE_ENVIRONMENT',value='Development']" -commit:apphost
```

以下のエラーが出てしまう。上記の ```/+``` というパラメータは、指定した要素を「追加」することを意味するようだ。既存を更新したい場合は ```/```だけにするとのこと。

```
ERROR ( message:構成セクション "system.webServer/aspNetCore/environmentVariables" が不明です。ヘルプを表示するには、? で置き換えます。 )
```

実際には、applicationHost.config には以下のセクションに設定されなければならない。

```xml:appcationHost.config の一部
    <location path="Default Web Site/MyWebApp1">
        <system.webServer>
            <aspNetCore>
                <environmentVariables>
                    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
                </environmentVariables>
            </aspNetCore>
        </system.webServer>
    </location>
```

何かご存じの方がいらっしゃったら教えてください。