谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Dapr Advent Calendar 7日目 - Daprでシークレットストアを使う

こんにちは、Dapr Advent Calendar 7日目です。とうとう1週間続きました! これでもまだ全体の1/3も行ってないんですからね。先は長い!

Daprでシークレットストアを使ってみよう

今回はDaprを使って、パスワードなどを秘匿するシークレットストアを利用します。シークレットストアと言えば、AWS Secrets ManagerやAzure Key Vault、あるいはk8sのsecrets機能などがありますが、これらをDaprから利用することができます。

f:id:cero-t:20211207053457p:plain
今回作るアプリケーション

シークレットについてのドキュメントはこちらにあります。

docs.dapr.io

Daprからシークレットを利用する(読み取る)ことはできますが、シークレットを作成する(書き込む)機能はないため、シークレットは先に自分で作っておく必要があります。

Daprがサポートしているシークレットストアはここにまとめられています。

docs.dapr.io

今回は、最も簡単にできるローカルファイルを利用しましょう。

今回作成するアプリケーションのソースコード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 を指定しています。

typesecretstores.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やクラウドサービスのシークレットストアを利用する形になるでしょう。

f:id:cero-t:20211207053523p:plain
Daprを使ってシークレットストアにアクセス

Daprの設定にシークレットを使う

上の例はシークレットの値をそのままレスポンスとして返すものでしたが、実際のアプリケーションでそんな利用をすることもないでしょう。

シークレットを使うユースケースは、データベースへの接続パスワードを秘匿するとか、外部のWebサービスのアカウント情報を秘匿するといったものが多いと思います。それに近い例として、Daprからデータストアに接続する情報をシークレットから取得するという方法を試してみます。

Daprの設定ファイルでシークレットを利用する方法についてのドキュメントはここにあります。

docs.dapr.io

このドキュメントで指定されている方法を試してみましょう。

データストアにアクセスするコントローラークラスの作成

以前のエントリーで作成したデータストアにアクセスするコントローラーを利用します。

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 を使いました。

connectionStringsecretKeyRef を指定してシークレットストアを参照できるようにしています。ここで namekey の両方を connection としていますが、シークレットが単一の文字列である場合は、このように namekey の両方に同じキー名を指定します。シークレットがJSONになっている場合には、name にシークレットキーを指定し、keyJSONのキーを指定する形になります。

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 APIapp-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の中で良いユースケースと共に改めて紹介できればと思います。

f:id:cero-t:20211207053718p:plain
シークレットストアの情報を使ってPostgreSQLにアクセス

まとめ

  • Secrets APIを用いてシークレットストアから値の取得ができます
  • クラウドサービスやk8sの提供するシークレットストアに加え、ローカルファイルなどもシークレットストアとして利用できます
  • Daprのコンポーネント設定ファイルの中でもシークレットを利用することができます

ちなみに僕自身は、このDaprのSecrets機能を使ったことはなく(というか存在を認識しておらず)、k8sのSecrets機能を使ってDaprの設定をしていました。僕の場合はローカル環境でもk8sを使うか、あるいはそもそもk8sもDaprを使わずに開発するかのいずれかの方針を採っていたためです。

ちょうどその間の「k8sは使わないけど、Daprは使う」ような状況での開発だと、このSecrets機能は有用になりそうですね。その辺りの「どうやって開発するか?」みたいなところはまた今後のAdvent Calendarの中でテーマにしたいと思います。

それでは!