OpenShiftにHashicorp VaultをDepolyしてSingle "Secret" of Truthを実現しよう

# はじめに
Kubernetes/OpenShiftにおけるアプリケーション運用の高度化において「GitOps」は避けては通れません。しかし、GitOpsとSecret管理の両立には課題があります。Manifest群をGit Repositoryで管理する上で「Secretどうする」問題が付き物です。

というのも、Secretは機密情報を管理するためのK8sリソースですが、機密情報にあたる値はbase64エンコードされているだけなので誰でもデコードできちゃいます。つまり、公開Repositoryなんぞにアップしたら、それは平文のまま置いているのと同じです...

# Hashicorp Vaultを使おう
K8s Secretに限らず、あらゆる機密情報管理ソリューションとして広く使われているのが「Hashicorp Vault （以下Vault）」です。Kubernetesとの連携もばっちりで、特定のNameSpaceの特定のSercvice Accountに対して認証認可を経て適切なSecret操作権限（read, create, delete 等々）を付与してくれます。
2023年3月に公開された「Vault Secret Operator （以下VSO）」によって、大変に使い勝手がよくなりました。

# 要はこういうことが実現したい
実現したいことを絵に描いてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ec167216-86be-00fc-e317-bf3ef9bc80d9.png)
`Manifest.yaml`をSingle Source of TruthとしてGitOpsを実現するのと全く同じ雰囲気で、Vaultに唯一無二の真実のSecret情報（Single "Secret" of Truth）を格納し、その更新をK8s/OpenSshift Clusterに自動的に反映させたいのです。また、`Pod`や`Deployment`等の`Manifest`に対しては、なんらかの工夫や特別な記載をしたくもないのです。

まさにこうしたことがVSOでは実現可能です！

### 余談（興味なければ読まずにOK）
- VSO登場前は主に2通りの方法でManifest/KubernetesにSecret情報を参照させていたのですが、ぶっちゃけ初心者の私にはしんどかった印象です...
    - **Vault Agent方式：** Secretを参照させたいユーザワークロード（Mysql 等）のコンテナに`init Container`、あるいは`Sidecar`としてAgentを仕込み、AgentがVaultからSecret情報を取得、`Pod`内の共有メモリに展開する格好でユーザワークロードコンテナにSecret情報をシェアする。Sidecar方式を使えば、Vault上のSecret情報の更新を`Pod`に反映できる。
    - **Vault CSI Provider方式：** `CSI Driver`を応用した`Secret Store CSI Driver`という仕組みによって、`Pod`にVolumeをマウント、そのVolumeにファイルとしてのSecret情報を格納することで、`Pod/Container`がSecret情報を参照できる方式。K8s ClusterにSecret.yamlが作られるわけではない。Vault上のSecret情報の更新はDeploy済みの`Pod`に反映できない。
- 各方式の詳細やメリデメは[Hashicorp Vaultの公式サイト](https://www.hashicorp.com/blog/kubernetes-vault-integration-via-sidecar-agent-injector-vs-csi-provider)をご覧ください。



さっそくOpenShiftで試していきます〜！環境情報は以下。
- Red Hat OpenShift Services on AWS (v4.15.0)
- 特段環境差分は関係ないはずです。

# OpenShiftにVaultをDeployする
実はVaultをSetupしてから使えるようになるまでは、それなりにStepが必要です。以下に記載した流れでやっていきます。
1. VaultをDeployする為のNameSpaceを作成
1. Helm ChartからDeploy
1. Route公開
1. Initialize
1. Kubernetesと接続（Authentication）
1. Secret Engine作成（Key-Value）



## VaultをDeployする為のNameSpaceを作成
適当なNamespaceを作って（`vault`としました）ください。
```
oc new-project vault
```
できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/04f64f39-0090-86d5-d5e0-e429fe3d13c7.png)
## Helm ChartからDeploy
VaultはHelm Chartを公開しておりますので、そちらを使います。
「追加」のリンクをクリックして、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d658fd41-80da-4d6f-c0f0-25dae6ac8255.png)
「Helmチャート」をクリックして、
「vault」と検索して出てきたHelmチャートをクリックして、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/cbdfbb72-17a7-5027-71a6-bed512b7fac7.png)
「作成」をクリックして、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8604a9f7-0aed-6a03-0765-01f3ee43d683.png)
次も「作成」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/6669b2eb-f019-f4c8-5218-7e9b3371b2d1.png)
すると、トポロジー画面に遷移して、2つのリソースがDeployされます。
- `vault`: `StatefulSet`としてDeployされます。これがVault本体です。
- `vault-agent-injector`: こちらはAgent方式の際に`Init Container`や`Seidecar`を仕込む動作をする`Deployment`です。SVO方式では不要です。
Vaultは２つのPortでそれぞれに`Service`が付いていますが、デフォルトだと`Route`は存在しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/3b8b4f09-d9ea-ca95-0567-77b495ef77e0.png)
なので、`Route`をくっつけてInternetからアクセスできるようにします。

## Route公開
管理者向け表示の「ネットワーク」 → 「Route」と進んで、「Routeの作成」をクリック、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e29e81b1-a3fa-7e10-19ec-a4f09f62a161.png)
名前は適当なものをご自由にどうぞ。自分は`route-vault`としました。
`Service`は「vault」を選択、`TargetPort`は「8200 → 8200」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/08045ff2-7ccf-4397-acb5-71de7e299da0.png)
これで「作成」をクリック。再び「開発者向け表示」の「トポロジー」画面に戻ります。
`Route`が付きました。これでInternetからVaultのコンソール画面にアクセス可能になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0d3fdddc-ae59-5b35-42a4-0329be278cd1.png)

## Initialize
Vaultはインストール直後はSealed（暗号化）されており使えません。そのため、Unseal（暗号化解除）の作業が必要です。この作業は初回のみ必要です。ただしVaultコンテナが停止したり、再作成された場合は再度Useal作業が必要になります。
早速RouteのURLをクリックしてVaultのコンソール画面にいきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a8b8ff27-c2bf-bd9b-b5ec-0d80492de7d0.png)
こんな画面が出て「え...?」ってなりますよね。
- `Key Shares`： Unsealする為に必要なそれぞれユニークな`Key`をいくつ払い出すか？を指定できます。
- `Key threshold`： 「払出した`Key`をいくつ使わないとUnsealできないか」を指定できます。

つまり、「ユニークな`Key`5つのうち、3つを使わないとUnsealできないようにする」みたいな感じです。
ちょっとやってみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/73f08524-d787-3d1a-faa5-fdc7e40e1e70.png)

それぞれ`5`と`3`と入れてみて、「Initialize」をクリック。`root token`と`key`5つが生成されました。これらは無くすと大変なので、厳密に管理してください。
※無くした場合は再度初期化が必要です。その場合、Vaultに登録されたSecret情報も消えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/f1c41b5f-1a50-9705-bc69-90c9a8596261.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/139594c6-a6d8-fefe-c13a-f00cbcddb6d7.png)

「Download keys」をクリックして`.json`ファイルを入手、適切に管理してください。つぎに「Continue to Unseal」をクリックしますと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/25066c99-a397-b718-6613-9db7432eb568.png)

こんな画面になります。「Unseal Key Portion」の欄に、`Key Shares`で指定した値（先程は`5`を指定しました）だけ払い出された`Key`のうち、`Key threshold`で指定した数（先程は`3`を指定しました）だけ選んでコピペして「Unseal」をクリックします。
やってみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/6a2fb3fd-ad7d-bdd3-9106-82047fced283.png)

右下に「1/3 keys provided」と出ています。1つめに選んだ`Key`以外のもので同じことを2回繰り返すと、Unsealできます。
ログイン画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c02da3d9-c48f-911f-3335-cff447c9a12c.png)

初期状態だと`root token`以外でのログイン手段がありませんので、先程の`.json`ファイルの中にある`root token`を使ってログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/bce84aef-5fac-548a-9eef-3c6a6a6be25e.png)

やりました！これでVaultの初期設定は終了です！
次にKubernetenetes Clusterとの接続を行います。

## Kubernetesと接続（Authentication）
TOP画面の左メニューから「Access」 → 「Authentication Methods」と進みます。
右上の「Enable new method」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b17022ca-39fc-ad25-4cf5-216de7077878.png)

「Kubernetes」を選んで、「Next」をクリックして、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/546949f8-b77e-9c8c-ee29-0e4fbc0168f1.png)

「Enable Method」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4861c061-150d-1fcc-f5d5-4972a3dd5cf7.png)

接続先のKubernetesの情報を求められます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/db6f0460-eeaa-d2f2-09cc-c4dfd65aae11.png)

「Kubernetes host」は、`Kubernetes API`（Cluster外からKubernetesを操作するための情報）のホスト情報を入れます。なお、今回はVaultはKubernetes Clusterの上で動いている為、内部ホスト名でアクセス可能です。
Kubernetes API Serverの内部ホストは`https://kubernetes.default.svc.cluster.local:443`です。

::: note info
ROSAの場合外部ホスト名は「Red Hat Hybrid Cloud Console」から確認できます。「Control Plane API endpoint」の欄の値です。ただし今回は外部ホスト名は使いません。
![スクリーンショット 2024-03-20 0.06.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/06f68899-62d8-1aab-bc10-fe9cd668c2f1.png)
:::

内部ホスト名を入力して「Save」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b5d6d09c-d54b-ca34-3ae3-abb1a160a24f.png)
これにてKubernetes Clusterとの連携が可能となりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b43011da-0079-4ce8-7700-6a2d018c41d5.png)

## Secret Engine作成（Key-Value）
VaultはSecret Engineの中にSecret情報を管理します。今回は最も一般的なSecret EngineであるKey-Valueタイプのものを使います。

VaultのTOP画面の左メニューから「Secret Engines」をクリックして、右上の「Enable new engine」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/163e9ac0-1109-ccac-2aca-e0d894b7c32a.png)

「KV」を選択して、「Next」をクリックし、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/cfe7626e-6eda-c80d-d112-ff49424e624b.png)

「Path」に任意のSecret Engine Pathを設定します。今回は`secret`にしてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c8c2d2a0-c511-dbde-a12f-f454981a5cca.png)

そのまま「Enable Engine」をクリックしてください。
`secret`という名のSecret Engine（KV-V2タイプ）ができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/333729b1-d967-851f-5418-429a506f4f57.png)

この中にSecretファイルを作りましょう！右上の「Create secret」をクリック！
（Secretがゲシュタルト崩壊しそう...）
「Path for this secret」でSecretファイルへのPathを設定する事ができます。
まずは`test/test-secret`と入力し、Secretの`Key,Value`に適当な値を入れてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/94a29380-64af-ee75-4586-bb169201ec93.png)

これによって以下のようなSecretファイルをVault上に作成した事になります。
```test-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque #特に型指定しない場合の一般的なSecretファイル形式です
Data:
  hogehoge: ZnVnYWZ1Z2E= #fugafugaがBASE64エンコードされた文字列です
```
「Path」タブをクリックすると、今作ったSecretファイルのSecret Engine内におけるPathが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/fb0e6f9b-9dff-6304-a16c-3c84090959e4.png)

すごいややこしいのですが、先ほど作成した`Secret`を参照するためのPath指定は、
- APIで指定する場合: `/version/secret/data/test/test-secret`
- CLIから指定する場合: `mount=Secret Engine名, Path=test/test-secret`

となります。このPath情報は後ほどVault上で`Policy`を作成したり、SVOから提供される`CustomResource`を作成する際に使いますので、頭の片隅に入れておいてください。

お疲れ様でした。これでOpenShift Clusterの中でVaultが使えるようになり、これからOpenShiftの`NameSpace`に同期させたい`Secret`の準備もできました。では、Single "Secret" of Truthを実現させましょう！

# Secret EngineとNameSpace内のSecretの同期を試す
今からやること（≒作成するリソース）を先に示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e94b2eb0-b0ca-3c29-4968-2e28b6a3de79.png)

ざっくり２つの事をやっていきます。
まずはVault内で`Policy`と`Role`を作ります。
- `Policy`: Secret Engine内のSecretに対する権限（CRUD 等）を定義したもの
- `Role`: どの`NameSpace`のどの`ServiceAccount`に`policy`を紐づけるのかを定義したもの

次に、Secretを同期したい宛先NameSpace内に「Vault Secret Operator(VSO)」が提供する`CustomResouce`を3つ作成します。
- `VaultConnection`: Vaultとの接続情報を定義したもの
- `VaultAuth`: どの認証Methodを使って、どの`SeviceAccout`からのアクセスを許可し、更にどのRoleを適用するのか？」を定義したもの。今回は認証Method(Authentication)として既にKubernetesを設定済みです。
- `VaultStaticSecret`: VaultAuthを経て認証認可されたServiceAccountが、Vaultに格納されているどのSecret情報（Static）をどう取り扱うのか？を定義します。

がんばってやっていきましょう！

## `Policy`を作成
Secret Engine名`secret`の中の`test/test-secret`を閲覧のみ可能（ReadOnly）なポリシーを作成します。
なお、Vaultのポリシールールは[Vault policies](https://developer.hashicorp.com/vault/tutorials/policies/policies)が参考になります。

VaultのTOP画面から左メニューの「Plicies」をクリックして、右上の「Create ACL Policy」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/709d7f70-2e80-9fe8-c1df-c2c0921c9da2.png)
「Name」はポリシー名ですので、なんらかわかり易いものを入れておきます。とりあえず`test-readonly`としてみました。[Vault policies](https://developer.hashicorp.com/vault/tutorials/policies/policies)を参考にしながら作ります。今回は、先ほど作成した`Secret`の参照権限のみをもたせた`Policy`を作成します。
```json
path "secret/data/test/test-secret" {
  capabilities = ["read"]
}
```
となります。Policy作成時のPathにはSecretの「API Path」を入れます。ちなみに、もし作成したSecret Engine内全ての`Secret`に参照権限を与えたい場合は
```json
path "secret/*" {
  capabilities = ["read"]
}
```
こんな感じでPathにワイルドカード表現を設定すればOKです。今回は明示的に対象の`Secret`ファイルのみを指定しましたが、以下の挙動はどちらの`Policy`でも同じです。

OKなら「Create policy」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/74b37016-7e8a-1017-c9ec-53750186a6f3.png)

次は`Role`を作成します。

## `Role`を作成
再びVaultのTOP画面から「Access」 → 「Authentication Method」と進み、先程EnableしたMethodである「Kubernetes/」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/3448e6ef-39a4-d8ba-1be9-76c57d5e160b.png)

今このAuthentication MethodにはRoleがありませんので、「Create role」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4978a144-b5f5-e8ec-c7f3-2a559c5eeef9.png)
次の画面で、「どのNameSpaceのどのユーザアカウントにどういうポリシーを適用するのか？」を記載します。
まずは「Name」の所にお好きなRole名を入力します。
次に、「Bound service account names」には、`NameSpace`に存在する`ServiceAccount`を入力します。
なお、OpenShiftは`NameSpace`を作成するといくつかの`ServiceAccount`が勝手に作られます。
ちょっとOpenShiftのコンソール画面で「管理者向け表示」にして「ユーザー管理」 → 「ServiceAccounts」とクリックしてみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0cd2bf99-ad3d-6373-0de9-0546594c8911.png)

`NameSpace: test`内に存在する`ServiceAccount`の一覧が出てきます。特に、デフォルトで作られる、その名の通り`defaul`という`ServiceAccount`がありますので、こいつにSecretの閲覧権限を付与することにしましょう。
もちろん、なんらか任意の`ServiceAccount`を作って、そいつに権限を付与しても良いです。とりあえず適当な`Role`名称を付けて、以下のように設定します。
- Name: test-role
- Bound service account names: default
- Bound service account namespaces: test
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9c44648d-e2be-5c04-66d2-1879996a52a7.png)

なお、`NameSpace`は`NameSpace`名でも、同じ`Selector`名をキーにして複数指定もできます。
まだ完成ではありません。`Policy`の指定をしましょう。「Tokens」というメニューを開くと、色々設定値が出てきます。「Generated Token's Policies」というところに、付与する`Policy`名を指定します。
先程作成した`test-readonly`を入れます。`Policy`は複数指定することが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9acb26b2-d357-6e30-5a19-c54d59f459ec.png)

これで「Save」をクリックすれば`Role`ができました。

# Vault Secret OperatorをInstall
OpenShift ClusterにVSOをInstallしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/60685458-4b47-15fa-352d-fc694f5e56d1.png)


先にVSOをInstallするための`NameSpace`を作成してください。（とりあえず`vso`としておきます）
```
oc new-project vso
```
Operator HubからInstall可能です。Installは一旦デフォルト設定のままで大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/939b8fe6-c8a3-d8bd-be88-49941c023cff.png)

Installが完了すると、`CustomResource`が作成可能になります。

## `CustomResource`を作成
あらためて今から作成する`CustomResource`を振り返りましょう。
- `VaultConnection`: Vaultとの接続情報を定義したもの
- `VaultAuth`: どの認証Methodを使って、どの`SeviceAccout`からのアクセスを許可し、更にどのRoleを適用するのか？」を定義したもの。今回は認証Method(Authentication)として既にKubernetesを設定済みです。
- `VaultStaticSecret`: VaultAuthを経て認証認可されたServiceAccountが、Vaultに格納されているどのSecret情報（Static）をどう取り扱うのか？を定義します。

先に`NameSpace: test`を作成しておいてください。
```
oc new-project test
```
空のProject画面を右クリックして、追加メニューから「Operator提供」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ed3c23e7-27ee-ae34-c7b6-63bc9144ccef.png)
「vault」と検索すると、VSOが提供する`CustomResource`一覧がでてきます。ここから上述の3つの`Resource`を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/35942340-d61b-171e-1dc7-f95f951bbaed.png)

### VaultConnectionを作成
VaultConnectionを選択して作成します。あるいは以下のYAMLを`apply`してもOKです。
```vaultconnection-sample.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  name: vaultconnection-sample
  namespace: test
spec:
  skipTLSVerify: false
  address: 'http://vault.vault.svc.cluster.local:8200'
```
`spec.address`はVaultのアクセス先を設定します。元から`http://vault.vault.svc.cluster.local:8200`が入っているかと思いますが、これはCluster内部で名前解決できる内部URLになります。一応Vaultの`Service`を見ておきましょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/22637d4c-0c8c-b514-4b69-ab522ee8d2ff.png)
Cluster内でのみアクセス可能なホスト名として`vault.vault.svc.cluster.local`が存在します。これに`Schema: http`と`Port: 8200`をつければ、先程の内部URLになるわけです。もし別Clusterに存在するVaultであれば、`Route`を指定する事になりますが、今回は同一Cluster内で完結した通信です。

### VaultAuthを作成
`VaultConnection`を作成した際と同様、あるいは以下のYAMLを`apply`します。
```vaultauth-sample.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vaultauth-sample
  namespace: test
spec:
  kubernetes:
    tokenExpirationSeconds: 600
    role: test-role
    #Vault上で作成したRoleを指定してください
    serviceAccount: default
    #Roleで指定しているServiceAccountと同じものを指定してください
  vaultConnectionRef: vaultconnection-sample
  #参照するVaultConnection名を指定します。先程作成したものです。
  method: kubernetes
  mount: kubernetes
  #今回はKubernetesをAuthentication Methodに指定しているので、Kubernetesを設定
```

### VaultStaticSecretを作成
同様に`VaultStaticSecret`を作成します。YAMLは以下です。
```vaultstaticsecret-sample.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: vaultstaticsecret-sample
  namespace: test
spec:
  hmacSecretData: true
  destination:
    create: true
    #同名のSecretが存在しない場合は新規作成する設定
    overwrite: true
    #同名のSecretが存在する場合に更新する設定
    name: test-secret
    #作成するSecret名
  vaultAuthRef: vaultauth-sample
  #参照するVaultAuthを指定します
  mount: secret
  path: test/test-secret
  #Vault上でSecretのPath情報を確認した場合にCLI pathとして示されるものを入れます
  type: kv-v2
  #Secret Engineのタイプです
  refreshAfter: 5s
```
さて、これでやりたいことは全てできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0a5205f6-6505-dce9-f0dd-8c37c071e0d0.png)


## Secret Engineにある`Secret`が`NameSpace`と同期されるか確認
まずは`NameSpace: test`の開発社向け表示の左メニュー「シークレット」から、`NameSpace`に`apply`された`Secret`を見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/abb9e786-f83d-75d4-c086-f7e6b846884f.png)

あ！`test-secret`が存在します。クリックしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9ee38a80-3435-5eb4-7dfd-526f7afdb4a4.png)

`VaultStaticSecret`によって管理されている事がわかります。`Secret`の中身もVault上で作成した
```test-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque #特に型指定しない場合の一般的なSecretファイル形式です
Data:
  hogehoge: ZnVnYWZ1Z2E= #fugafugaがBASE64エンコードされた文字列です
```
が同期されています。Vault上で`Secret`を編集してみます。適当に`Key-Value`を追加してみて、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d580d00a-146d-e5fa-ff4f-56f9a6009235.png)

「Save」をクリックします。再びOpenShiftのConsoleを見てみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7a742d8b-e12e-7b32-5abe-7db3ae3f3ee6.png)

即座にVault上で更新した内容が`NameSpace`内の`Secret`に反映されています。素晴らしい！

試しに`VaultStaticSecret`を`NameSpace`から削除してみてください。「インストール済みのOperator」メニューからVSOを選択し、VSOが提供する`CustomResource`一覧から「VaultStaticSecretの削除」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/646b1bc0-05ec-58f8-d437-5a9a665e0263.png)

`test-secret.yaml`が消えてしまいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c419372b-37cb-0c02-643f-9353c3cbb5fa.png)

## 応用編： `Secret`更新を受けて`Deployment`の`Rollout`を試す
ここからは少し応用編です。アプリケーションの`Deployment`内で参照する`Secret`の情報が変更された場合、それを反映して`Rollout`させます。これで`Secret`の更新がアプリケーション自体にも自動で反映されるわけです。

:::note info
Kubernetes DeploymentのRolloutについては以下を御覧ください。
https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/#updating-a-deployment
:::

### Mysql(Ephemeral)をDeploy
サンプルの`Deployment`として、PVを利用しないEphemeralなMysqlをDeployしておきます。
適当な`NameSpace`を作成し（私は`mysql-ephemeral`としておきました）、以下のYAMLを`oc apply`してください。

今回は`Secret`を先にClusterに`apply`してしまいます。
```mysql-ephemeral-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-ephemeral-secret
type: Opaque #特に型指定しない場合の一般的なSecretファイル形式です
stringData:
  MYSQL_ROOT_PASSWORD: sousuke
  #rootユーザのパスワードです
  MYSQL_USER: gakenoueno
  #user名です
  MYSQL_PASSWORD: ponyo
  #userのパスワードです
  MYSQL_DATABASE: ghibli
  #Deploy時に自動作成するDatabase名です
```

::: note info
Red Hatが提供しているMysqlのContainer Imageに設定できる環境変数は以下をご参照ください
https://catalog.redhat.com/software/containers/rhel9/mysql-80/61a60915c17162a20c1c6a34?q=mys&architecture=amd64&image=6618250aa47a5279071a75a0
:::

続いて`Deployment`も`apply`してください。
```mysql-ephemeral-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-ephemeral
spec:
  selector:
    matchLabels:
      app: mysql-ephemeral
  strategy:
    type: Recreate
  template:
    metadata:
      labels: 
        app: mysql-ephemeral
    spec:
      containers:
      - image: registry.redhat.io/rhel9/mysql-80 #RedHatが公開している安心安全なMysqlコンテナイメージです。Container Hostからのrootログインに限ってはPWレスでログイン可能。
        name: mysql-ephemeral
        envFrom: #別ファイルから環境変数を読み込む様に指定します
        - secretRef: #Secretを参照するように指定します
            name: mysql-ephemeral-secret #対象のSecretファイル名
        ports:
        - containerPort: 3306 #Mysqlのポート番号です
          name: mysql-ephemeral
```
アプリケーションがDeployできました。`mysql-ephemeral-secret.yaml`で設定した環境変数が反映されているか確認しておきます。`Pod`名をクリックし、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/1bcefcf8-cae1-c087-9673-edbdf00f1cd4.png)

`Pod`の詳細画面の「ターミナル」タブから`oc exec`していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7e8a1d6a-25e2-a129-edf2-dd5477564c88.png)

Mysqlにuserでログインしてみます。
```sh
sh-5.1$ mysql -ugakenoueno -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
```

無事にログインできました。続いてDatabaseが作成されているかも見てみます。
```sql
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| ghibli             |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
```
問題なく`mysql-ephemeral-secret.yaml`からアプリケーションに環境変数が引き渡されています。

### Vault上で`Secret`を作成し、`Policy`&`Role`を作成
二度手間になってしまいますが、同じ`Secret`をSecret Engine（先ほど作ったkv-v2 typeの`secret`という名称のそれ）に、Path名`mysql/mysql-ephemeral-secret`として作成しておきます。以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/13e2171e-026b-11ae-1864-b0a05058e87f.png)

前回同様の手順で同`Secret`に対する参照権限`Policy`を定義します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9acd297d-d585-d5d9-934b-c02e6f9cc365.png)

```json
path "secret/data/mysql/mysql-ephemeral-secret" {
  capabilities = ["read"]
}
```
同じく前回同様の手順で`Role`も作成してください。設定値は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/23f85d6e-9fe6-211a-b1bd-e6212e0dc3e2.png)

### VSOから提供される`CustomResources`を作成。
さて、また前回同様に3つの`CustomResource`を作成します。`NameSpace: mysql-ephemeral`内に以下のYAMLを`oc apply`していってください。中身がどうなっているのか、前回の内容を振り返りながら復習してみてください。
```vaultconnection-mysql-ephemeral.yaml
kind: VaultConnection
apiVersion: secrets.hashicorp.com/v1beta1
metadata:
  name: vaultconnection-mysql-ephemeral
  namespace: mysql-ephemeral
spec:
  address: 'http://vault.vault.svc.cluster.local:8200'
```

```vaultauth-mysql-ephemeral.yaml
kind: VaultAuth
apiVersion: secrets.hashicorp.com/v1beta1
metadata:
  name: vaultauth-mysql-ephemeral
  namespace: mysql-ephemeral
spec:
  vaultConnectionRef: vaultconnection-mysql-ephemeral
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: mysql-ephemeral-role
    serviceAccount: default
```

```vaultstaticsecret-mysql-ephemeral.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: vaultstaticsecret-mysql-ephemeral
  namespace: mysql-ephemeral
spec:
  hmacSecretData: true
  rolloutRestartTargets: #mysql-ephemeral-secretが更新された場合のRollout対象のResourceを設定します。
    - name: mysql-ephemeral
      kind: Deployment
  destination:
    create: true
    overwrite: true
    name: mysql-ephemeral-secret
  vaultAuthRef: vaultauth-mysql-ephemeral
  mount: secret
  type: kv-v2
  path: mysql/mysql-ephemeral-secret
  refreshAfter: 5s
```
`VaultStaticSecret`に前回は無かった行が増えていますね。`spec.rolloutRestartTargets`に`Rollout`対象の`Resource`を設定する事ができます。なお`kind`は`Depoyment`, `DemonSet`, `StatefulSet`の3種類が選択可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/eefc93c6-48c1-7a38-ae70-77f1d7b73448.png)
`VaultStaticSecret`作成画面の「フォームビュー」でGUIからも`rolloutRestartTargets`は設定可能です。

全てのYAMLを`apply`すると、Vault上と`NameSpace: mysql-ephemeral`の`Secret`が同期されます。

### Vault上の`Secret`を変更して`Rollout`がかかるか試す
Vault上で作成した`mysql-ephemeral-secret`をお好きな様に変更してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a368a6e9-c68c-6cab-ed85-2166e1f24e3a.png)

「Save」をクリックすると、直ぐ様`NameSpace: mysql-ephemeral`の`Secret`も更新されます。ここまでは前回と全く同じ挙動です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/77bbec7d-e479-29c5-06cc-f187c57f3821.png)

OpenShiftのConsole画面で、開発者向け表示「トポロジー」メニューに切り替え、`Deployment: mysql-ephemeral`の詳細画面で「イベント」タブを見てみます。何やら自動的にPodが再作成されています。つまり`Rollout`したということです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/dc3eab86-cbd4-6adc-7ec7-98f594008ab0.png)

再作成された`Pod`の詳細画面で「ターミナル」タブから、先ほどと同様に`oc exec`して、user情報やDatabase情報の変更が反映されているか見てみます。

```sh
sh-5.1$ mysql -uneongenesis -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
```

```sql
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| color              |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
```

`rolloutRestartTargets`の設定を行うことで、Vault上の`Secret`の更新が`VaultStaticSecret`を介して、OpenShiftに`Deploy`されたアプリケーションに自動的に反映されることが確認できました。

# おわりに
VaultSecretOperatorを使ってSingle "Secret" of Truthが簡単に実現できる事がわかりました。今回はROSA（AWSのManaged ServiceとしてのOpenShift）で試しましたが、VaultはOpenShift Cluster内で動かすことができますので、同じことはオンプレミスのOpenShiftでも可能です。GitOpsを推進する上でぶち当たる「Secretどうする問題」に対する、ひとつの解決策としてVaultはとても役に立ちそうです。