Azure Cosmos Emulatorにリモート接続する方法

# 概要
Azure Cosmos DBを利用するアプリケーションを開発する際、MSの提供する[Azure Cosmos Emulator](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator)を利用すると、Azureを利用することなく動作させることが出来るため、開発やテストではよく使われるのではないかと思います。
ところがこのエミュレータ、Windows 10以上もしくはWindows Server 2012 R2以降のOSのみサポート[^1]となっており、macOSやWindows 7等のOSではインストールすることが出来ず、これらのOSを開発環境として使用していた場合は利用することが出来ません。
こういったケースの場合、仮想マシン等で別に用意したWindows 10環境に[Azure Cosmos Emulator](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator)をインストールし、そこを開発用Cosmos DBサーバーとして開発マシンから接続したくなる事があると思いますが、これを実現する方法が少し分かりにくかったため手順を残しておきます。

# 動作確認済み環境
## サーバー側
* Windows 10 Pro
* Microsoft Azure Cosmos Emulator 2.2.1.0

# クライアント側(macOS)
* macOS High Sierra
* .NET Core 2.2 (Microsoft.Azure.DocumentDB.Core 2.30)

# クライアント側(Windows 7)
* Windows 7 Professional
* .NET Core 2.2 (Microsoft.Azure.DocumentDB.Core 2.30)

# サーバー側の手順
## Microsoft Azure Cosmos Emulatorのインストール
[こちら](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator#installation)を参考にインストーラを入手してインストールを行ってください。
インストール完了時点ではまだエミュレータを起動しないようにして下さい。

## 証明書の生成
[Azure Cosmos Emulator](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator)をインストールするマシンのIPアドレスまたはホスト名に対応した証明書を生成します。
IPアドレスまたはホスト名は、アプリケーションから接続する際の接続先に指定するものと同じ物にしておく必要があります。
コマンドプロンプトを起動し、下記のコマンドを実行してください。

```powershell
"C:\Program Files\Azure Cosmos DB Emulator\Microsoft.Azure.Cosmos.Emulator.exe" /GenCert=192.168.0.1
```
※`192.168.0.1`を[Azure Cosmos Emulator](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator)をインストールするマシンのIPアドレスまたはホスト名に読み替えてください。複数指定する場合はカンマ区切りで`/GenCert=192.168.0.1,dev.cosmos.local`の様にすればOKです。
※この手順を実施しない場合、アプリケーションから接続を行った場合に証明書の検証に失敗し下記の様な例外が発生してしまいます。

```
Unhandled Exception: System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
   at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
...
```

## 証明書のエクスポート
生成した証明書は後ほどクライアント側にインポートする必要があるため、今のうちにエクスポートを行っておきます。
ファイル名を指定して実行(Win+Rキー)から`certlm.msc`を起動し、「個人」→「証明書」の中からフレンドリ名が「DocumentDbEmulatorCertificate」となっている証明書を秘密キーなしの「Base 64 encoded X.509 (.CER)」形式でエクスポートします。
エクスポートした証明書はクライアント側マシンにコピーしておいて下さい。

## エミュレータの起動
コマンドプロンプトを起動し、下記のコマンドを実行してください。
キーはデフォルトの物を指定していますが、必要に応じて変更しても構いません。重要なのは`/AllowNetworkAccess`と`/NoFirewall`オプションになります。

```
"C:\Program Files\Azure Cosmos DB Emulator\Microsoft.Azure.Cosmos.Emulator.exe" /AllowNetworkAccess /NoFirewall /Key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
```

# クライアント側の手順(macOSの場合)
## 証明書のインポート
証明書の生成でエクスポートしておいた証明書ファイルをダブルクリックし、キーチェーンに「システム」を選択し追加して下さい。
その後、Launchpadからキーチェーンアクセスを起動し、「システム」から先ほど追加した証明書をダブルクリックし、「信頼」→「この証明書を使用するとき」を「常に信頼」に変更します。
.NET Coreアプリケーションの場合、以上の手順で接続可能になります。Java等の他の言語ではインポート先が異なるため、[こちら](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator#export-the-ssl-certificate)を参考にインポートを行って下さい。

# クライアント側の手順(Windows 7の場合)
## 証明書のインポート
証明書の生成でエクスポートしておいた証明書ファイルをダブルクリックし、ローカルコンピュータの「信頼されたルート証明機関」にインポートして下さい。
.NET系アプリケーションの場合、以上の手順で接続可能になります。Java等の他の言語ではインポート先が異なるため、[こちら](https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator#export-the-ssl-certificate)を参考にインポートを行って下さい。

[^1]: 2019/04/29時点での対応状況。[こちら](https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/18533509-add-documentdb-emulator-support-for-mac-os-x-and)でmacOSやlinuxへの対応が開始されており、将来的には対応される可能性が高そうです。