Dapr Advent Calendar 7日目 - Daprでシークレットストアを使う
こんにちは、Dapr Advent Calendar 7日目です。とうとう1週間続きました! これでもまだ全体の1/3も行ってないんですからね。先は長い!
Daprでシークレットストアを使ってみよう
今回はDaprを使って、パスワードなどを秘匿するシークレットストアを利用します。シークレットストアと言えば、AWS Secrets ManagerやAzure Key Vault、あるいはk8sのsecrets機能などがありますが、これらをDaprから利用することができます。
シークレットについてのドキュメントはこちらにあります。
Daprからシークレットを利用する(読み取る)ことはできますが、シークレットを作成する(書き込む)機能はないため、シークレットは先に自分で作っておく必要があります。
Daprがサポートしているシークレットストアはここにまとめられています。
今回は、最も簡単にできるローカルファイルを利用しましょう。
今回作成するアプリケーションのソースコードはgithubに置いてあります。
https://github.com/cero-t/dapr-advent-2021/tree/main/secrets
シークレットを取得するためのアプリケーション作成
コントローラークラスの作成
まずはWebアプリケーションの作成です。シークレットを取得するコントローラーを作成します。
SecretsController.java
@RestController public class SecretsController { private RestTemplate restTemplate; @Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/secrets/my-secret") private String secretsUrl; public SecretsController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/{key}") public String read(@PathVariable String key) { return restTemplate.getForObject(secretsUrl + "/" + key, String.class); } }
DaprのSecrets APIを呼び出しているだけの簡単な処理です。
Secrets APIは /v1.0/secrets/(シークレットストア名)/(シークレットのキー)
という形式になります。シークレットストア名は my-secret
として、シークレットのキーはリクエストのパスで渡されたものをそのまま使います。
ポート番号をアプリケーション設定ファイルで指定
続いて、設定ファイルでアプリケーションの起動ポートを指定します。
application.properties
server.port=8086
このアプリケーションは8086番ポートを利用します。
シークレットストアを参照するためのDapr設定ファイルを作成
さらに、シークレットストアを参照するためのDapr設定ファイルを作成します。
components/my-secret.yaml
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: my-secret namespace: default spec: type: secretstores.local.file version: v1 metadata: - name: secretsFile value: ./components/my-secret.json
metadata.name
はこのコンポーネントの名称です。この値がSecrets APIのシークレットストア名として利用されるため、上のソースコードと同じ /my-secret
を指定しています。
type
に secretstores.local.file
を指定しており、これはシークレットストアとしてローカルファイル(平文のJSON)を使うという指定です。
secretsFile
はシークレットストアに利用するローカルファイルのファイル名です。このyamlファイルと同じ場所に置きたいので ./components/my-screts.json
を指定しました。本来、シークレットファイルはもう少し安全な場所に置くべきかも知れませんが、ローカル開発環境のパスワードなどは雑に共有することも多いでしょうから、あまり気にせずここに置くことにしました。
もう少し慎重を期するなら、ユーザーディレクトリに .dapr/secrets
ディレクトリなどを作成して他のユーザーに読み取れないようにするのが良いと思います。
シークレットファイルの作成
最後に、シークレットストアに使うファイルを作成します。
components/my-secret.json
{ "password1": "p@ssw0rd", "password2": "12345678" }
世界で最も著名なパスワード2種を指定しました。
Daprを使ったアプリケーションの起動
それではDaprを使ってアプリケーションを起動します。
dapr run --app-port 8086 --components-path ./components ../mvnw spring-boot:run
アプリケーションが起動したら次のコマンドでアクセスしてみます。
curl localhost:8086/password1
結果が取得できます。
{ "password1": "p@ssw0rd" }
もう一つのパスワードも取得してみましょう。
curl localhost:8086/password2
問題なく取得できるはずです。
{ "password1": "12345678" }
ここまでで作成したアプリケーションでは平文のパスワードが書かれたjsonファイルを読み込んだだけで、シークレット感も何もありませんね。実際に運用する際にはDaprの設定を変えて、k8sのsecretsやクラウドサービスのシークレットストアを利用する形になるでしょう。
Daprの設定にシークレットを使う
上の例はシークレットの値をそのままレスポンスとして返すものでしたが、実際のアプリケーションでそんな利用をすることもないでしょう。
シークレットを使うユースケースは、データベースへの接続パスワードを秘匿するとか、外部のWebサービスのアカウント情報を秘匿するといったものが多いと思います。それに近い例として、Daprからデータストアに接続する情報をシークレットから取得するという方法を試してみます。
Daprの設定ファイルでシークレットを利用する方法についてのドキュメントはここにあります。
このドキュメントで指定されている方法を試してみましょう。
データストアにアクセスするコントローラークラスの作成
以前のエントリーで作成したデータストアにアクセスするコントローラーを利用します。
StateController.java
@RestController public class StateController { private RestTemplate restTemplate; @Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/state/postgresql-store") private String stateUrl; public StateController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @PostMapping("/write") public void write(@RequestBody Object message) { restTemplate.postForObject(stateUrl, message, Void.class); } @GetMapping("/read/{key}") public Object read(@PathVariable String key) { return restTemplate.getForObject(stateUrl + "/" + key, Object.class); } }
完全にコピペですが、データストア名だけ分かりやすいように postgresql-store
にしています。
シークレットストアのDapr設定ファイルを作成
次に、シークレットストアの設定ファイルを作ります。
components/postgresql-secret.yaml
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: postgresql-secret namespace: default spec: type: secretstores.local.file version: v1 metadata: - name: secretsFile value: ./components/postgresql-secret.json
上で作成したシークレットの設定ファイルと同様ですね。
続いて、シークレットストアとして利用するファイルを作りましょう。
components/postgresql-secret.json
{ "connectionString": "host=localhost user=postgres password=secretpassword port=5432 connect_timeout=10 database=postgres" }
PostgreSQLに接続する情報をすべて記述しています。この接続文字列も以前のエントリーで指定したものと同じです。
本来はパスワードだけシークレットを利用したかったのですが、DaprのPostgreSQL設定ファイルでは接続文字列をすべて指定する形になるため、接続文字列全体をシークレットに保存することにしました。
データストアのDapr設定ファイルを作成
最後に、データストアに接続するための設定ファイルを作成します。この接続設定がシークレットストアを参照するようにします。
components/postgresql-store.yaml
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: postgresql-store spec: type: state.postgresql version: v1 metadata: - name: connectionString secretKeyRef: name: connection key: connection auth: secretStore: postgresql-secret
これも以前作ったものと同様ですが connectionString
にシークレットを使うように設定しました。
auth.secretStore
でシークレットストア名を指定します。上で作成したシークレットストア設定の postgresql-secret
を使いました。
connectionString
は secretKeyRef
を指定してシークレットストアを参照できるようにしています。ここで name
と key
の両方を connection
としていますが、シークレットが単一の文字列である場合は、このように name
と key
の両方に同じキー名を指定します。シークレットがJSONになっている場合には、name
にシークレットキーを指定し、key
にJSONのキーを指定する形になります。
※GitHubのサンプルコードではファイル名を postgresql-store.yaml.disabled
として、起動時にPostgreSQLにアクセスしないようにしていました。こちらを利用する場合はファイル名を postgresql-store.yaml
に変更してください。
これでアプリケーションの修正は完了です。
PostgreSQLの起動
まずはPosgreSQLを起動しましょう。Dockerを使って起動します。
docker run -d --name dapr_postgres -p 5432:5432 -e POSTGRES_PASSWORD=secretpassword postgres
もし以前のエントリーで起動した時のDockerコンテナが起動したままであれば、それをそのまま使っても構いません。
コンテナが残っていて停止していた場合は docker start dapr_postgres
で起動してください。
Daprを使ったアプリケーションの(再)起動
それではDaprを使ってアプリケーションを起動します。いったん上で起動したアプリケーションを停止させた後、再起動してください。
dapr run --app-id secrets-app --app-port 8086 --components-path ./components ../mvnw spring-boot:run
State mangement APIは app-id
ごとにデータを保存するので、お行儀良く app-id
を指定しています。マナーです。
起動を確認できたら、次のコマンドでデータを登録します。
curl -XPOST "localhost:8086/write" -H "Content-type:application/json" -d '[ { "key": "cero_t", "value": { "name": "Shin Tanimoto", "twitter": "@cero_t" } } ]'
登録ができたら、次のコマンドでデータを取得します。
curl localhost:8086/read/cero_t
登録したデータが取得できるはずです。
{ "name": "Shin Tanimoto", "twitter": "@cero_t" }
正しくPostgreSQLでデータの読み書きができることを確認できました。
PostgreSQLの接続設定をシークレットに保存することで、接続設定ファイルに平文のパスワードを書くことなく接続できるようになりました。この機能はどちらかと言えば開発時よりは運用時に効いてくる機能ですので、また今後のAdvent Calendarの中で良いユースケースと共に改めて紹介できればと思います。
まとめ
- Secrets APIを用いてシークレットストアから値の取得ができます
- クラウドサービスやk8sの提供するシークレットストアに加え、ローカルファイルなどもシークレットストアとして利用できます
- Daprのコンポーネント設定ファイルの中でもシークレットを利用することができます
ちなみに僕自身は、このDaprのSecrets機能を使ったことはなく(というか存在を認識しておらず)、k8sのSecrets機能を使ってDaprの設定をしていました。僕の場合はローカル環境でもk8sを使うか、あるいはそもそもk8sもDaprを使わずに開発するかのいずれかの方針を採っていたためです。
ちょうどその間の「k8sは使わないけど、Daprは使う」ような状況での開発だと、このSecrets機能は有用になりそうですね。その辺りの「どうやって開発するか?」みたいなところはまた今後のAdvent Calendarの中でテーマにしたいと思います。
それでは!