Secrets Store CSI Driverを使って、Vaultから簡単にシークレット情報を取得しよう

# はじめに
Red Hat OpenShiftはVersion 4.18から「Secrets Store CSI Driver」がGA（General Availability, 一般提供）となりました。

このSecrets Store CSI Driverは、CSI Driverの仕組みを応用して、外部のシークレットストアに保存されている機密情報（シークレット情報）をコンテナに渡すことのできる仕組みです。

>[Secret Store CSI Driver Operator の一般提供開始](https://docs.redhat.com/ja/documentation/openshift_container_platform/4.18/html-single/release_notes/index#ocp-4-18-release-notes-storage-secret-csi-driver_release-notes)
Secrets Store Container Storage Interface (CSI) Driver Operator である secrets-store.csi.k8s.io を使用すると、OpenShift Container Platform がエンタープライズグレードの外部シークレットストアに保存されている複数のシークレット、キー、証明書をインラインの一時ボリュームとして Pod にマウントできます。Secrets Store CSI Driver Operator は、gRPC を使用してプロバイダーと通信し、指定された外部シークレットストアからマウントコンテンツを取得します。ボリュームがアタッチされると、その中のデータがコンテナーのファイルシステムにマウントされます。Secrets Store CSI Driver Operator は、OpenShift Container Platform 4.14 でテクノロジープレビュー機能として利用可能でした。OpenShift Container Platform 4.18 では、この機能の一般提供が開始されました。

このSecrets Store CSI Driverの仕組みを使い、「**Hashicorp Vault**」で管理しているシークレット情報をPodにセキュアに渡してみる、そんな実験をしていきます。

## Hashicorp Vaultとは
HashiCorp Vault は、API や CLI を通じて機密情報（シークレット）を安全に保存・管理・配布できるセキュリティツールです。シークレットの暗号化、動的な認証情報の発行、アクセス制御を統一的に管理できます。クラウドネイティブな環境やマルチテナント構成（※エンタープライズ版の機能）にも柔軟に対応でき、Kubernetesとの連携も可能です。

## IBMによるHashicorp社の買収
IBMは2025年03月10日にHashicorp社の買収を完了しました。IBMとしては、特にVaultについてOpenShiftとの統合を計画しているらしく、今後OpenShiftとよりシームレスにつながり、便利に利用できるようになるかもしれませんね！

:::note 
[IBMがHashiCorp社の買収を完了し、総合的なエンドツーエンドのハイブリッドクラウド・プラットフォームを実現](https://jp.newsroom.ibm.com/2025-03-10-ibm-completes-acquisition-of-hashicorp-creates-comprehensive-end-to-end-hybrid-cloud-platform)
>HashiCorp VaultとRed Hat OpenShiftを統合することで、ハイブリッドクラウド環境全体で強力なシークレット管理とセキュリティー機能を提供します。
:::

## やることのイメージ
これからやることのイメージをざっくり描いてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/01f81377-6f40-493b-bc99-a99c0c930b1c.png)

ちょっと分かりづらいので、それぞれの登場人物の役割や、サンプルアプリにSecret情報が渡される流れを解説します。

### なにが起こるのか？
まずは、OpenShiftクラスタ内にVaultをデプロイします。その際、とある設定を行い「Vault CSI Driver」も合わせてデプロイします。このVault CSI Driverが今回の主役です。これがVaultからSecret情報を取得し、サンプルアプリの特定のパスにその情報をマウント（格納）します。Vault CSI DriverはDaemonSetとして、OpenShiftの各Computeノード（Workerノード）にデプロイされます。


なお、そのための認証や「具体的にどのSecret情報を取得するのか？」などの定義を司るのが、「Secret Provider Class」というカスタムリソースです。このSecret Provider Classは、Red Hatが提供する公式Operator「Secrets Store CSI Driver Operator」が提供するAPIを用いて作成・管理されます。

:::note
なお、Kubernetes/OpenShiftにおいて、Vaultからワークロードにシークレット情報を渡す手法は、Secrets Store CSI Driver以外にも存在しています。以前別の記事にて、VSO（Vault Secret Operator）を用いた方法を解説しました。よろしければご参照ください。
[OpenShiftにHashicorp VaultをDepolyしてSingle "Secret" of Truthを実現しよう](https://qiita.com/masaki-oomura/items/4444cb77e8e90dd78766)
:::

### サンプルアプリについて
今回はSecrets Store CSI Driverの挙動をわかりやすく理解するためにサンプルアプリを用意しました。このアプリはNode.jsで作成した非常に単純なアプリで、コンテナの`/mnt/secrets-store/`内に保存されている`secret-info`というファイルの中身（例：`vaule123`）を表示するだけのアプリです。

**サンプルアプリのスクショ**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/70112639-abef-4b11-b7f8-6b36cdf81b92.png)

### 今回使用するソースコード等の一式
今回使うするマニフェストファイルやサンプルアプリのソースコード一式は、以下の公開リポジトリに格納しています。
- https://gitlab.com/masaki-oomura/vault-csi-driver-app

また、サンプルのソースコードからビルドしたコンテナイメージは以下に格納しています。
- https://quay.io/repository/rh-ee-moomura/vault-csi-driver-app


### 実践
それでは、早速やっていきましょう。なお、今回は「OpenShiftのバージョン：4.18.8」を利用しています。また、ROSA (Red Hat OpenShift Service on AWS, AWSのマネージドサービスとして利用できるOpenShift）を活用し、さくっと簡単にOpenShiftのクラスタを払い出しています。

:::note
無料のRed Hatアカウントさえ作れば、ROSAがすぐに試せる「[Red Hat OpenShift Service on AWS Hands-on Experience](https://console.redhat.com/openshift/overview/rosa/hands-on)」を利用しましょう。Cluster-admin権限をもったアカウントがもらえるので、自由にOperatorもインストール可能です。なお、当該環境は8時間×3回まで利用できます。詳しい利用開始までの流れは、[日本語ガイド](https://www.redhat.com/ja/explore/rosa-hands-on)を参照ください。
:::

# Secrets Store CSI Driver Operatorをインストール
同OperatorをOperatorHubからインストールします。なお、以後の操作はすべてCluster-admin権限を持つOpenShiftユーザにて実施しています。


管理者表示の「Operator」メニューから、「OperatorHub」を選択し、検索欄に「Secrets Store CSI Driver Operator」と入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/55ae2832-df1f-4614-a2de-3db130de5352.png)

これをデフォルト設定のままインストールします。カスタムリソース「Secret Provider Class」は後ほど作成しますので、このタイミングではインストールが完了すればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d9573f80-e396-4cf1-9a9e-ffe510e3249d.png)

# Vaultをインストール
さて、次はVaultをインストールします。

## Namespaceの作成と設定
その前に、VaultをデプロイするためのNamespaceを作成します。OpenShiftのコンソール画面を開発者向け表示に切り替え、「すべてのプロジェクト」をクリック、「プロジェクトの作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/48f32d13-a2cb-4b06-81ad-0eff2949ebe9.png)

プロジェクト名には「vault」と入力し、「作成」をクリックします。プロジェクト（Namespace）が作成できたら、OpenShiftのcluster-adminアカウントでログイン済みのCLIから以下のコマンドを適用し、Namespace「Vault」に各種ラベルを適用します。

```sh
oc label ns vault \
  security.openshift.io/scc.podSecurityLabelSync=false \
  pod-security.kubernetes.io/enforce=privileged \
  pod-security.kubernetes.io/audit=privileged \
  pod-security.kubernetes.io/warn=privileged \
  --overwrite
```

このコマンドにより、PSA（Pod Secrity AdmissionsとSCC（Security Context Constraints）間のラベル同期を無効（false）にします。なお、PSAはKubernetesオリジナルのPodセキュリティ設定の仕組み、SCCはOpenShift独自のそれです。また、PSAには各種モード（`enforce`, `audit`, `warn`）がありますが、それぞれのモードについて全て`privileged`を設定します。これにより、当該ラベルを付与されたNamespaceにデプロイされるPodは当該セキュリティ標準が適用されます。特権は特にCSI Driverを司るDaemonSetに求められる権限です。

:::note
PSAについて詳しく知りたい方は[Kubernetes公式ドキュメント](https://kubernetes.io/docs/concepts/security/pod-security-admission/)の参照をお勧めします。また、以下の記事の説明が大変わかりやすかったです。
[Pod Security AdmissionでPodの特権を制限する](https://qiita.com/comware_higashi/items/b8b2d90c15b57223713e)
:::



## Helmチャートを選択
次に「+追加」メニューから「Helmチャート」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/48b9e305-20a1-44d9-ae78-3752a0d25feb.png)

VaultのインストールはHashicorpが提供しているHelmチャートを利用します。検索欄に「vault」と入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8be2785f-b983-4cfb-8e5b-a4171b2f90a9.png)

「Hashicorp Vault」をクリックし、「作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/72bbf4e8-8d08-47f7-ba72-45b1f2cbd343.png)


## YAMLの編集
ここでマニフェスト編集画面（YAMLビュー）になります。このYAMLについて、以下のものに置き換えてください。

```yaml
csi:
  agent:
    enabled: true
    image:
      pullPolicy: IfNotPresent
      repository: hashicorp/vault
      tag: 1.19.0
    logFormat: standard
    logLevel: info
  daemonSet:
    kubeletRootDir: /var/lib/kubelet
    providersDir: /var/run/secrets-store-csi-providers
    updateStrategy:
      type: RollingUpdate
  debug: false
  enabled: true
  hostNetwork: false
  image:
    pullPolicy: IfNotPresent
    repository: hashicorp/vault-csi-provider
    tag: 1.5.0
  livenessProbe:
    failureThreshold: 2
    initialDelaySeconds: 5
    periodSeconds: 5
    successThreshold: 1
    timeoutSeconds: 3
  logLevel: info
  readinessProbe:
    failureThreshold: 2
    initialDelaySeconds: 5
    periodSeconds: 5
    successThreshold: 1
    timeoutSeconds: 3
global:
  enabled: true
  openshift: true
  psp:
    annotations: >
      seccomp.security.alpha.kubernetes.io/allowedProfileNames:
      docker/default,runtime/default

      apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default

      seccomp.security.alpha.kubernetes.io/defaultProfileName:  runtime/default

      apparmor.security.beta.kubernetes.io/defaultProfileName:  runtime/default
    enable: false
  serverTelemetry:
    prometheusOperator: false
  tlsDisable: true
injector:
  affinity: |
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              app.kubernetes.io/name: {{ template "vault.name" . }}-agent-injector
              app.kubernetes.io/instance: "{{ .Release.Name }}"
              component: webhook
          topologyKey: kubernetes.io/hostname
  agentDefaults:
    cpuLimit: 500m
    cpuRequest: 250m
    memLimit: 128Mi
    memRequest: 64Mi
    template: map
    templateConfig:
      exitOnRetryFailure: true
  agentImage:
    repository: registry.connect.redhat.com/hashicorp/vault
    tag: 1.19.0-ubi
  authPath: auth/kubernetes
  certs:
    certName: tls.crt
    keyName: tls.key
  enabled: false
  failurePolicy: Ignore
  hostNetwork: false
  image:
    pullPolicy: IfNotPresent
    repository: registry.connect.redhat.com/hashicorp/vault-k8s
    tag: 1.6.2-ubi
  leaderElector:
    enabled: true
  livenessProbe:
    failureThreshold: 2
    initialDelaySeconds: 5
    periodSeconds: 2
    successThreshold: 1
    timeoutSeconds: 5
  logFormat: standard
  logLevel: info
  metrics:
    enabled: false
  port: 8080
  readinessProbe:
    failureThreshold: 2
    initialDelaySeconds: 5
    periodSeconds: 2
    successThreshold: 1
    timeoutSeconds: 5
  replicas: 1
  revokeOnShutdown: false
  startupProbe:
    failureThreshold: 12
    initialDelaySeconds: 5
    periodSeconds: 5
    successThreshold: 1
    timeoutSeconds: 5
  webhook:
    failurePolicy: Ignore
    matchPolicy: Exact
    objectSelector: |
      matchExpressions:
      - key: app.kubernetes.io/name
        operator: NotIn
        values:
        - {{ template "vault.name" . }}-agent-injector
    timeoutSeconds: 30
server:
  affinity: |
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              app.kubernetes.io/name: {{ template "vault.name" . }}
              app.kubernetes.io/instance: "{{ .Release.Name }}"
              component: server
          topologyKey: kubernetes.io/hostname
  auditStorage:
    accessMode: ReadWriteOnce
    enabled: false
    mountPath: /vault/audit
    size: 10Gi
  authDelegator:
    enabled: true
  dataStorage:
    accessMode: ReadWriteOnce
    enabled: true
    mountPath: /vault/data
    size: 10Gi
  dev:
    devRootToken: root
    enabled: false
  enabled: '-'
  enterpriseLicense:
    secretKey: license
  ha:
    config: >
      ui = true


      listener "tcp" {
        tls_disable = 1
        address = "[::]:8200"
        cluster_address = "[::]:8201"
      }

      storage "consul" {
        path = "vault"
        address = "HOST_IP:8500"
      }


      service_registration "kubernetes" {}


      # Example configuration for using auto-unseal, using Google Cloud KMS. The

      # GKMS keys must already exist, and the cluster must have a service
      account

      # that is authorized to access GCP KMS.

      #seal "gcpckms" {

      #   project     = "vault-helm-dev-246514"

      #   region      = "global"

      #   key_ring    = "vault-helm-unseal-kr"

      #   crypto_key  = "vault-helm-unseal-key"

      #}


      # Example configuration for enabling Prometheus metrics.

      # If you are using Prometheus Operator you can enable a ServiceMonitor
      resource below.

      # You may wish to enable unauthenticated metrics in the listener block
      above.

      #telemetry {

      #  prometheus_retention_time = "30s"

      #  disable_hostname = true

      #}
    disruptionBudget:
      enabled: true
    enabled: false
    raft:
      config: |
        ui = true

        listener "tcp" {
          tls_disable = 1
          address = "[::]:8200"
          cluster_address = "[::]:8201"
          # Enable unauthenticated metrics access (necessary for Prometheus Operator)
          #telemetry {
          #  unauthenticated_metrics_access = "true"
          #}
        }

        storage "raft" {
          path = "/vault/data"
        }

        service_registration "kubernetes" {}
      enabled: false
      setNodeId: false
    replicas: 3
  hostNetwork: false
  image:
    pullPolicy: IfNotPresent
    repository: registry.connect.redhat.com/hashicorp/vault
    tag: 1.19.0-ubi
  includeConfigAnnotation: false
  ingress:
    activeService: true
    enabled: false
    hosts:
      - host: chart-example.local
    pathType: Prefix
  livenessProbe:
    enabled: false
    failureThreshold: 2
    initialDelaySeconds: 60
    path: /v1/sys/health?standbyok=true
    periodSeconds: 5
    port: 8200
    successThreshold: 1
    timeoutSeconds: 3
  networkPolicy:
    enabled: false
    ingress:
      - ports:
          - port: 8200
            protocol: TCP
          - port: 8201
            protocol: TCP
  preStopSleepSeconds: 5
  readinessProbe:
    enabled: true
    failureThreshold: 2
    initialDelaySeconds: 5
    path: /v1/sys/health?uninitcode=204
    periodSeconds: 5
    port: 8200
    successThreshold: 1
    timeoutSeconds: 3
  route:
    host: ''
    tls:
      termination: edge
      insecureEdgeTerminationPolicy: Redirect
    activeService: true
    enabled: true
  service:
    active:
      enabled: true
    enabled: true
    externalTrafficPolicy: Cluster
    instanceSelector:
      enabled: true
    port: 8200
    publishNotReadyAddresses: true
    standby:
      enabled: true
    targetPort: 8200
  serviceAccount:
    create: true
    createSecret: false
    serviceDiscovery:
      enabled: true
  shareProcessNamespace: false
  standalone:
    config: >-
      ui = true


      listener "tcp" {
        tls_disable = 1
        address = "[::]:8200"
        cluster_address = "[::]:8201"
        # Enable unauthenticated metrics access (necessary for Prometheus Operator)
        #telemetry {
        #  unauthenticated_metrics_access = "true"
        #}
      }

      storage "file" {
        path = "/vault/data"
      }


      # Example configuration for using auto-unseal, using Google Cloud KMS. The

      # GKMS keys must already exist, and the cluster must have a service
      account

      # that is authorized to access GCP KMS.

      #seal "gcpckms" {

      #   project     = "vault-helm-dev"

      #   region      = "global"

      #   key_ring    = "vault-helm-unseal-kr"

      #   crypto_key  = "vault-helm-unseal-key"

      #}


      # Example configuration for enabling Prometheus metrics in your config.

      #telemetry {

      #  prometheus_retention_time = "30s"

      #  disable_hostname = true

      #}
    enabled: '-'
  terminationGracePeriodSeconds: 10
  updateStrategyType: OnDelete
serverTelemetry:
  prometheusRules:
    enabled: false
  serviceMonitor:
    enabled: false
    interval: 30s
    scrapeTimeout: 10s
ui:
  activeVaultPodOnly: false
  enabled: false
  externalPort: 8200
  externalTrafficPolicy: Cluster
  publishNotReadyAddresses: true
  serviceType: ClusterIP
  targetPort: 8200
```

設定変更した箇所（4箇所）について簡単に解説します。

### csi.enabled（必須）
```diff_yaml
csi:
...
- enabled: false
+ enabled: true
...
```

実は、VaultのHelmチャートのデフォルトでは「Vault CSI Driver」の機能が無効（`false`）になっています。それを有効（`true`）にします。

### csi.daemonSet.providersDir（必須）
```diff_yaml
csi:
...
- daemonSet:
-   providersDir: /etc/kubernetes/secrets-store-csi-providers
+ daemonSet:
+   providersDir: /var/run/secrets-store-csi-providers
...
```

先ほど述べた通り、Vault CSI DriverはDaemonSetとして各Computeノードにデプロイされます。その際の設定を行なっています。なお、ここで値を変更した「providersDir」とは、Secrets Store CSI Driver が Vaultなどのシークレットプロバイダと通信するためのソケットファイルを探すディレクトリです。

>[Secret Store CSI Driver Operator の一般提供開始](https://docs.redhat.com/ja/documentation/openshift_container_platform/4.18/html-single/release_notes/index#ocp-4-18-release-notes-storage-secret-csi-driver_release-notes)
Secrets Store CSI Driver Operator は、gRPC を使用してプロバイダーと通信し、指定された外部シークレットストアからマウントコンテンツを取得します。

Secrets Store CSI Driver は、Provider（Vaultなど）と gRPC で通信する必要がありますが、この通信はノード内に閉じて行われます。その際にUnixドメインソケットを通じて通信しています。そのソケットファイルが置かれるディレクトリが`/var/run/secrets-store-csi-providers`になります。このため、Vault CSI Provider 側のDaemonSetがこのパスにソケットをexpose（公開）している必要があるわけです。

### server.route（GUIを利用する場合は必須）
Helmチャートのデフォルト設定では、OpenShiftのRouteが無効化されているため、以下のように有効化しておきます。ここで`host`については特に指定せずに空欄とし、OpenShiftに自動的に作成してもらいます。また、TLS終端（Edge）をRouteとし、HTTPアクセスは自動的にHTTPSにリダイレクトさせるものとします。

```diff_yaml
server:
...
  route:
    activeService: true
-   enabled: false
-   host: chart-example.local
-   tls:
-     termination: passthrough
+   enabled: true
+   host: ''
+   tls:
+     termination: edge
+       insecureEdgeTerminationPolicy: Redirect
...
```

### injector.enabled（任意）
```diff_yaml
injector:
...
- enabled: true
+ enabled: false
...
```
最後は、インジェクタの動作を無効（false）にします。このインジェクタは、「Vault Agent方式」と呼ばれる、「Secrets Store CSI Driver方式」とは別の方式を利用する際に利用するDeploymentです。今回はインジェクタは不要なので、無効化していますが、デプロイされたとしても「Secrets Store CSI Driver方式」の挙動には影響はありません。

::: note
Vault Agent方式についての詳細は、[公式サイト](https://www.hashicorp.com/ja/blog/kubernetes-vault-integration-via-sidecar-agent-injector-vs-csi-provider)をご覧ください。
:::

VaultからPodへのシークレット情報連携においてSecrets Store CSI Driverを利用する上では、上記の設定にて十分です。その他、VaultのHA機能などを利用する場合は、必要に応じて設定してください。

YAMLを差し替えたら、「作成」をクリックし、Vaultをデプロイします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/1443ab67-dba6-4f0a-9080-709be47af33e.png)

実はまだいくつか設定が必要です。このままだと、Vault CSI DriverのDaemonSetがいつまでも起動しません。

## VaultおよびCSI Provider用にprivileged SCCを付与
まずは、CLIで以下のコマンドを適用します。
```sh
oc adm policy add-scc-to-user privileged -z vault -n vault
oc adm policy add-scc-to-user privileged -z vault-csi-provider -n vault
```
これらのコマンドは、Namespace「vault」に存在するServiceAccount（vault および vault-csi-provider）に、privileged SCC（Security Context Constraints）を付与するものです。これにより、両サービスアカウントが特権的な操作を実行できるようになります。

Vault CSI Driver は、先述の通りhostPath マウントを使用して Unix ソケット (vault.sock) を介した Secrets Store CSI Driver とのノード内通信を行うため、特権モードが必要です。
また、Vault本体（サーバー側）についても、ストレージへの書き込みやノードレベルのネットワーク設定など、OpenShiftの標準的な制約を超える操作を安定して行うために特権が求められます。

なお、Helmチャートを使用してVaultをデプロイする場合、Namespace「vault」内には、以下のサービスアカウントが自動的に作成されます。
- vault（Vaultサーバー用）
- vault-csi-provider（Vault CSI Provider用）

これらのServiceAccountに適切に権限を付与することで、VaultおよびVault CSI Driverが正しく動作するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e4542d96-cda4-4556-bced-4f6b13d8e388.png)

## 明示的にVault CSI DriverのPodに特権を付与
以下のコマンドを適用し、起動しかかり中の当該Podに対して、特権を付与してあげます。

```sh
oc patch daemonset -n vault vault-csi-provider --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"privileged": true} }]'
```

これで無事、DaemonSetのデプロイが完了しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5e36df6e-7e44-4a65-8347-f3c621015cd2.png)

:::note warn
もし一部のComputeノードに`Taints`を付与している場合は、DaemonSet側に`Torelations`を設定してあげないと、そのComputeノードにDaemonSetがデプロイされないので、お気をつけください。
- [TaintとToleration](https://kubernetes.io/ja/docs/concepts/scheduling-eviction/taint-and-toleration/)

後からTorelations（容認）を設定する場合は、以下の手順で可能です。

1. Daemonset「vault-csi-provider」の詳細画面の「容認」欄から「0件の容認」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/eb609ce7-59da-4643-8f02-9745b11de578.png)

2. 「追加する」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ccbdc749-9f42-4a72-a82c-68cb42daa6a3.png)

3. Taintsの内容に対応するkey-value、エフェクト等を入力し「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/bdab9f63-53c8-4c93-9005-8c2d3e4b23a3.png)

Podの台数が所望の台数に増えればOKです。
:::

# Vaultをセットアップ
さて、VaultのGUIにアクセスし、初期設定を行います。Routeの提供するURLをブラウザで開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d05a4fc3-493a-4dcb-9479-492e513dd5d7.png)

## Unseal
Vaultは起動直後は暗号化（Sealed）されています。暗号化を解除（Unseal）するためには鍵が必要なのですが、その鍵の設定画面が最初に開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/52f02c2f-9c3f-4603-b9d6-f3dfc23101ff.png)

- `Key shares`: 払い出す鍵の数
- `Key threshold`: 払い出した鍵のうち、Unsealするために必要な鍵の数

今回は簡単のため、それぞれ1つずつにしておきます。なお、VaultのPodが再起動した際等には、再び鍵によるUnsealが必要になります。鍵を無くすと初期化が必要になり、その際にはVault内のシークレット情報や設定値が消えることになります。

適当な`Key shares`と`Key threshold`を設定したら、「Initialize」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7d9a3c0e-a857-48cb-a0e0-40b107d4f04f.png)

「Downlaod keys」をクリックして、鍵と初期ルートトークンをダウンロードします。これらは適切に管理するようにしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b0682184-fe0e-4677-a432-691e4c14a2a1.png)

今保存した鍵を使ってUnsealします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/10e36361-ba13-4a7f-9963-f04960e2e07b.png)

最後に、初期ルートトークンを用いてルートユーザでログインします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a4d60548-0768-4667-aceb-b45d97d6e360.png)

これでVaultの初期設定が完了しました。

### Kubernetes認証
次にVaultと連携先のOpenShft(=Kubernetes）の認証設定を行います。VaultのGUIの左側にある「Access」メニューから「Authentication Methods」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/3efcf294-0273-4172-b3d6-db0a4583fc17.png)

画面右上にある「Enable new method +」をクリックし、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7f525ed1-83d8-49d9-ad9b-87f0adad2402.png)

「Kubernetes」を選択、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8830c622-3f06-4845-bd23-3ca0ec930c34.png)

「Enable method」をクリックします。次の画面にはなにやら色々情報を入力する必要がありそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/16128c56-90cb-47d3-98b1-ea1fb87a33e7.png)

今回入力が必要な情報について説明します。

### Kubernetes host
値：`https://kubernetes.default.svc.cluster.local:443`
Kubernetesクラスタを操作するためのAPIを入力します。なお、今回はVaultはKubernetes Clusterの上で動いている為、内部ホスト名（同左）を指定することが可能です。

### Kubernetes CA Certificate
値：`ConfigMap「kube-root-ca.crt」の値`
Kubernetes host」からAPIを叩く際に必要な認証情報です。これはKubernetesの全てのNamespaceに自動的に作成されるConfigMap「kube-root-ca.crt」のKey: `ca.crt`の値です。

:::note
OpenShiftの開発者向け表示において、任意のNamespaceを選択した状態で「ConfigMap」メニューから確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e4443bb8-6e3a-4a7c-aeda-6ced0b9c562c.png)

この一番最初の`-----BEGIN CERTIFICATE-----`から一番最後の`-----END CERTIFICATE-----`までをコピーします。
```yaml
-----BEGIN CERTIFICATE-----
MIIDPDCCA...
-----END CERTIFICATE-----
```
:::

### Token Reviewer JWT

値：コンテナ「vault-0」`/var/run/secrets/kubernetes.io/serviceaccount/token`の中身
「Token Reviewer JWT」とは、KubernetesのServiceAccountに自動で割り当てられるトークンであり、ServiceAccountが関連づけられたPodの同パスに格納されます。　つまり、Kubernetes APIに対して当該トークンを持つServiceAccount「Vault」を確認するために使う認証情報です。

:::note
StatefulSet「Vault」の詳細画面からPod「vault-0」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/084c55a9-ae2d-4660-a2f7-918439fd04e4.png)

ターミナル画面にて以下のコマンドを実行します。
```sh
cat /var/run/secrets/kubernetes.io/serviceaccount/token
```

すると、以下のようにToken Reviewer JWTを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ff072e1b-ef77-4d0a-9484-56d727ec469f.png)

これをコピペします。
:::

必要な値がそれぞれ入力できたら、「Save」をクリックしてKubernete認証設定を保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b30be592-9659-4192-ae8c-7528de909651.png)

:::note
なお、Pod「vault-0」のターミナルで以下のコマンドを実行しても同じように同認証設定が可能です。

```sh
## Vaultにログイン
vault login <root token>

## Kubernete認証を有効化
vault auth enable kubernetes

## Token Reviewer JWTを環境変数に設定
TOKEN_REVIEWER_JWT="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"

## Kubernetes CA Certificateを環境変数に設定
KUBERNETES_CA_CERTIFICATE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)"

## Kubernetes認証設定
vault write auth/kubernetes/config \
  issuer="https://kubernetes.default.svc.cluster.local" \
  token_reviewer_jwt="${TOKEN_REVIEWER_JWT}" \
  kubernetes_host="https://kubernetes.default.svc.cluster.local:443" \
  kubernetes_ca_cert="${KUBERNETES_CA_CERTIFICATE}"
```
ぶっちゃけコマンド操作の方が楽ですね...
なお、こちらの手順は[OpenShiftのドキュメント](https://docs.redhat.com/ja/documentation/openshift_container_platform/4.18/html/nodes/nodes-pods-secrets-store#secrets-store-vault_nodes-pods-secrets-store)にも記載されています。
:::

お疲れ様でした。ここまででVaultのKubernetes認証が完了しました。

## Secret Engineを有効化
続いて、VaultのSecret Engineを有効化します。GUI左の「Secret Engines」メニューを選択し、「Enable new engine」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/1fc57c58-3b8a-4593-9d13-6dd1e442a949.png)

Vaultはさまざまなサービスや連携先に対応したSecret Engineの種類を用意していますが、今回は最も汎用的な「Key-Valune形式（KV）」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0b4ba3e7-88ad-48f1-91af-adcb94e9104b.png)

「Path」欄に`secret`と入力し、「Enable engine」をクリックしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a76c12e7-df33-441b-91e9-967c44cff050.png)

## シークレット情報を登録
次にサンプルとなるKey-Value形式のシークレット情報を登録します。「Create secret」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/bb2e0feb-7956-4214-ad30-5b0a815a519a.png)

今回は以下の通り値を登録します。
- Path for this secret: `example`
- Secret data: 
    - key: `testSecret1`
    - value: `value123`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/2258db3a-e1b1-454d-9c26-f52bbf57d750.png)

値を入力したら「Save」をクリックして保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4df832b0-7d2a-4f77-ac16-f90fe1355599.png)

今登録したシークレット情報「example」の詳細を確認できる画面になります。ここで「API path」が後ほど必要な情報なので、覚えておいてください。

:::note
なお、この一連の操作はコマンドでも実行可能です。
```sh
## Vaultにログイン
vault login <root token>

## KVタイプのSecret Engineを有効化しシークレット情報を追加
vault kv put secret/example testSecret1=value123
```
あきらかにコマンドの方が楽ですね...
:::

## ポリシーを作成
次に、今作成したシークレット情報に対する参照権限を持つポリシーを作成します。GUIの左メニューから「Policies」を選択し、さらに「ACL Policies」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a81db07f-4419-45ed-9b2e-b774e4b77fe5.png)

すでに2つのポリシーが作成されています。「default」はその名の通り、defaultで用意されているもの、「root」はルートトークンでログインするユーザ（ルートユーザ）のポリシーです。ここでは「Create ACL policy +」をクリックします。今回は以下の通り値を入力します。
- Name: csi
- Policy: 
    ```hcl
    path "secret/data/*" {
      capabilities = ["read"]
      }
    ```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9b45ac02-c1c5-4aa7-ad4c-130203ab3488.png)

このポリシーは、先ほど作成したKVタイプのSecret Engine「secret」内の全てのシークレットの参照権限を定義したポリシーです。「Create policy」をクリックします。

:::note
ポリシー作成もコマンドで実行可能です。
```sh
## Vaultにログイン
vault login <root token>

## ポリシー作成
vault policy write csi -<<EOF
  path "secret/data/*" {
  capabilities = ["read"]
  }
  EOF
```
:::

## ロールを作成
最後に、Kubernetes認証の仕組みを介して、先ほど作成したポリシーに即してシークレット情報を取得する「ロール」を作成します。再びVaultのGUIの左側にある「Access」メニューから「Authentication Methods」を選択し、先ほど設定を行った「Kubernetes」認証をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/bc45ef3e-34fa-473a-9f0e-49b97a0b276d.png)

続いて「Create role」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/55e3ea7c-b8ad-4285-b73b-f07cab574596.png)

ここでは以下の通り値を設定します。
- Name: `csi`
- Bound service account names: `default`
- Bound service account namespaces: `sample-app`
- Generated Token's Policies: `csi`

このロールは「**OpenShiftのNamespace「sample-app」に作成されたService Account「default」が、ポリシー「csi」に基づいてシークレット情報を取得するための「csi」という名称のロール**」を意味しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/61c94aa9-add1-4c65-b98f-9f4758f0494a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/49b92f35-124c-4b1d-a327-d4819854bc86.png)

値を入力し終えたら「Save」をクリックしてロールの作成を完了します。

:::note
ロール作成もコマンドで実行可能です。
```sh
## Vaultにログイン
vault login <root token>

## ロール作成
vault write auth/kubernetes/role/csi \
  bound_service_account_names=default \
  bound_service_account_namespaces=sample-app \
  policies=csi```
:::

お疲れ様でした。これでVaultのセットアップとシークレット情報の登録、ポリシー＆ロール作成が完了しました。次以降はいよいよコンテナにシークレット情報を渡すところまでをやっていきます。

# Podにシークレット情報を連携
ここからは、先ほどセットアップしたVaultの中に作成済みのシークレット情報を、コンテナに渡すところまでをやっていきます。

## サンプルアプリ用のNamespaceを作成
先ほどNamespace「vault」を作成した時と同じ要領で「sample-app」という名称のNamespaceを作成しておいてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/54f9e19c-801d-4655-a080-c0f9954973e2.png)

## SecretProviderClassを作成
次にカスタムリソース「SecretProviderClass」を作成します。OpenShiftのコンソール画面を「管理者向け表示」とし、左メニューの「Operator」から「インストール済みのOperator」をクリック、インストール済みOperator一覧から「Secrets Store CSI Driver Operator」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5e590278-be2c-4921-b926-ea219ed457e4.png)

「SecretProviderClass」の欄から「インスタンスの作成」をクリックします。YAMLビューに切り替え、以下のYAMLをコピペします。

```yaml
kind: SecretProviderClass
apiVersion: secrets-store.csi.x-k8s.io/v1
metadata:
  name: example-vault-provider
  namespace: sample-app
spec:
  provider: vault #シークレットプロバイダ名「vault」を指定
  parameters:                               
    roleName: "csi" #Vaultからシークレット情報を取得するロールを指定
    vaultAddress: "http://vault.vault.svc.cluster.local:8200" #OpenShiftクラスタ内からアクセスできるホスト名を指定
    objects:  |
      - secretPath: "secret/data/example"
        secretKey: "testSecret1"
        objectName: "secret-info"
```

ここで、以下のパラメータの意味について補足します。
- `secretPath`: Vault内のシークレットの格納パス（API path）を指定
- `secretKey`: シークレット情報のKey
- `objectName`: コンテナにマウントする際のファイル名


:::note
Vaultの内部ホスト名はServiceの詳細から確認可能です。

![スクリーンショット 2025-04-23 0.16.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/771a855d-5768-4292-9d0a-064d8ee63a72.png)

このHostname「`vault.vault.svc.cluster.local`」の先頭に`http://`をつけ、末尾にPort番号「`8200`」をつければOKです。
:::

YAMLをコピペしたら「作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/dbafdcb5-1a41-4d02-a391-b1c1651bcc04.png)

カスタムリソース「SecretProviderClass」が作成できました。それではいよいよ、サンプルアプリをデプロイしましょう。

## サンプルアプリのマニフェストを適用
以下にマニフェストを用意しました。これらをNamespace「sample-app」に適用しています。このサンプルアプリのソースコードは[こちら](https://gitlab.com/masaki-oomura/vault-csi-driver-app)に格納しています。このアプリは環境変数`SECRET_PATH`を持っており、このパスに格納されているテキストファイル（シークレット情報）の内容を画面に表示することができます。

```configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: vault-csi-driver-app
data:
  SECRET_PATH: '/mnt/secrets-store/secret-info'
```

```service.yaml
kind: Service
apiVersion: v1
metadata:
  name: vault-csi-driver-app
  labels:
    app: vault-csi-driver-app
spec:
  ports:
    - name: 3000-tcp
      protocol: TCP
      port: 3000
      targetPort: 3000
  type: ClusterIP
  selector:
    app: vault-csi-driver-app
```

```route.yaml
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: vault-csi-driver-app
  labels:
    app: vault-csi-driver-app
spec:
  to:
    kind: Service
    name: vault-csi-driver-app
  port:
    targetPort: 3000-tcp
  tls:
    termination: edge
```

```deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vault-csi-driver-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vault-csi-driver-app
  template:
    metadata:
      labels:
        app: vault-csi-driver-app
    spec:
      containers:
      - name: vault-csi-driver-app
        image: quay.io/rh-ee-moomura/vault-csi-driver-app:latest
        ports:
        - containerPort: 3000
        envFrom:
        - configMapRef: ##環境変数を読み込みます
            name: vault-csi-driver-app
        volumeMounts:
        - name: secrets-store-inline
          mountPath: "/mnt/secrets-store" ##シークレット情報を/mnt/secrets-storeに格納します
          readOnly: true
      volumes:
        - name: secrets-store-inline
          csi: ##ボリュームにCSIを指定します
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes: ##先ほど作成したSecretProviderClassを指定します
              secretProviderClass: "example-vault-provider"
```

:::note
マニフェストの適用方法はいくつかあります。OpenShiftのコンソール画面右上の「+ボタン」から、「YAMLをインポート」をクリックすると、YAML貼り付け画面が出てきます。「作成」をクリックすれば、選択されているプロジェクト（Namespace）にマニフェストが適用されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8f1a5468-0b4b-434b-a795-d4f20bd2cd83.png)

また、本記事で利用するマニフェストは[Gitlabの公開リポジトリ](https://gitlab.com/masaki-oomura/vault-csi-driver-app)に格納してありますので、そのURLを用いて、ocコマンドで適用することも可能です。

```sh
## ConfigMapの適用
oc apply -f https://gitlab.com/masaki-oomura/vault-csi-driver-app/-/raw/main/manifest/configmap.yaml

## Serviceの適用
oc apply -f https://gitlab.com/masaki-oomura/vault-csi-driver-app/-/raw/main/manifest/service.yaml

## Routeの適用
oc apply -f https://gitlab.com/masaki-oomura/vault-csi-driver-app/-/raw/main/manifest/route.yaml

## Deploymnetの適用
oc apply -f https://gitlab.com/masaki-oomura/vault-csi-driver-app/-/raw/main/manifest/deployment.yaml
```

:::

Namespace「sample-app」を確認すると、アプリケーションがデプロイされていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/86115877-d968-4860-96e8-c67e0ba72dae.png)

RouteのURLをクリックして、アプリの画面にアクセスしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4e8fb487-9b5d-4478-9bb8-7b446db9c34c.png)

このように、シークレット情報のkey「testSecret1」に対応するvalue「value123」が表示されました。
これにより、Vaultからコンテナアプリケーションに対してシークレット情報が渡されたことが確認できます。

次に、コンテナにターミナルで接続し、`/mnt/secrets-store/`に`secret-info`が存在するのか確認しておきます。Pod名「vault-csi-driver-<ランダム文字列>」をクリックし、「ターミナル」タブから以下のコマンドを入力してみます。

```sh
cat /mnt/secrets-store/secret-info
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/49660915-fd21-4092-bda4-10826dab637e.png)

確かにシークレット情報が指定したパスにファイルとしてマウントされていることが確認できました。

# Vault上のシークレット情報変更が反映されるか確認
次に、Vault上のシークレット情報を変更した場合、アプリケーションにちゃんと反映されるか？を確認します。

## シークレット情報の更新
再びVaultのKV型シークレットエンジン「secret」にアクセスし、先ほど作成したシークレット情報「example」を確認します。ここで右上の「+ Create new version」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/2cd9cb8d-fab2-4f8d-8beb-9faec0356400.png)

ここではシークレット情報の編集・更新が可能です。ここでは、key「testSecret1」のvalueを「value456」に変更してみましょう。変更できたら、「Save」をクリックし、変更を保存してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ee04a931-0121-4131-833b-83502206588c.png)

## アプリケーションに反映
Vault上のシークレット情報が変更されたものの、アプリケーションに表示されるシークレット情報は「value123」のままのはずです。シークレット情報の変更反映は、コンテナを再作成したタイミングで反映されます。では、Deploymentをロールアウトしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a1798ae6-1c73-4cbf-a195-0b4e18ff6ffa.png)

OpenShiftのコンソール画面の「トポロジー表示」において、Deploymentアイコンの右側「︙」をクリックし、メニュー一覧から「ロールアウトの再開」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c7a347cd-f270-459d-8361-f7b62357ded5.png)

すると、コンテナが再作成され、あたらしいPodが立ち上がります。この状態でサンプルアプリの画面を更新します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8d112d43-e63c-445e-ae51-79aff4f54397.png)

このようにVault上のシークレット情報の変更が、コンテナアプリケーションに反映されていることがわかります。

# Kubernetes Secret自動作成
最後に、Vaultのシークレット情報をPodにマウントする際に、「Kubernetes Secret を自動的に作成する」というオプションを試してみましょう。

というのも、Vaultのシークレットはコンテナ内の指定パスにファイルとしてマウントされるものの、Kubernetesクラスタ内にはその情報が保存されていないためです。ユースケースによっては、KV型シークレットエンジンで管理している情報を、コンテナアプリケーションだけでなく、Kubernetes Secretとしてクラスタ内に保持したいケースもあるでしょう。

## 現在のKunbernetes Secretの情報一覧を確認
事前にNamespace「sample-app」に作成されているKubernetes Secret一覧を確認しておきましょう。開発者向け表示の「シークレット」メニューを選択します。ここには、OpenShiftの機能によってデフォルトで作成されたKubernetes Secretしかありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/baa9b278-607b-4c85-a9af-4cf4e0e7980b.png)

## カスタムリソースの編集
それでは、カスタムリソース「SecretProviderClass」を編集し、Vaultからシークレット情報がコンテナに反映されるタイミングでKubernetes Secretが作成されるようにします。OpenShiftのコンソール画面を管理者向け表示に切り替え、「管理」メニューから「CustomResourceDefinitions」を選択、検索欄に「SecretProviderClass」と入力し、「SecretProviderClass」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8042e6a5-88e0-459f-8077-504ef9982c9a.png)

次の画面で「インスタンス」タブに切り替え、先ほど作成した「example-vault-provider」をクリックし、「YAML」タブに切り替えてマニフェストを編集可能な状態にします。

以下の通り、`spec.secretObjects`を追記しましょう。

```diff_yaml
...
spec:
  parameters:
    objects: |
      - secretPath: "secret/data/example"
        objectName: "secret-info"
        secretKey: "testSecret1"
    roleName: csi
    vaultAddress: 'http://vault.vault.svc.cluster.local:8200'
  provider: vault
+ secretObjects:
+   - secretName: synced-secret # 作成するKubernetes Secretの名称
+     type: Opaque # Kubernetes Secretのタイプ。OpaqueはKV形式のそれ。
+     labels:
+       app: vault-csi-driver-app # 作成するSecretに付与したいラベル
+     data:
+       - objectName: secret-info # Vault内のシークレット情報から作成されるファイル名（コンテナ内に作成されるファイル名）
+         key: gakenoueno # Kubernetes Secretを作成する際に使うKey
```

これでOKです。「保存」をクリックしましょう。

## 再度Vault上のシークレット情報を編集
先ほどの要領と同じく、再度Vault上でシークレット情報を編集します。


今度は適当にkeyを「ponyo」としてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/bd703e51-3209-40d4-aa25-5126e9180486.png)

## Deploymentをロールアウト
先ほどと同じくDeploymentをロールアウトし、コンテナアプリケーションにシークレット情報の変更を再反映します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c081eb55-0603-492d-84cd-f045a248d98d.png)

ロールアウトが完了したら、再びアプリケーションを更新します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/281d5108-7cfa-4502-b00d-4dbd2e3a337d.png)

アプリケーションにシークレット情報の変更が再び反映されました。

## Namespace「sample-app」のKubernetes Secret一覧を再確認
では、再びNamespace「sample-app」のKubernetes Secret一覧を見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/61ef850a-2324-40cf-b6e7-94a8c0a63628.png)

お！先ほどカスタムリソース「SecretProviderClass」を編集して`spec.secretObjects.secretName`で設定したKubernetes Secret名「synced-secret」が作成されています。これをクリックして、中身を確認しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/49d4f8d0-cfec-49df-b348-9fc263e5f63d.png)

こちらもカスタムリソース「SecretProviderClass」を編集した通り、 
- key: gaenoueno
- value: ponyo
がデータに反映されました。

# おわりに
お疲れさまでした。OpenShift 4.18よりGAされた「Secrets Store CSI Driver」の仕組みを用いて、Vaultと連携したシークレット情報の管理を試してみました。Vault上でシークレット情報を適切に管理しつつ、カスタムリソースを利用してコンテナアプリケーションにシークレット情報を連携するという、一連の流れがわかっていただけたと思います。

Vault CSI Driver方式は、コンテナアプリケーション内に直接機密情報を安全に渡したい場合に特に有効です。オプションとしてKubernetes Secretを作成することも可能ですが、クラスタ内に機密情報を保存することなく、Vaultなどの外部のシークレットストアからPod／コンテナに直接シークレットを渡せる点が、特定のユースケースで大きなメリットになります。

例えば、データベースへのアクセス情報や、各種外部APIのキー・トークンなど、アプリケーションの動作に必要なパラメータを安全にコンテナ内へ連携しながら、しかしそうした情報をクラスタ自体には保存しない運用が可能となります。

## Vault以外の外部シークレットストアとの連携方法
本記事では説明しきれないので、[OpenShiftのドキュメント](https://docs.redhat.com/ja/documentation/openshift_container_platform/4.18/html/nodes/nodes-pods-secrets-store#mounting-secrets-external-secrets-store)を確認いただくことをお勧めします。

おわり