.NET 6 と Daprを使った分散サービス開発 その11 Secret Store

# Daprでシークレット管理

前回は、Daprに備わっているBinding (バインディング)を用いて、RabbitMQでのPubSubを扱ってきました。今回は、接続文字列やDBなどのパスワード、APIのアクセスキーなどのシークレット管理について触れていきたいと思います。

**今までの前提の上でバインディングの機能を追加しますので、このページから読まれている方は、前提として以下を読んできてください。**

----
https://qiita.com/kazumihirose/items/d6d98d2e482947aad936

https://qiita.com/kazumihirose/items/aa34373369ce818e8027

https://qiita.com/kazumihirose/items/ff13fa45bf797e6eb878

https://qiita.com/kazumihirose/items/58d0127212068563fe27

https://qiita.com/kazumihirose/items/45b0ea36dd50d63f1d86

https://qiita.com/kazumihirose/items/701fcbfb397fe8670bf7

https://qiita.com/kazumihirose/items/016dde8ca9b35d1030cc

https://qiita.com/kazumihirose/items/e996e3cc34c511bc1124

https://qiita.com/kazumihirose/items/f7811bf3e5abe405a551

https://qiita.com/kazumihirose/items/57e3aeaab65a1a68977b

## Daprでシークレット管理

マイクロサービスや分散サービスなどでは様々な言語を用いて、これらシークレットを共通で管理するケースも多く、どの言語からも、ローカルでもKubernetesでも、同じように取得できる必要もあり、どのようにルールを定めて運用するか悩ましい所でもあります。
そして、ハードコードする事だけは避けたいものです。

これらに加えて、AWS、Azure、GCPなどのクラウドでは、これらのHSMキー管理のサービスがありますから、対応していくのも大変面倒ですし、ローカルで自分だけってケースであれば、常にそれらを使うというシチュエーションでもないケースも多分にあります。

### Secret store(シークレットストア）コンポーネント

そこで、Daprではシークレット管理を抽象化するコンポーネントがあり、コンポーネント設定によって必要に応じて切り替える事ができます。

普段はローカル環境で環境変数で開発しておき、いざAzureなどにデプロイする際にはAzure Key Vaultで管理するとすれば良い事になります。

- Environment Variables (環境変数)
- Local file (ローカルファイル)
- Kubernetes secrets
- AWS Secrets Manager
- Azure Key Vault
- GCP Secret Manager
- HashiCorp Vault

### コンポーネントを設定

今回は最も簡単なセッティング方法である、ローカルファイルに集約する方法で試します。componentsフォルダに以下のようにファイルを構成しました。
また、nestedSeparatorによって キー名:ネストされたキー名で呼び出す事ができるようになります。

```YAML:secret.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: daprsecret
spec:
  type: secretstores.local.file
  metadata:
    - name: secretsFile
      value: ./secrets.json
    - name: nestedSeparator
      value: ":"
```

また、合わせてsecret.jsonを**ルートフォルダ**に作ります。パスが解決できていない場合、Dapr側のログにも見つからないとログでますので、ロードできているか確認しましょう。

例として、以下のようなファイルを作成しました。

nestedSeparatorが設定されているので、以下のファイルの場合では、connectionStrings:productdbというキー名での呼び出しも可能です。

```json:secret.json
{
    "azureStorageKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
    "redisPassword": "",
    "connectionStrings": {
        "customerdb": "Server=127.0.0.1;Port=3306;Database=customerdb;Uid=guest@localhost;Pwd=Passw0rd!;SslMode=Required;",
        "productdb": "Server=127.0.0.1;Port=3306;Database=productdb;Uid=guest@localhost;Pwd=Passw0rd!;SslMode=Required;"
    }
}
```
### 他のコンポーネントから読み出す

daprのコンポーネント設定そのものに、このようなシークレットが含まれています。そこで、まずは他のコンポーネントの設定をこれらのシークレットデータに置き換える方法について確認しましょう。
以下は、今まで設定してきたState Store用のRedisを設定しているコンポーネント設定のYMALです。ローカルからアクセスしていますので、特にパスワードの設定をしていないのですが、ここでは**設定している**と思ってください。

```YAML:statestore.yaml (設定前)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
```

設定後は、以下のようになります。

```YAML:statestore.yaml (設定後)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    secretKeyRef:
      name: redisPassword
      key: redisPassword
auth:
  secretStore: daprsecret
```

設定しているキーが同じ名前なので、ちょっと疑問に思うかもしれません。
これはキー設定の環境によっては、異なる為で、これはローカルから今回jsonを設定しているケースの設定だからです。

```YAML
    secretKeyRef:
      name: redisPassword
      key: redisPassword
```

Kubernetes やAzure Key Vaultなどの場合は、このnameとkeyは設定が異なる場合があります。
例えば、以下のようにKubernetes でSecretを作って、読み込みたい場合であるとして


```bash:(キーの設定)
kubectl create secret generic daprsecrets --from-literal=redisPassword=secretPassword -n appsecret
```

```YAML
    secretKeyRef:
      name: daprsecrets 
      key: redisPassword
```

となります。

### アプリケーション側から呼び出す

今回は、前回使ったpub-serviceのプロジェクトを修正して、シークレットをロードしてみます。

```csharp:Program.cs
using Dapr.Client;

namespace PubService
{
    class Program
    {
        static async Task Main(string[] args)
        {
            string PUBSUB_NAME = "daprpubsub";
            string TOPIC_NAME = "AppStatus";
            while (true)
            {
                System.Threading.Thread.Sleep(3000);
                Random random = new Random();
                int stateId = random.Next(1, 1000);
                CancellationTokenSource source = new CancellationTokenSource();
                CancellationToken cancellationToken = source.Token;
                using var client = new DaprClientBuilder().Build();

                await client.PublishEventAsync(PUBSUB_NAME, TOPIC_NAME, stateId.ToString(), cancellationToken);
                Console.WriteLine("Published data: " + stateId);

                // 以下を新規に追加
                var storageKey = await client.GetSecretAsync("daprsecret", "azureStorageKey");
                Console.WriteLine($"Load secret : {storageKey["azureStorageKey"]}");

                var connStr = await client.GetSecretAsync("daprsecret", "connectionStrings:customerdb");
                Console.WriteLine($"Load connection string : {connStr["connectionStrings:customerdb"]}");

            }
        }
    }
}

```

#### tyeの起動と確認

ここまで来たらTyeを起動して、確認しましょう。
サイドカーとして起動したDaprからシークレットを読み取り、コンソールに表示しているログが出力されているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/3906fba8-dfd1-0f80-9198-cd0fa15fb84a.png)