🥢

HCP Vault Private Clusterで弊社AKSのSecret Operator実装が辛すぎた

2023/10/31に公開

どうも、イオンスマートテクノロジー株式会社(社内ではASTと略します)CTO室SREチームのあおしょんです。

弊社では現在シークレット管理の切り札としてHCP (HashiCorp Cloud Platform) Vaultの導入を推進しています。
https://developer.hashicorp.com/hcp/docs/vault
今回は検証の一環として実施した下記のチュートリアルについて、弊社のシステム構成で実施した場合の辛みと対策についてご説明いたします。
https://developer.hashicorp.com/vault/tutorials/cloud/get-started-vault
https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator

検証の前提条件

弊社のプラットフォームはほぼ全てAzureなのでHCP VaultのクライアントとなるKubernetesはAKS (Azure Kubernetes Service) を利用しています。

また、社内のセキュリティーポリシー上、下記の条件で検証を実施しました。

  • HCP Vault ClusterはPrivateであること。
  • HCP Vaultのクライアントとなるリソースのエンドポイントはプライベートであること。

この条件がVault Secret Operator検証の一番の辛みになりました…

HCP Vaultとの繋ぎ、即ちピアリングとルート定義

妄想ハブ&スポーク構成

繰り返しますが現在弊社のプラットフォームほぼ全てAzureです。オンプレも一切ございません。(但し、オンプレのシステムと接続することはあります。)そして弊社全体のシステム構成は下記を元にしたハブ&スポークとなっています。
https://learn.microsoft.com/ja-jp/azure/architecture/reference-architectures/hybrid-networking/hub-spoke?tabs=cli
本記事ではHCP Vaultが主役なのでAzureのハブ&スポークが何たるかを語りたいわけではないです。(ハブ&スポークにしてみた大辛みはいつか別の記事でお伝え出来たらと思います。)ここでお伝えしたいのは検証開始直前の私の妄想システム構成と比べて実際に検証をしてみたら妄想とはかなり異なった構成になったことです。
ハブ&スポークと言うくらいなので当然ではありますがスポークから別のスポークへの通信経路は下記の様にハブを経由します。

Azure Firewallをルーターとして各スポークのユーザールート定義でネクストホップに指定しています。デフォでこの構成が私の頭の中にあり、かつピアリングの中心をハブに集約したかったのでHCP Vaultが関連するHVN (HashiCorp Virtual Network)もイチスポークとして構成することを思い浮かべていました。

なおHVN is ナニ?の場合は下記の公式ドキュメントご参照ください。
https://developer.hashicorp.com/hcp/docs/hcp/network

HVN関連の設定と落とし穴

まずは下記を参考にVault Clusterを関連付けするHVNを作成しました。
https://developer.hashicorp.com/hcp/docs/hcp/network/hvn-azure/hvn-azure
HVNの作成、Peering Connectionsの作成はチョロです。但しPeering Connectionsの作成の過程でHCPポータル上で生成される下記例のtfファイルをterraform applyする必要があります。

provider "azurerm" {
  features {}
}

locals {
  application_id = "4ed5cea3-474b-4823-b281-ef175367094d"
  role_def_name  = join("-", ["hcp-hvn-peering-access", local.application_id])
  vnet_id        = "/subscriptions/57e71321-355a-4993-a8f3-1bc4c5a430c7/resourceGroups/poc-vault-rg/providers/Microsoft.Network/virtualNetworks/poc-vault-vnet"
}

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 applyをすればPeering Connectionsの状態をActiveに出来るのですが中身を理解するにはAzureのService Principalの理解が必須となります。が、本筋ではないので割愛します。(もちろんAST CTO室SREチームのメンバーは全員理解しています。)

HVN関連設定の最後の実施項目はRoute Tableなのですが先述した通り私の妄想は下記でした。

ですが…下記のドキュメントをちゃんとしっかり読み込めば分かるのですが…
https://developer.hashicorp.com/hcp/docs/hcp/network/hvn-azure/routes
HVNのRoute Tableで指定出来るのは下記のみでした。

  • Destination:宛先のCIDR範囲
  • Target:ターゲットのPeering Connection

ハブを中継してホップ!ステップ!ジャンプ!出来ない👼
これは受け入れざる得ないので各スポークとHVNをピアリングすることを我慢しました。

実際スポークはもっと沢山あるのでTerraformで管理する時にVNet PeeringのTerraform Resourceゴチャゴチャするな…と思ったのですが、azurerm_virtual_network_peeringで定義するわけではなくhcp_azure_peering_connectionで定義するのでVNet同士のPeeringとは別世界のことなんだと自身に納得感を持たせることが出来ました。(と思いこむしかない)どうせTerraform CloudのWorkspaceもGitリポジトリも分けますし。

結局ここまでほぼ全てHCP VaultではなくHVN周りの話になりましたがHCP Vault Clusterの作成自体はチョロなので割愛します。(え?

Secret Operator実装までの道のり

ここまでが事前準備(長かった…)で本項目ではKubernetesのシークレット管理の主役でありデファクトスタンダードのSecret Operatorの検証内容と辛みをお話します。
冒頭でお伝えした通りThe Vault Secrets Operator on Kubernetesのチュートリアルを弊社のシステム構成を模擬して実施しました。

AKS, HCP Vault間でプライベート通信をするためにAKSをプライベートクラスターとすることでHVNとPeeringしているVNet上にAKSのAPI Serverのプライベートエンドポイントが作成されます。プライベートクラスターのためAPI Serverのパブリックエンドポイントは存在しません。

KubernetesのAuth Methodだとうまくいかなかった

まずはチュートリアルの通りKubernetesのAuth Methodで実施しました。
https://developer.hashicorp.com/vault/docs/auth/kubernetes
実施結果は…HCP Vaultとの認証に失敗する…!
本事象の解決のためにHashiCorpのソリューションエンジニアの方々、サポートエンジニアの方々と数週間に渡ってやり取りをしてトライアンドエラーを繰り返した結果サポートエンジニアの回答によりHCP Vault側のサーバーログに下記が出力されていることが分かりました。

login unauthorized: err="Post \"https://aaa.XXX.privatelink.japaneast.azmk8s.io:443/apis/authentication.k8s.io/v1/tokenreviews\": dial tcp: lookup aaa.XXX.privatelink.japaneast.azmk8s.io on 127.0.0.53:53: no such host"

HCP VaultがAKSのAPI ServerのプライベートFQDNを名前解決出来ない👼

名前解決出来ないのは当然のことでAKSのAPI ServerのDNSレコードを定義しているAzure Private DNS Zoneは弊社のEntraテナント(昔で言うAzureADテナント)に存在しており、テナントの異なるHVNの仮想ネットワークリンクは作成出来ません。そもそもAzure VNetでもないですしね…
絵にするまでも無いですが…

以上のことからHCP VaultからAKSのAPI Serverへのアクセスが発生するKubernetesのAuth Methodは弊社のシステム構成では利用出来ないことが分かりました。

JWTというAuth Methodでうまくいくらしい

じゃあHCP VaultでK8sのシークレット管理出来ないんじゃ…と少しの間途方に暮れていたのですがサポートエンジニアから別のAuth Methodを利用する案を提示されました。
https://developer.hashicorp.com/vault/docs/auth/jwt/oidc-providers/kubernetes
OIDC?JWT?なんのこっちゃというレベルの私なのですが、ようはHCP VaultからAKSのAPI Serverへのアクセスが発生しないAuth Methodです。HCP Vaultとの認証にK8s Service Accountの署名済み公開鍵を利用する方式のようです。(ザックリ過ぎてすみません。)

まあまあ手順とYAMLファイルの修正があった

結果的にはチュートリアルの通りSecret OperatorでK8sのSecretリソースを作成することが出来たのですがAuth MethodをJWTにしたことによりチュートリアルの手順とK8sのYAMLファイルにまあまあの修正が必要だったのでその点だけ抜粋します。

Auth Method JWTの構成

まずはJWTのAuthMethod(以下、JWT)を有効にします。

vault auth enable jwt

Using JWT validation public keysに従いJWTのconfigにK8s Service Accountの署名済み公開鍵を設定します。
設定内容は下記コマンドで確認できます。

vault read auth/jwt/config

Key                       Value
---                       -----
bound_issuer              n/a
default_role              n/a
jwks_ca_pem               n/a
jwks_url                  n/a
jwt_supported_algs        []
jwt_validation_pubkeys    [-----BEGIN PUBLIC KEY-----
......
-----END PUBLIC KEY-----]
namespace_in_state        true
oidc_client_id            n/a
oidc_discovery_ca_pem     n/a
oidc_discovery_url        n/a
oidc_response_mode        n/a
oidc_response_types       []
provider_config           map[]

次にJWTのroleを作成します。下記は作成コマンドの実行例です。

vault write auth/jwt/role/role1 \
   role_type="jwt" \
   bound_audiences="https://aaa.XXX.privatelink.japaneast.azmk8s.io" \
   user_claim="sub" \
   bound_subject="system:serviceaccount:app:default" \
   policies="dev" \
   ttl="24h"

bound_audienceはAKSのAPI ServerのプライベートFQDNを指定する必要があります。

Vault Secret Operatorのインストール

Install the Vault Secrets Operatorでもツマずいたことが2点ありました。

1点目として下記のエラーでインストール失敗しました。

helm install vault-secrets-operator hashicorp/vault-secrets-operator --version 0.1.0 -n vault-secrets-operator-system --create-namespace --values vault/vault-operator-values.yaml

WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/ast-aoshon/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/ast-aoshon/.kube/config
Error: INSTALLATION FAILED: repo hashicorp not found

その場合は下記を実行することでインストール出来るようになります。(K8s詳しい人には当たり前だろと思われる内容です。無知ですみません。)

chmod 600 ~/.kube/config

2点目はインストール自体は成功したのですが後続のVault Secret OperatorのK8s Secretリソース作成が上手くいかなかったことの原因の一つでチュートリアルのVault Secret Operatorのバージョンが古いことです。最新バージョンのインストール方法を記載しておきます。

まずは最新バージョンを調べます。

helm search repo hashicorp/vault

最新バージョンでVault Secret Operatorをインストールします。検証時は0.3.1が最新だったので下記はその時のコマンド実行例となります。

helm install vault-secrets-operator hashicorp/vault-secrets-operator --version 0.3.1 -n vault-secrets-operator-system --create-namespace --values vault/vault-operator-values.yaml

K8s YAMLファイルの修正

JWTの認証に対応するためDeploy and sync a secretでkubectl applyするvault-auth-static.yamlに一部修正が必要となります。修正例は下記の通りです。

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: static-auth
  namespace: app
spec:
  vaultConnectionRef: vault-secrets-operator-system/default
  namespace: admin
  method: jwt
  mount: jwt
  jwt:
    role: role1
    serviceAccount: default 

修正内容について説明します。

  • Auth MethodをJWTにするためにmethodをjwtにする。
  • auth pathはdemo-auth-mountではなくデフォルトのjwtとしているためmountをjwtにする。
  • vaultConnectionRefでVault Secret Operatorを<NAMESPACE _NAME/RESOURCE_NAME>というフォーマットで指定する。

各フィールドの詳細は下記を参照してください。
VaultAuthSpec
VaultAuthConfigJWT

以上の対応でチュートリアルのRotate the static secretまで実施出来ることを確認しています。

おわりに

ここまでやり通すまで約二か月以上の期間を要しました。(コレだけやっていたわけではないという言い訳)やはりいざ自社のシステムに新規モノのプロダクトを入れようとするとなかなかすんなりいかないものですよね。

また、今回はDynamic secretsの検証内容まで説明出来ていないので別途投稿したいと思います。

最期に定型文となりますが、イオンスマートテクノロジーではエンジニアを絶賛採⽤中です!
https://hrmos.co/pages/ast

AEON TECH HUB

Discussion