APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Vault Secrets Operator と HCP Vault で Kubernetes のシークレットを管理しよう

はじめまして、ACS 事業部の埜下です。

みなさんは Kubernetes のシークレットはどのように管理されていますか?

先日、HashiCorp 社から「Vault Secrets Operator」がプレビュー公開されました。 また、2023/2 には HCP Vault on Azure が GA しました。

そこで、今回はシークレット管理についてお伝えしつつ、Vault Secrets Operator と HCP Vault on Azure を組み合わせたシークレット管理の動作確認の内容についてもお伝えします。

はじめに

シークレット管理の必要性

「シークレット管理をする必要があるの?」と思われる方もいらっしゃるかもしれません。 そこで、はじめにシークレット管理の必要性について説明します。

コンテナで動くアプリケーションは他のサービス/システムと連携するために「ユーザ名/パスワード」や「トークン」といったシークレットを使います。 Kubernetes では Secretというリソースでシークレットを表現しています。

たとえば、データベースに接続するためのユーザ名とパスワードを持たせた Secret リソースは以下のように表現できます。

apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: "2023-04-12T04:52:50Z"
  name: db-secret
  namespace: default
type: Opaque
data:
  username: aG9nZQ==
  password: ZnVnYQ==

こちらには username: hogepassword: fuga という認証情報を設定しています。

このマニフェストで passwordusername は一見暗号化されているように見えますが、これらは base64 エンコードされただけの文字列なので簡単に値を参照できてしまいます。

$ kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d
fuga

そのため、他のリソースのマニフェストと同じように Git などで Secret のマニフェストをバージョン管理してしまうと情報漏洩に繋がる恐れがあります。 とは言え、Secret を手動で作成することは運用的によろしくないことは想像に難くないです。

そこで、安全に Secret リソースを Kubernetes 上にデプロイするために「シークレット管理」が必要になってきます。

Vault Secrets Operator について

それでは、どのような「シークレット管理」の方法があるでしょうか。 Kubernetes では以下のようなシークレット管理のエコシステムが公開されています。

それぞれの仕組みについては本記事では割愛させていただきます。

また、 HashiCorp 社からは「HashiCorp Vault (以下、Vault)」 と Kubernetes を統合する「Vault Sidecar Agent Injector」や「Vault Container Storage Interface (CSI) provider」という方法が公開されています。

Vault Sidecar Agent Injector は、サイドカーパターンを利用して Vault と連携する「Vault Agent コンテナ」を Pod 内にデプロイします。 Pod 内のアプリケーションコンテナは Vault Agent コンテナが Vault から取得したシークレットを共有ストレージ経由で参照できます。

Vault CSI provider は Secrets Store CSI Driver という機能を使って、Vault から取得したシークレットをボリュームとして Pod に見せることができます。

上記に加えて、先日新たに「Vault Secrets Operator」というシークレット管理が公開されました。

www.hashicorp.com

既存の方法と同じく、Vault Secrets Operator は Vault に格納されたシークレットを Kubernetes へ同期する機能をもっています。

それでは、HashiCorp から公開されている 3 種類の方法にはどのような違いがあるのでしょうか。

ちょうどよいタイミングで HashiCorp 公式ブログに比較記事が投稿されましたので詳細はそちらにお任せして、本記事では 1 点だけ Vault Secrets Operator が他と異なる点をお伝えします。

www.hashicorp.com

それは、「Deployment リソースなどをローリングアップデートしてシークレットを反映させることができる」という点です。 これについては動作確認の中で説明します。

また、Vault Secrets Operator では 3 種類のシークレットを扱うことができます。

  • 静的シークレット (Static Secret)
  • 動的シークレット (Dynamic Secret)
  • PKI シークレット

今回は「静的シークレット」を対象に動作確認しています。

HCP Vault on Azure について

つぎに、「HCP Vault on Azure」について説明します。

HCP Vault は HashiCorp Cloud Platform (HCP) 上で提供されるフルマネージドな Vault のサービスです。 元々、HCP Vault は AWS 上にデプロイする方法しかなく、他のクラウドとはセキュアに連携することができませんでした。

HashiCorp 社は HCP をマルチクラウドに対応させることが目標ということらしく、2023/2 に HCP Vault が Azure に対応しました。

www.hashicorp.com

具体的には、HCP では HashiCorp Virtual Network (HVN) というものを使ってネットワークを構築し、そのネットワーク上に Vault などのプロダクトをデプロイします。

この HVN が Azure にも対応した、ということで「HCP Vault on Azure 」という表現がされています。

今回の目的

以上のように、Vault Secrets Opertor という新しいシークレット管理の方法が公開され、HCP Vault も Azure とセキュアに接続されるようになりました。

そこで、本記事では「AKS にインストールした Vault Secrets Operator から Azure のネットワークを介して HCP Vault に接続してシークレットを同期できるか確認する」と、「読者が同じような環境を構築できるアウトプットにする」ことを目的として検証をおこないます。

そのため作業の説明が多いところがございますが、ご了承ください。

また、ぜひ実際に構築してみていただき、手順どおりにいかなった等がございましたらご連絡いただけますと幸いです。

前提条件

今回ご紹介する手順では以下のコマンドを使用しています。 手順を実行される場合は、これらのコマンドが実行可能な環境をご準備ください。

  • Azure CLI
  • Terraform
  • kubectl
  • helm

環境構築

それでは、動作確認のための環境を構築していきます。

構築の流れは以下のようになります。

  1. HashiCorp Virtual Network 作成
  2. HCP Vault クラスタ作成
  3. AKS クラスタ作成
  4. Vault Secrets Operator インストール
  5. HCP Vault の認証設定

HashiCorp Virtual Network 作成

HCP Vault クラスタを作る前に、HashiCorp Cloud Platform (HCP) 上のサービスが使用する「HashiCorp Virtual Network (HVN)」という仮想ネットワークを作成します。

まずは HashiCorp Cloud Platform にログインします。 もし初めてログインする場合は、アカウントを作成したうえで Organization も作成してください*1

Organization が表示されたら、左メニューの [HashiCorp Virtual Network] から [Create network] を選択します。

HVN 作成画面では以下のように設定しました。

項目 設定
Network name demo-hvn
Provider Microsoft Azure
Region selection Japan East (japaneast)
CIDR block 172.25.16.0/20

HVP Vault は Azure 東日本リージョンにも対応しているようですね。

5 分ほど待つと作成されます。

「This network is not fully configured. Create a peering connection to connect to your Azure infrastructure.」というメッセージの通り、HVN を使うためには Azure VNet とピアリングが必要です。

作成した HVN を選択すると左メニューに [Peering connections] が表示されるので、こちらからピアリングを設定していきます。 各項目にはピアリング先の Azure の情報を入力します。

項目 設定
Connection ID demo-hvn
Azure tenant ID Azure AD のテナント ID
Azure subscription ID Azure サブスクリプション ID
Resource group name ピアリングする VNet のリソースグループ
VNet name ピアリングする VNet 名

もし各項目の参照元が分からなければ「Where can i find this?」から Azure Portal のどこを見ればよいか分かるようになってます、とても親切ですね。

ピアリングを設定するとピアリング詳細ページを見れるようになりますが、設定直後は「Peering connection is pending」と「Create a route」という 2 つの警告メッセージが表示されます。

>

これは、Azure 側のピアリング設定ができていないことと、HVN のルートテーブルに Azure 向きのルートを作成していないためです。

それでは、それぞれの設定をしていきましょう。

Azure VNet ピアリング

Azure の VNet から HVN にピアリングを設定する方法は 2 種類あり、それぞれ実行するスクリプトは HCP ウェブコンソール上に表示されます。

  • Terraform
  • Azure Cloud Shell

そのはずですが、なぜか Azure Cloud Shell のほうは画面に表示されませんでした……。

そのため、今回は Terraform を使って Azure VNet にピアリングを設定します。

Terraform の場合は以下のような内容が HCP ウェブコンソールに表示されます。 内容を見てみると、サービスプリンシパルとピアリングに関するロールを作成することが分かります。 Terraform で直接ピアリングの設定をするわけではないようですね。

locals {
  application_id = "00000000-0000-0000-0000-000000000000"
  role_def_name  = join("-", ["hcp-hvn-peering-access", local.application_id])
  vnet_id        = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/hcp-vault-azure-demo/providers/Microsoft.Network/virtualNetworks/vnet-hcp-vault-azure-demo"
}

resource "azuread_service_principal" "principal" {
  application_id = local.application_id
}

resource "azurerm_role_definition" "definition" {
  name  = local.role_def_name
  scope = local.vnet_id

  assignable_scopes = [
    local.vnet_id
  ]

  permissions {
    actions = [
      "Microsoft.Network/virtualNetworks/peer/action",
      "Microsoft.Network/virtualNetworks/virtualNetworkPeerings/read",
      "Microsoft.Network/virtualNetworks/virtualNetworkPeerings/write"
    ]
  }
}

resource "azurerm_role_assignment" "role_assignment" {
  principal_id       = azuread_service_principal.principal.id
  role_definition_id = azurerm_role_definition.definition.role_definition_resource_id
  scope              = local.vnet_id
}

こちらを terraform コマンドを実行可能な環境に保存して実行します。 しばらくすると HVN と Azure VNet のピアリングが完了します(私の環境では 15 分ほど掛かりました)。

HVN ルートテーブル

続きまして、HVN のルートテーブルを設定します。

項目 設定
Route ID demo-route
Destinations 172.16.2.0/24

Destinations には HVN の CIDR と重ならないネットワーク範囲を指定します。

こちらはすぐに反映されます。

NSG 作成

これで HVN の設定も完了かと思いきや、最後に NSG の設定があります。

Azure 側のピアリング設定と同様、設定用のコマンドが HCP ウェブコンソールに表示されるのでこちらを使用します。

注意点としては、2023/4/12 時点でこちらの NSG 設定用コマンドに不備があるようで、Outbound の 401 というプライオリティが重複しています。 そのため、一部修正してから実行するようにしてください。

また、こちらのコマンドは対象の NSG 自体は作成しないため、あらかじめ作成しておいてください。

az コマンドを実行可能な環境でスクリプトを実行します。 もしくはスクリプトの内容を Terraform に落とし込んで実行してもよいかもしれません。

HVN_CIDR="172.25.16.0/20"
RESOURCE_GROUP_NAME=hcp-vault-azure-demo
SECURITY_GROUP_ID=nsg-hcp-vault-azure-demo

# Create inbound rules
az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name ConsulServerInbound \
    --priority 400 \
    --source-address-prefixes "$HVN_CIDR" \
    --destination-address-prefixes VirtualNetwork \
    --destination-port-ranges 8301 \
    --direction Inbound \
    --access Allow

az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name ConsulClientInbound \
    --priority 401 \
    --source-address-prefixes VirtualNetwork \
    --destination-address-prefixes VirtualNetwork \
    --destination-port-ranges 8301 \
    --direction Inbound \
    --access Allow

# Create outbound rules
az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name ConsulServerOutbound \
    --priority 400 \
    --source-address-prefixes VirtualNetwork \
    --destination-address-prefixes "$HVN_CIDR" \
    --destination-port-ranges "8300-8301" \
    --direction Outbound \
    --access Allow

az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name ConsulClientOutbound \
    --priority 401 \
    --source-address-prefixes VirtualNetwork \
    --destination-address-prefixes VirtualNetwork \
    --destination-port-ranges 8301 \
    --direction Outbound \
    --access Allow

az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name HTTPOutbound \
    --priority 402 \
    --source-address-prefixes VirtualNetwork \
    --destination-address-prefixes "$HVN_CIDR" \
    --destination-port-ranges 80 \
    --direction Outbound \
    --access Allow

az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name HTTPSOutbound \
    --priority 403 \
    --source-address-prefixes VirtualNetwork \
    --destination-address-prefixes "$HVN_CIDR" \
    --destination-port-ranges 443 \
    --direction Outbound \
    --access Allow
  
az network nsg rule create \
    --resource-group "$RESOURCE_GROUP_NAME" \
    --nsg-name "$SECURITY_GROUP_ID" \
    --name ConsulServerOutboundGRPC \
    --priority 401 \                                 <- プライオリティが重複しているため 404 に修正
    --source-address-prefixes VirtualNetwork \
    --destination-address-prefixes "$HVN_CIDR" \
    --destination-port-ranges 8502 \
    --direction Outbound \
    --access Allow

Azure Portal で NSG のルールが作成されていることを確認します。

以上で、HashiCorp Virtual Network (HVN) と Azure VNet のピアリングが完了しました。

それではつづいて Vault クラスタの作成です。

HCP Vault クラスタ作成

HCP Vault クラスタも HCP ウェブコンソールから作成します。 今回はスクラッチで作成するため、[Start from scratch] の [Create cluster] を選択します。

HCP Vault クラスタ作成画面では以下のように設定します。

項目 設定
Provider Microsoft Azure
Vault tier Development
Cluster size Extra Small
Network 前章で作成した HVN
Cluster accesibility Public
Cluster ID demo-cluster
Templates Start from scratch

「Vault tier」では運用レベルに合わせて 3 種類の Tier を選ぶことができます。

  • Development
  • Starter
  • Standard

それぞれクライアント数の制限やバックアップの有無が異なります。

また、Vault tier 毎に選択できる「Cluster size」も異なります。 今回の Development Tier では 2 vCPU/1 GiB RAM の「Extra Small」のみ選択でき、料金は $0.045/hr 掛かります。

ちなみに、HCP はアカウント作成時に $50 のクレジットが使える状態になっています。 Extra Small の HCP Vault クラスタのみの使用で計算すると約 1.5 ヶ月分のクレジットになります。 太っ腹ですね!

「Cluster accesibility」では HCP Vault クラスタへのアクセス制限を設定できます。 Private を選択した場合はピアリングした Azure VNet 経由でしか接続できません。 この項目は後ほど変更可能ということですので、今回は Public で構築します。

HCP Vault クラスタが作成できたら以下のような概要ページを見ることができます。 こちらには HCP Vault クラスタの構成情報やアクセス方法が載っていたり、admin 用トークンを生成できたりします。

以上で HCP Vault クラスタの構築が完了です。

AKS クラスタ作成

次に、Azure 上に AKS クラスタを構築していきます。 今回は検証中にクラスタの「作って壊して」が多かったので Terraform で構築しました。

使用した Terraform ファイルはデモ用リポジトリを参照ください。

本来はプライベート AKS クラスタで動作確認したかったのですが、HCP Vault から Kubernetes 認証(後述)することができなかったため、通常の AKS クラスタで確認しています。

構築後は kubeconfig を設定しておいてください。 Terraform を使って構築した場合は terraform output -json | jq -r .kube_config.value で kubeconfig を参照できます。

$ terraform output -json | jq -r .kube_config.value > ~/.kube/config_aks
$ export KUBECONFIG=~/.kube/config_aks
$ kubectl config current-context
aks-hcp-vault-azure-demo

Vault Secrets Operator インストール

AKS クラスタの構築が完了したら、Vault Secrets Operator をインストールします。

Vault Secrets Operator は Helm に対応しており、今回は 0.1.0-beta というバージョンをインストールしました。

$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm search repo hashicorp/vault-secrets-operator --devel
$ helm install --create-namespace --namespace vault-secrets-operator \
    vault-secrets-operator hashicorp/vault-secrets-operator --version 0.1.0-beta

無事に Pod が起動しているか確認します。

$ kubectl get pods -n vault-secrets-operator
NAME                                                       READY   STATUS    RESTARTS   AGE
vault-secrets-operator-controller-manager-d9949dd5-898lm   2/2     Running   0          152m

Vault Secrets Operator をインストールすると、以下の Custom Resource Definition (CRD) が Kubernetes にインストールされます。

CRD 内容
VaultConnection Operator が接続する Vault の構成情報
VaultAuth 使用する VaultConnection の指定や Vault に認証するための情報
VaultDynamicSecrets 使用する VaultAuth の指定や同期する動的シークレット情報
VaultPKISecrets 使用する VaultAuth の指定や同期する PKI シークレット情報
VaultStaticSecrets 使用する VaultAuth の指定や同期する静的シークレット情報

今回は静的シークレットのみ対象とするため、作成するカスタムリソースは VaultConnectionVaultAuth、そして VaultStaticSecrets となります。 これらのカスタムリソースについての説明は後述します。

つぎに、HCP Vault から Kubernetes 認証で使用するサービスアカウントトークンを作成します。 Kubernetes バージョン 1.21 以降では、BoundServiceAccountTokenVolume という機能が有効になっており、サービスアカウントトークンの有効期限が 1 時間になっています。 そのため、有効期限がないサービスアカウントトークンを別途作成して、後ほど使用します。

サービスアカウントトークンは Secret リソースに kubernetes.io/service-account.name アノテーションを付与して、対象のサービスアカウントを指定することで作成できます。

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: vault-secrets-operator-token
  namespace: vault-secrets-operator
  annotations:
    kubernetes.io/service-account.name: "vault-secrets-operator-controller-manager"
type: kubernetes.io/service-account-token
EOF

HCP Vault の設定

Vault Secrets Operator をインストールしてもすぐに使えるわけではありません。 HCP Vault 側で「シークレット作成」と「ポリシー作成」および「認証設定」が必要です。

vault コマンドを使って設定することも可能ですが、今回は HCP Vault の Web UI から設定していきます。

まず、Vault クラスタ概要ページの [New admin token] の Generate token を選択して、admin 用のトークンを生成します。 そして、[Access web UI - Public] のリンクから Web UI にアクセスし、さきほどのトークンを使ってログインします。

シークレット作成

シークレットを格納するための KV シークレットエンジンを作成します。 KV シークレットエンジンとは、Vault の物理ストレージ内にシークレットを格納するための Key-Value ストアです。

上部メニューの [Secrets] から [Enable new engine] を選択します。

シークレットエンジン一覧から [KV] を選択します。

このページは何も変更しません。

これでシークレットエンジンは作成できました。 この流れで [Create secret] から Kubernetes に同期するシークレットを作成します。

[Path for this secret] には demo/config、[Secret data] には以下のデータを設定します。

Key Value
username hoge
password fuga

作成後もシークレットの値を参照できます。

後ほど、このシークレットが Kubernetes に同期されることを確認します。

ポリシー

次に、上部メニューの [Policies] を選択し、[Create ACL policy] から先ほど作成したシークレットを参照できる権限を持つポリシーを作成します。

名前は demo-policy とし、権限は以下のように設定します。

path "secret/*" {
  capabilities = ["read"]
}

Vault Secrets Operator は HCP Vault のシークレットを参照して Kubernetes に反映させるだけなので、シークレットの参照権限 (read) のみで構いません。

こちらは作成後のポリシーです。

このポリシーは次の Kubernetes 認証で使用します。

認証設定

Vault は認証に使用するバックエンドとして、さまざまな認証方法を有しています。 今回は Vault Secrets Operator から使用する認証方法として「Kubernetes 認証」を設定します。

プレビュー段階の Vault Secrets Operator は Kubernetes 認証のみ対応していますが、今後その他の認証方法もサポートされる予定です。

上部メニューの [Access] を選択し、[Enable new method] から新しい認証方法を作成していきます。

多くの認証方法が表示されますが、今回は [Kubernetes] を選択します。

[Path] は kubernetes のままで [Enable Method] を選択して Kubernetes 認証を有効化します。

[Configure Kubernetes] ページでは、さきほど作成した AKS クラスタの情報を入力していきます。

[Kubernetes host] は Kubernetes API サーバの URL を入力します。 URL の確認方法はいろいろありますが、Terraform で AKS クラスタを構築している場合は以下のコマンドで URL を取得できます。

terraform output -json | jq -r .kube_config.value | yq .clusters[0].cluster.server

[Kubernetes CA Certificate] と [Token Reviewer JWT] は Kubernetes 認証時に使用する情報です。 前章で作成したサービスアカウントトークンを参照してそれぞれ設定します。

$ kubectl get secret vault-secrets-operator-token -n vault-secrets-operator -o jsonpath='{.data.ca\.crt}' | base64 -d
$ kubectl get secret vault-secrets-operator-token -n vault-secrets-operator -o jsonpath='{.data.token}' | base64 -d

ロール

Kubernetes 認証の設定ができたら、Kubernetes 上のアプリケーションが使用するロールを作成します。 このロールに先ほど作成したポリシーを紐づけることで、アプリケーションは Kubernetes の TokenReview API で認証されたのち、ポリシーでシークレットの参照を認可されます。

Kubernetes 認証ページの [Create role] からロール作成ページに移ります。

項目 設定
Name demo-role
Bound service account names default
Bound service account namespaces vault-demo
Generated Token's Plicies demo-policy

動作確認

HCP Vault の認証設定ができたので、いよいよ Vault Secrets Operator の動作確認です。

以下の観点で検証しました。

  • HCP Vault に設定したシークレットが Kubernetes 側に同期されるか
  • アプリケーションが更新後のシークレットを参照できるか

検証用マニフェスト

Terraform ファイルと同様、検証に使用したマニフェストはデモ用リポジトリに格納していますので、ご参照ください。

こちらのマニフェストには Vault Secrets Operator のカスタムリソースと同期する Secret、その Secret を使用する Deployment で構成されています。

では、マニフェストの中身について説明していきます。 カスタムリソースについては公式ドキュメントの API リファレンスもご参照ください。

VaultConnection

VaultConnection リソースには Vault Secrets Operator が接続する Vault クラスタの情報を記載します。

apiVersion: secrets.hashicorp.com/v1alpha1
kind: VaultConnection
metadata:
  name: vaultconnection-demo
  namespace: vault-demo
spec:
  address: https://vault-private-vault-9640d9a6.d9c88c70.z1.hashicorp.cloud:8200

今回は spec.address に HCP Vault のプライベートアクセス用 URL を設定しています。 プライベートアクセス用 URL は HCP ウェブコンソールのクラスタ概要ページから参照できます。

VaultConnection でプライベートアクセス用 URL を指定することで、AKS から HCP Vault には Azure のピアリングされた VNet を通して接続されます。 ここが HCP Vault on Azure の醍醐味になります。

VaultAuth

VaultAuth リソースには前述の VaultConnection リソースや Vault クラスタへアクセスする際の認証方法を記載します。

apiVersion: secrets.hashicorp.com/v1alpha1
kind: VaultAuth
metadata:
  name: vaultauth-demo
  namespace: vault-demo
spec:
  vaultConnectionRef: vaultconnection-demo
  namespace: admin
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: demo-role
    serviceAccount: default

HCP Vault は Enterprise 版の Vault が使用されるため、Namespace 機能が有効になっています。 そのため、spec.namespace で admin を指定しています。

spec.kubernetes.role には Vault クラスタに作成したロールを指定します。 ロールという概念は Kubernetes にも存在するため、注意が必要です。

また、spec.kubernetes.serviceAccount にはアプリケーションが利用する Kubernetes 上のサービスアカウント名を指定します。 Vault への認証には指定したサービスアカウントの「サービスアカウントトークン」が使用されます。

VaultStaticSecret

VaultStaticSecret リソースには前述の VaultAuth や同期するシークレットの情報を記載します。

apiVersion: secrets.hashicorp.com/v1alpha1
kind: VaultStaticSecret
metadata:
  name: vaultstaticsecret-demo
  namespace: vault-demo
spec:
  vaultAuthRef: vaultauth-demo
  namespace: admin
  mount: secret
  name: demo/config
  type: kv-v2
  refreshAfter: 5s
  rolloutRestartTargets:
    - kind: Deployment
      name: vault-demo-app
  destination:
    name: secret-demo

spec.mount にはシークレットエンジン名、spec.name には Vault のシークレット名を指定します。

spec.destination.name には Kubernetes 上の同期対象とする Secret リソース名を指定します。

spec.rolloutRestartTargets では Vault シークレットの更新を検知した際にロールアウトするリソースの種類と名前を指定します。 サポートされているには Deployment/DaemonSet/StatefulSet の 3 種類です。 ここで指定されたリソースがロールアウトされることで、アプリケーションが参照するシークレットの情報も更新されるようになっています。

Secret

こちらは普通の Secret リソースですが、シークレット情報を含んでいません。 空っぽのリソースだけ作成しておいて、シークレット情報は Vault Secrets Operator に設定してもらいます。

Secret リソースが存在しない場合でも Vault Secrets Operator で新規作成するように VaultStaticSecret で指定できますが、今回は事前に作成しておきます。

apiVersion: v1
kind: Secret
metadata:
  name: secret-demo
  namespace: vault-demo
type: Opaque

Deployment

こちらもいたって普通の Deployment リソースです。 先ほど作成した Secret を /etc/secrets にマウントしています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vault-demo-app
  namespace: vault-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vault-demo-app
  template:
    metadata:
      labels:
        app: vault-demo-app
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - name: secrets
              mountPath: "/etc/secrets"
              readOnly: true
      volumes:
        - name: secrets
          secret:
            secretName: secret-demo
            optional: false

HCP Vault に設定したシークレットが Kubernetes 側に同期されるか

検証用マニフェストを AKS にデプロイします。

kubectl create namespace vault-demo
kubectl apply -f vaultstaticsecret-demo.yaml

すると、Vault Secrets Operator がすぐに HCP Vault からシークレットを同期してくれました。 Secret のマニフェストでは data を記載していませんでしたが、デプロイ後の状態をみると HCP Vault のシークレットが入っていました。

# Secret リソースの確認
$ kubectl get secret secret-demo -n vault-demo -o yaml
apiVersion: v1
data:
  _raw: eyJkYXRhIjp7InBhc3N3b3JkIjoiZnVnYSIsInVzZXJuYW1lIjoiaG9nZSJ9LCJtZXRhZGF0YSI6eyJjcmVhdGVkX3RpbWUiOiIyMDIzLTA0LTEzVDE5OjQ0OjQ1LjI4NzIyOTk1NFoiLCJjdXN0b21fbWV0YWRhdGEiOm51bGwsImRlbGV0aW9uX3RpbWUiOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsImRlc3Ryb3llZCI6ZmFsc2UsInZlcnNpb24iOjV9fQ==
  password: ZnVnYQ==
  username: aG9nZQ==
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"secret-demo","namespace":"vault-demo"},"type":"Opaque"}
  creationTimestamp: "2023-04-13T19:45:16Z"
  name: secret-demo
  namespace: vault-demo
  resourceVersion: "58730"
  uid: bce83fcc-be62-4a01-aa5d-22a39a442c93
type: Opaque

# data._raw の値の確認
$ kubectl get secret secret-demo -n vault-demo -o jsonpath='{.data._raw}' | base64 -d | jq
{
  "data": {
    "password": "fuga",
    "username": "hoge"
  },
  "metadata": {
    "created_time": "2023-04-13T19:44:45.287229954Z",
    "custom_metadata": null,
    "deletion_time": "0001-01-01T00:00:00Z",
    "destroyed": false,
    "version": 1
  }
}

# data.username の値の確認
$ kubectl get secret secret-demo -n vault-demo -o jsonpath='{.data.username}' | base64 -d
hoge

# data.password の値の確認
$ kubectl get secret secret-demo -n vault-demo -o jsonpath='{.data.password}' | base64 -d
fuga

それではデモ用コンテナでシークレットを参照できるか確認してみます。

# デモ用コンテナの /etc/secrets を確認
$ kubectl exec -it $(kubectl get pods -n vault-demo -o jsonpath={.items[*].metadata.name}) -n vault-demo -- ls -l /etc/secrets
total 0
lrwxrwxrwx 1 root root 11 Apr 13 19:45 _raw -> ..data/_raw
lrwxrwxrwx 1 root root 15 Apr 13 19:45 password -> ..data/password
lrwxrwxrwx 1 root root 15 Apr 13 19:45 username -> ..data/username

# デモ用コンテナの /etc/secrets/username を確認
$ kubectl exec -it $(kubectl get pods -n vault-demo -o jsonpath={.items[*].metadata.name}) -n vault-demo -- cat /etc/secrets/username
hoge

# デモ用コンテナの /etc/secrets/password を確認
$ kubectl exec -it $(kubectl get pods -n vault-demo -o jsonpath={.items[*].metadata.name}) -n vault-demo -- cat /etc/secrets/password
fuga

アプリケーションコンテナからも HCP Vault に設定したシークレットを参照できていました。

このように、Kubernetes マニフェストにシークレットを記載することなく HCP Vault からシークレットが同期されることを確認できました。

アプリケーションが更新後のシークレットを参照できるか

つぎに、HCP Vault のシークレットを password: fuga から password: piyo に更新してみます。 あわせてデモ Pod の状況もチェックしておきます。

すると、Vault Secrets Operator が検知して Pod が削除され、新しい Pod が立ち上がってきました。

$ kubectl get pods -n vault-demo -w
NAME                              READY   STATUS    RESTARTS   AGE
vault-demo-app-57d48c77bd-7hd8b   1/1     Running   0          13m
vault-demo-app-77697c6bfc-75rp9   0/1     Pending   0          0s
vault-demo-app-77697c6bfc-75rp9   0/1     Pending   0          0s
vault-demo-app-77697c6bfc-75rp9   0/1     ContainerCreating   0          0s
vault-demo-app-77697c6bfc-75rp9   1/1     Running             0          2s
vault-demo-app-57d48c77bd-7hd8b   1/1     Terminating         0          14m
vault-demo-app-57d48c77bd-7hd8b   0/1     Terminating         0          14m
vault-demo-app-57d48c77bd-7hd8b   0/1     Terminating         0          14m
vault-demo-app-57d48c77bd-7hd8b   0/1     Terminating         0          14m

また、ReplicaSet を見てみるとローリングアップデートされたことが分かります。

$ kubectl get replicaset -n vault-demo
NAME                        DESIRED   CURRENT   READY   AGE
vault-demo-app-57d48c77bd   0         0         0       14m
vault-demo-app-77697c6bfc   1         1         1       19s

アプリケーションのコンテナからも password のみ値が更新されていることを確認できました。

# デモ用コンテナの /etc/secrets を確認
$ kubectl exec -it $(kubectl get pods -n vault-demo -o jsonpath={.items[*].metadata.name}) -n vault-demo -- ls -l /etc/secrets
total 0
lrwxrwxrwx 1 root root 11 Apr 13 19:59 _raw -> ..data/_raw
lrwxrwxrwx 1 root root 15 Apr 13 19:59 password -> ..data/password
lrwxrwxrwx 1 root root 15 Apr 13 19:59 username -> ..data/username

# デモ用コンテナの /etc/secrets/username を確認
$ kubectl exec -it $(kubectl get pods -n vault-demo -o jsonpath={.items[*].metadata.name}) -n vault-demo -- cat /etc/secrets/username
hoge

# デモ用コンテナの /etc/secrets/passwordを確認
$ kubectl exec -it $(kubectl get pods -n vault-demo -o jsonpath={.items[*].metadata.name}) -n vault-demo -- cat /etc/secrets/password
piyo

このように、Vault Secrets Operator がシークレットの更新を検知して Deployment をローリングアップデートすることを確認できました。

おわりに

以上で、Vault Secrets Operator と HCP Vault on Azure を使った動作確認は終了です。

それでは、まとめに入ります。

まとめ

今回検証した Vault Secrets Operator と HCP Vault について要点をまとめます。

  • Vault Secrets Operator
    • 2023/4 にプレビュー公開された
    • Vault シークレットと Kubnertnetes シークレットを同期
    • Deployment をローリングアップデートしてシークレットを反映
  • HCP Vault on Azure
    • 2023/2 に GA
    • Azure VNet とピアリングして Azure VM や AKS とセキュアに接続可能

今後の展望

今回の検証では静的シークレットのみ対象としていましたが、Vault には「動的シークレット」というとても便利な機能があります。 Vault Secrets Operator で動的シークレットを使うことで、Kubernetes 上で Azure のサービスプリンシパルをより扱い易くなるのでは、と考えています。 こちらは別途、動作確認してみるつもりです。

また、Vault Secrets Operator はまだプレビュー段階のため Kubernetes 認証しかサポートしていませんが、今後は他の認証方法もサポートされる予定です。 今後とも Vault Secrets Operator には注目していきたいですね。

ACS事業部のご紹介

私達 ACS 事業部は Azure・AKS などのクラウドネイティブ技術を活用した内製化のご支援をしております。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。

www.ap-com.co.jp

本記事の投稿者: 埜下 太一
AKS/ACA をメインにインフラ系のご支援を担当しています。
個人ブログ