IK.AM

@making's tech note


Tanzu Application PlatformのWorkloadからHashiCorp Vault上のSecretにアクセスする

🗃 {Dev/CaaS/Kubernetes/TAP}
🏷 Kubernetes 🏷 Cartographer 🏷 Tanzu 🏷 TAP 🏷 Vault 
🗓 Updated at 2023-06-15T05:45:08Z  🗓 Created at 2023-01-11T11:26:09Z   🌎 English Page

⚠️ 本記事の内容はVMwareによってサポートされていません。 記事の内容で生じた問題については自己責任で対応し、 VMwareサポート窓口には問い合わせないでください

Tanzu Application PlatformではWorkloadにはK8sのSecretをService Binding経由で設定して機密情報を設定するのが一般的ですが、K8sのSecretに機密情報を置きたくないという場合に HashiCorp Vaultを使いたいですね。

K8sからVaultにアクセスするには

がありますが、

  • TAPのWorkloadにCSI Volumeの設定をするのがhackなしでは難しい
  • External SecretsはVaultのSecretをK8sのSecretにsyncするので、K8sのSecretに機密情報を置きたくないというニーズを満たなさない

ということで、Vault Agent Injectorを使います。

Tanzu Application Platform 1.4でExternal Secretが実験的にサポートされています。
https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/external-secrets-about-external-secrets-operator.html

今回はこちらの記事で作成したKind上のTAPにWorkloadをデプロイして、Vault上に設定したSecretを参照させます。

TAPだとMulti Cluster構成が一般的で、VaultはWorkloadが載るRun Clusterとは別の場所で動く可能性が高いので、今回は既存のVaultに対してWorkloadからアクセスします。以下のチュートリアルを参考にしました。

https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-external-vault

目次

Vaultの起動

K8sクラスタ外でVaultを起動します。今回はdevモードで起動します。

vault server -dev -dev-root-token-id root -dev-listen-address 0.0.0.0:8200

Vault Agent Injectorのインストール

TAPをインストールしたクラスタ (Multi Cluster構成の場合はRun Cluster) にVault Agent Injectorをインストールします。

Helm Chartを使用します。

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

helm templateでマニフェストを生成します。injector.externalVaultAddrにK8sクラスタから見たVaultのURLを指定します。

helm template vault hashicorp/vault -n vault --set "injector.externalVaultAddr=http://host.docker.internal:8200" > vault-agent-injector.yaml
kubectl create ns vault
kubectl apply -f vault-agent-injector.yaml -n vault

Podを確認。

$ kubectl get pod -n vault
NAME                                    READY   STATUS    RESTARTS   AGE
vault-agent-injector-547d8fc8db-kxtd7   1/1     Running   0          14s

Kubernetes Auth Methodの有効化

Kubernetes Auth Methodの有効化します。

vault auth enable kubernetes

Service Accountに対するトークンを作成します。

kubectl apply -n vault -f -<<EOF
apiVersion: v1
kind: Secret
metadata:
  name: vault-token
  namespace: vault
  annotations:
    kubernetes.io/service-account.name: vault
type: kubernetes.io/service-account-token
EOF

VaultにアクセスするK8sの情報を設定します。

TOKEN_REVIEW_JWT=$(kubectl get secret -n vault vault-token -otemplate='{{index .data "token" | base64decode}}')
KUBE_CA_CERT=$(kubectl get secret -n vault vault-token -otemplate='{{index .data "ca.crt"| base64decode}}')
KUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')

vault write auth/kubernetes/config \
     token_reviewer_jwt="$TOKEN_REVIEW_JWT" \
     kubernetes_host="$KUBE_HOST" \
     kubernetes_ca_cert="$KUBE_CA_CERT" \
     issuer="https://kubernetes.default.svc.cluster.local"

アプリケーション用のSecretを登録

アプリケーションには https://github.com/making/vehicle-api を使用します。このアプリはPostgreSQLにアクセスするシンプルなSpring Bootのアプリで、Vaultとは完全に独立しています。
アクセスするPostgreSQLには ElephantSQL のfree planを使用します。

ElephantSQLでインスタンスを作成し、インスタンスの情報をvehicle-api用のSecretとして登録します。

vault kv put secret/vehicle-api/config \
  host='floppy.db.elephantsql.com' \
  username='ixyepwbw' \
  password='QIDpi7cBKioNGyp7JeN8ZMTL-rIWL_9B' \
  database='ixyepwbw'

登録された情報を確認します。

$ vault read secret/data/vehicle-api/config
Key         Value
---         -----
data        map[database:ixyepwbw host:floppy.db.elephantsql.com password:QIDpi7cBKioNGyp7JeN8ZMTL-rIWL_9B username:ixyepwbw]
metadata    map[created_time:2023-01-11T08:46:51.471224Z custom_metadata:<nil> deletion_time: destroyed:false version:1]

このSecretに対する読み取り専用のPolicyを作成します。

vault policy write vehicle-api - <<EOF
path "secret/data/vehicle-api/config" {
  capabilities = ["read"]
}
EOF

Workloadのデプロイ (application.propertiesの追加)

Workloadをデプロイする、demo namespaceのdefault Service Accountに対するRoleを作成し、先に作ったPolicyをアタッチします。

vault write auth/kubernetes/role/vehicle-api \
     bound_service_account_names=default \
     bound_service_account_namespaces=demo \
     policies=vehicle-api \
     ttl=24h

このSecretにアクセスするためのannotationsをWorkloadに設定します。

Secretの内容を/vault/secrets/application.propertiesに書き込み、アプリからこのファイルを読み込むように環境変数SPRING_CONFIG_IMPORTを設定します。

cat <<EOF > workload.yaml
apiVersion: carto.run/v1alpha1
kind: Workload
metadata:
  labels:
    app.kubernetes.io/part-of: vehicle-api
    apps.tanzu.vmware.com/workload-type: web
  name: vehicle-api
spec:
  params:
  - name: annotations
    value:
      autoscaling.knative.dev/minScale: '1'
      vault.hashicorp.com/agent-inject: 'true'
      vault.hashicorp.com/role: vehicle-api
      vault.hashicorp.com/agent-inject-secret-application.properties: secret/data/vehicle-api/config
      vault.hashicorp.com/agent-inject-template-application.properties: |
        {{- with secret "secret/data/vehicle-api/config" -}}
        spring.datasource.url=jdbc:postgresql://{{ .Data.data.host }}/{{ .Data.data.database }}
        spring.datasource.username={{ .Data.data.username }}
        spring.datasource.password={{ .Data.data.password }}
        {{- end -}}
  env:
  - name: SPRING_CONFIG_IMPORT
    value: /vault/secrets/application.properties
  build:
    env:
    - name: BP_JVM_VERSION
      value: "17"
  source:
    git:
      url: https://github.com/making/vehicle-api
      ref:
        branch: main
EOF

tanzu apps workload apply -f workload.yaml -n demo
# or kubectl apply -f workload.yaml -n demo

vault.hashicorp.com/agent-inject-template-...tanzu CLIの--annotationで設定できませんでした。
CLIだけでWorkloadを作成する場合は次のようなパラメータの指定が必要です。

tanzu apps workload apply vehicle-api2 \
  -n demo\
  --git-repo https://github.com/making/vehicle-api \
  --git-branch main \
  --type web \
  --app vehicle-api \
  --env SPRING_CONFIG_IMPORT=/vault/secrets/application.properties \
  --build-env BP_JVM_VERSION=17 \
  --param-yaml annotation='{"autoscaling.knative.dev/minScale":"1","vault.hashicorp.com/agent-inject":"true","vault.hashicorp.com/agent-inject-secret-application.properties":"secret/data/vehicle-api/config","vault.hashicorp.com/agent-inject-template-application.properties":"{{- with secret \"secret/data/vehicle-api/config\" -}}\nspring.datasource.url=jdbc:postgresql://{{ .Data.data.host }}/{{ .Data.data.database }}\nspring.datasource.username={{ .Data.data.username }}\nspring.datasource.password={{ .Data.data.password }}\n{{- end -}}","vault.hashicorp.com/role":"vehicle-api"}'

アプリのデプロイが完了して、Workloadを確認します。Podにvault agentのサイドカーが含まれるのでコンテナ数が3/3になります。

 $ tanzu apps workload get -n demo vehicle-api
📡 Overview
   name:   vehicle-api
   type:   web

💾 Source
   type:     git
   url:      https://github.com/making/vehicle-api
   branch:   main

📦 Supply Chain
   name:   source-to-url

   RESOURCE           READY   HEALTHY   TIME   OUTPUT
   source-provider    True    True      134m   GitRepository/vehicle-api
   image-provider     True    True      131m   Image/vehicle-api
   config-provider    True    True      131m   PodIntent/vehicle-api
   app-config         True    True      131m   ConfigMap/vehicle-api
   service-bindings   True    True      131m   ConfigMap/vehicle-api-with-claims
   api-descriptors    True    True      131m   ConfigMap/vehicle-api-with-api-descriptors
   config-writer      True    True      131m   Runnable/vehicle-api-config-writer

🚚 Delivery
   name:   delivery-basic

   RESOURCE          READY   HEALTHY   TIME   OUTPUT
   source-provider   True    True      130m   ImageRepository/vehicle-api-delivery
   deployer          True    True      130m   App/vehicle-api

💬 Messages
   No messages found.

🛶 Pods
   NAME                                            READY   STATUS      RESTARTS   AGE
   vehicle-api-00001-deployment-7b6d9f7f49-rl25z   3/3     Running     0          30m
   vehicle-api-build-1-build-pod                   0/1     Completed   0          34m
   vehicle-api-config-writer-trr8r-pod             0/1     Completed   0          31m

🚢 Knative Services
   NAME          READY   URL
   vehicle-api   Ready   https://vehicle-api-demo.127-0-0-1.sslip.io

To see logs: "tanzu apps workload tail vehicle-api --namespace demo"

アプリにアクセスして、データベースからデータが取れることを確認します。

$ curl -sk https://vehicle-api-demo.127-0-0-1.sslip.io/vehicles
[{"id":1,"name":"Avalon"},{"id":2,"name":"Corolla"},{"id":3,"name":"Crown"},{"id":4,"name":"Levin"},{"id":5,"name":"Yaris"},{"id":6,"name":"Vios"},{"id":7,"name":"Glanza"},{"id":8,"name":"Aygo"}]

Vaultから取得したSecretがファイルに書き込まれていることを確認します。

POD_NAME=$(kubectl get pod -n demo -l serving.knative.dev/service=vehicle-api -ojsonpath='{.items[0].metadata.name}')
$ kubectl exec -it ${POD_NAME} -n demo -c workload -- ls -la /vault/secrets/
total 8
drwxrwxrwt 2 root root   60 Jan 11 09:14 .
drwxr-xr-x 3 root root 4096 Jan 11 09:14 ..
-rw-r--r-- 1 _apt cnb   170 Jan 11 09:14 application.properties
$ kubectl exec -it ${POD_NAME} -n demo -c workload -- cat /vault/secrets/application.properties
spring.datasource.url=jdbc:postgresql://floppy.db.elephantsql.com/ixyepwbw
spring.datasource.username=ixyepwbw
spring.datasource.password=QIDpi7cBKioNGyp7JeN8ZMTL-rIWL_9

Actuatorのenvエンドポイントにアクセスして、実際にこのファイルから設定が読み込まれていることを確認します。

kubectl port-forward ${POD_NAME} -n demo 8081:8081
$ curl -s localhost:8081/actuator/env | jq .
{
  "activeProfiles": [],
  "propertySources": [
    {
      "name": "server.ports",
      "properties": {
        ...
      }
    },
    {
      "name": "servletContextInitParams",
      "properties": {}
    },
    {
      "name": "systemProperties",
      "properties": {
        ...
      }
    },
    {
      "name": "systemEnvironment",
      "properties": {
        ...
      }
    },
    {
      "name": "Config resource 'file [/vault/secrets/application.properties]' via location '/vault/secrets/application.properties'",
      "properties": {
        "spring.datasource.url": {
          "value": "jdbc:postgresql://floppy.db.elephantsql.com/ixyepwbw",
          "origin": "URL [file:/vault/secrets/application.properties] - 1:23"
        },
        "spring.datasource.username": {
          "value": "ixyepwbw",
          "origin": "URL [file:/vault/secrets/application.properties] - 2:28"
        },
        "spring.datasource.password": {
          "value": "******",
          "origin": "URL [file:/vault/secrets/application.properties] - 3:28"
        }
      }
    },
    {
      "name": "Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'",
      "properties": {
        ...
      }
    }
  ]
}

TAPからVaultにできました。K8sのSecretを使うことなく、TAPから機密情報を取得できることが確認できました。


✒️️ Edit  ⏰ History  🗑 Delete