HCP Vault Private Clusterで弊社AKSのSecret Operator実装が辛すぎた
どうも、イオンスマートテクノロジー株式会社(社内ではASTと略します)CTO室SREチームのあおしょんです。
弊社では現在シークレット管理の切り札としてHCP (HashiCorp Cloud Platform) Vaultの導入を推進しています。
今回は検証の一環として実施した下記のチュートリアルについて、弊社のシステム構成で実施した場合の辛みと対策についてご説明いたします。検証の前提条件
弊社のプラットフォームはほぼ全てAzureなのでHCP VaultのクライアントとなるKubernetesはAKS (Azure Kubernetes Service) を利用しています。
また、社内のセキュリティーポリシー上、下記の条件で検証を実施しました。
- HCP Vault ClusterはPrivateであること。
- HCP Vaultのクライアントとなるリソースのエンドポイントはプライベートであること。
この条件がVault Secret Operator検証の一番の辛みになりました…
HCP Vaultとの繋ぎ、即ちピアリングとルート定義
妄想ハブ&スポーク構成
繰り返しますが現在弊社のプラットフォームほぼ全てAzureです。オンプレも一切ございません。(但し、オンプレのシステムと接続することはあります。)そして弊社全体のシステム構成は下記を元にしたハブ&スポークとなっています。
ハブ&スポークと言うくらいなので当然ではありますがスポークから別のスポークへの通信経路は下記の様にハブを経由します。
Azure Firewallをルーターとして各スポークのユーザールート定義でネクストホップに指定しています。デフォでこの構成が私の頭の中にあり、かつピアリングの中心をハブに集約したかったのでHCP Vaultが関連するHVN (HashiCorp Virtual Network)もイチスポークとして構成することを思い浮かべていました。
なおHVN is ナニ?の場合は下記の公式ドキュメントご参照ください。
HVN関連の設定と落とし穴
まずは下記を参考にVault Clusterを関連付けするHVNを作成しました。
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なのですが先述した通り私の妄想は下記でした。
ですが…下記のドキュメントをちゃんとしっかり読み込めば分かるのですが…
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で実施しました。
本事象の解決のために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を利用する案を提示されました。
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の検証内容まで説明出来ていないので別途投稿したいと思います。
最期に定型文となりますが、イオンスマートテクノロジーではエンジニアを絶賛採⽤中です!
Discussion