HCP Vault Secrets でシークレットを管理する
APIキーなどのシークレットを安全かつ効率的に管理することは、セキュリティリスクを軽減し、開発速度を向上させるために重要です。この記事では、HCP Vault Secretsを使ったシークレット管理について、以下についてお伝えします。
- HCP Vault Secretsの概要
- HCP Terraformとの連携
- Kubernetes環境のSecretリソースとの同期
HCP Vault Secretsの概要
HCP Vault Secrets(以下においてはVault Secretsと記載)とは、HashiCorpが提供しているSaaS型のシークレット管理ツールです。非常にシンプルなので、どなたでも簡単に直ぐに使い始められます。
Vault Secretsのセキュリティモデルに関するドキュメントの中で、ハイレベルなアーキテクチャーが記載されていますので、Vault Secretsがどのような仕組みで稼働しているか概要を把握する事ができます。
また、最近はVault Secretsの機能強化も積極的に行われており、クラウドプロバイダ等が提供するマネージドシークレット管理ソリューションへの1-Wayのシークレットの同期、シークレットローテーション、動的シークレットといった機能が利用できる様になってきています。
Vault Secretsの操作は、CLI、GUI、hcp
Terraformプロバイダから行う事ができます。CLIに関しては、hcp
CLIのvault-secrets
サブコマンドを利用する事で、Vault Secretsの操作を行う事が出来る様になっています。
HCP Terraformとの連携
Terraformのワークフロー管理で利用されているHCP Terraformでは、AWS、Azure、Google Cloud、Kubernetes、Vault、HCPといったプロバイダを利用する際に必要な認証情報を動的に扱える機能を提供しています。ただ、これら以外のプロバイダを利用する際は、静的にAPIキーを生成し、それを環境変数等に設定して利用する必要があります。
その様なAPIキーをVault Secretsに保管し、一元管理しておく事で、シークレットを安全に管理する事ができます。さらに、Vault SecretsのSecrets Syncの機能でHCP TerraformのVariables、Variable Setsにシークレットを同期させてあげる事でVault Secrets側でシークレット情報を更新した際に、自動で同期されるので便利です。
設定方法は以下のドキュメントに記載されていますが、非常にシンプルですので、HCP Terraformをお使いの方は併せてVault Secretsも利用するとシークレットの管理をよりシンプルに出来る可能性があります。
以下はDatadogのTerraformプロバイダーを利用する際に必要な認証情報などをVault Secretsに保管しています。
Vault SecretsのApplication名:datadog
にシークレットを保管
これをVault SecretsのSecrets Syncを利用して、HCP TerraformのVariable Setsに同期しています。同期先のVariable Setsは事前に作成しておく必要がありますが、同期が正しく開始されると、Key:Valueの値が指定したVariable Setsの中に作成されます。
HCP TerraformのVariable Setsに同期されたシークレット
このVariable SetsをHCP Terraformのプロジェクトやワークスペースと紐付ける事で、それらの環境でdatadog
プロバイダーを利用するの認証情報をTerraformコード上で指定する必要がなく、HCP Terraform上でTerraform Runを実行できます。
Kubernetes環境のSecretリソースとの同期
シークレットは様々な環境で必要になると思いますが、ここでは、Datadog Operatorを利用し、Datadog Agentをデプロイする際に必要となるDatadogのAPIキー、アプリケーションキーを、Vault Secrets Operator(以下VSOと記載)経由でVault Secretsから取得し、Kubernetes(以下K8sと記載)のSecretリソースに同期する方法を紹介します。
Operatorのインストール
K8sクラスタはTerraformを使ってGKEをプロビジョニングしています。まずは最初にDatadog Operator、VSOをTerraformを利用し、インストールしていきます。
TerraformのワークフローはHCP Terraformを利用しています。事前にプロビジョニングしたGKEはHCP Terraformの別のワークスペースで管理されており、リモートステートでkubernetes
プロバイダ、helm
プロバイダに必要な認証情報をそのワークスペースで管理されているステートから取得しています。hcp
プロバイダに関しては、動的クレデンシャルを利用し、このワークスペースでTerraformの処理を実行するタイミングのみ有効な認証情報を設定しています。
data "google_client_config" "default" {}
data "terraform_remote_state" "gke" {
backend = "remote"
config = {
organization = "HCP_TERRAFORM_ORGANIZATION_NAME"
workspaces = {
name = "HCP_TERRAFORM_WORKSPACE_NAME"
}
}
}
provider "kubernetes" {
host = "https://${data.terraform_remote_state.gke.outputs.gke_endpoint}"
cluster_ca_certificate = base64decode(data.terraform_remote_state.gke.outputs.gke_cluster_ca_certificate)
token = data.google_client_config.default.access_token
}
provider "helm" {
kubernetes {
host = "https://${data.terraform_remote_state.gke.outputs.gke_endpoint}"
cluster_ca_certificate = base64decode(data.terraform_remote_state.gke.outputs.gke_cluster_ca_certificate)
token = data.google_client_config.default.access_token
}
}
provider "hcp" {}
各Operatorはhelm
プロバイダを利用して、以下の様に設定しています。
resource "helm_release" "datadog_operator" {
name = "my-datadog-operator"
chart = "datadog-operator"
namespace = "default"
repository = "https://helm.datadoghq.com"
version = "1.7.0"
}
resource "helm_release" "vault_secrets_operator" {
name = "my-vault-secrets-operator"
chart = "vault-secrets-operator"
namespace = "default"
repository = "https://helm.releases.hashicorp.com"
version = "0.9.1"
}
正しくインストールされると以下のようにカスタムリソースがK8sクラスタで利用出来る様になるはずです。
$ k api-resources |grep hashicorp
hcpauths secrets.hashicorp.com/v1beta1 true HCPAuth
hcpvaultsecretsapps secrets.hashicorp.com/v1beta1 true HCPVaultSecretsApp
secrettransformations secrets.hashicorp.com/v1beta1 true SecretTransformation
vaultauthglobals secrets.hashicorp.com/v1beta1 true VaultAuthGlobal
vaultauths secrets.hashicorp.com/v1beta1 true VaultAuth
vaultconnections secrets.hashicorp.com/v1beta1 true VaultConnection
vaultdynamicsecrets secrets.hashicorp.com/v1beta1 true VaultDynamicSecret
vaultpkisecrets secrets.hashicorp.com/v1beta1 true VaultPKISecret
vaultstaticsecrets secrets.hashicorp.com/v1beta1 true VaultStaticSecret
$ k api-resources |grep datadog
datadogagents dd datadoghq.com/v2alpha1 true DatadogAgent
datadogmetrics datadoghq.com/v1alpha1 true DatadogMetric
datadogmonitors datadoghq.com/v1alpha1 true DatadogMonitor
Vault Secretsからのシークレットの取得
Datadog OperatorとVSOのインストールが完了したので、VSOを介してDatadog Agentのインストールに必要な認証情報、APIキーとアプリケーションキーを取得していきます。これらのキーは既にDatadog上で作成され、Vault SecretsのApplicationdatadog
で管理されているものとします。
この後の処理もHCP Terraformを利用し、別のワークスペースを用意し実施していきます。プロバイダの設定は先ほどと同様の設定をしています。
VSOのカスタムリソースHCPAuth
を利用する際に、HCPへの認証を行う必要があるため、HCPのサービスプリンシパルをTerraformで作成しています。サービスプリンシパルにはロールを付与する必要がありますが、HCPで利用出来るロールに関しては以下のドキュメントで確認できます。
作成したHCPのサービスプリンシパルに紐付くclientID
とclientSecret
を、K8sのSecretリソースvso-hcp-sp
に定義しています。
data "hcp_organization" "my_org" {}
data "hcp_project" "default" {}
resource "hcp_service_principal" "vso" {
name = "vso-hcp-sp"
parent = data.hcp_project.default.resource_name
}
resource "hcp_project_iam_binding" "vso" {
project_id = data.hcp_project.default.resource_id
principal_id = hcp_service_principal.vso.resource_id
role = "roles/viewer"
}
resource "hcp_service_principal_key" "vso" {
service_principal = hcp_service_principal.vso.resource_name
}
resource "kubernetes_secret_v1" "vso_hcp_sp" {
metadata {
name = "vso-hcp-sp"
namespace = "default"
}
data = {
clientID = "${hcp_service_principal_key.vso.client_id}"
clientSecret = "${hcp_service_principal_key.vso.client_secret}"
}
type = "Opaque"
depends_on = [hcp_service_principal_key.vso]
}
VSOのカスタムリソースを利用する準備が出来たので、VSOを利用して、Vault Secretsで管理しているDatadogの認証情報をK8sのSecretリソースに同期させます。
以下のTerraformコードでは、K8sプロバイダで利用出来るFunctionを利用して、別のディレクトリで管理しているマニフェストファイルを読み込ませています。
resource "kubernetes_manifest" "hcp_vault_secrets_auth" {
manifest = provider::kubernetes::manifest_decode(templatefile("${path.module}/../../vso/hcp-vault-secrets-auth.yaml", {
hcp_org_id = data.hcp_organization.my_org.resource_id,
hcp_project_id = data.hcp_project.default.resource_id
}))
depends_on = [
kubernetes_secret_v1.vso_hcp_sp
]
}
resource "kubernetes_manifest" "hcp_vault_secrets_app" {
manifest = provider::kubernetes::manifest_decode(templatefile("${path.module}/../../vso/hcp-vault-secrets-app.yaml", {
app_name = var.vault_secrets_app_name
}))
depends_on = [
kubernetes_manifest.hcp_vault_secrets_auth
]
}
上記のTerraformコードで利用しているHCPAuth
リソース、HCPVaultSecretsApp
リソース、それぞれのマニフェストファイルは以下の通りです。
hcp-vault-secrets-auth.yaml
では、VSOとHCPとの認証設定を行っています。この中で、先ほど作成したサービスプリンシパルをストアしているK8sのSecretリソースを参照しています。
apiVersion: secrets.hashicorp.com/v1beta1
kind: HCPAuth
metadata:
name: hcp-auth
namespace: default
spec:
organizationID: "${hcp_org_id}"
projectID: "${hcp_project_id}"
allowedNamespaces: ["*"]
servicePrincipal:
secretRef: vso-hcp-sp
hcp-vault-secrets-app.yaml
では、VSOがVault SecretsのターゲットとなるApplication名(Terraform変数vault_secrets_app_name
の値を格納した${app_name}
)と利用するHCPの認証設定を定義し、取得したシークレット情報をコピーするK8sのSecretリソースに関する設定をしています。
apiVersion: secrets.hashicorp.com/v1beta1
kind: HCPVaultSecretsApp
metadata:
name: datadog
namespace: default
spec:
appName: ${app_name}
hcpAuthRef: hcp-auth
destination:
name: datadog-secret
create: true
overwrite: true
labels:
hvs: "true"
type: Opaque
refreshAfter: 1h
VSOがHCPAuth
リソースで定義された情報を元にVault Secretsにアクセスし、HCPVaultSecretsApp
リソースで定義された情報を元に、Vault Secrets上で管理されているシークレットを取得し、それらをK8sのSecretリソースにコピーします。
この処理が終わった後に、Datadog Agentをデプロイします。Terraformコードに関しては、VSOのカスタムリソースをデプロイした際とほとんど同じですが、depends_on
でVSO側のデプロイが完了した後に処理を行う様にしています。
resource "kubernetes_manifest" "datadog_agent" {
manifest = provider::kubernetes::manifest_decode(file("${path.module}/../../datadog/operator/datadog.yaml"))
depends_on = [
kubernetes_secret_v1.vso_hcp_sp,
kubernetes_manifest.hcp_vault_secrets_auth,
kubernetes_manifest.hcp_vault_secrets_app
]
}
datadog.yaml
は以下の様に設定しています。spec.global.credentials
でAPIキー、アプリケーションキーの設定をしていますが、これらはVSOを介して、Vault Secretsから取得したシークレット情報になっています。
apiVersion: datadoghq.com/v2alpha1
kind: DatadogAgent
metadata:
name: datadog
namespace: default
spec:
global:
clusterName: my-cluster-agent
kubelet:
tlsVerify: false
credentials:
apiSecret:
secretName: datadog-secret
keyName: DD_API_KEY
appSecret:
secretName: datadog-secret
keyName: DD_APP_KEY
features:
logCollection:
enabled: true
containerCollectAll: true
apm:
hostPortConfig:
enabled: true
liveProcessCollection:
enabled: true
usm:
enabled: true
override:
nodeAgent:
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Exists
HCP Terraform上でterraform apply
を実行すると、無事リソースが立ち上がる事が確認できると思います。
$ k get pods
NAME READY STATUS RESTARTS AGE
datadog-agent-7fblw 4/4 Running 0 26h
datadog-agent-9pwk7 4/4 Running 0 26h
datadog-agent-gdcbk 4/4 Running 0 26h
datadog-agent-tgzmq 4/4 Running 0 26h
datadog-cluster-agent-cc8874d64-clf5q 1/1 Running 0 26h
my-datadog-operator-db85ff84c-rqssq 1/1 Running 0 26h
my-vault-secrets-operator-controller-manager-8696484498-t4tx6 2/2 Running 0 8d
$ k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
datadog-cluster-agent 1/1 1 1 26h
my-datadog-operator 1/1 1 1 8d
my-vault-secrets-operator-controller-manager 1/1 1 1 8d
Datadogに限らず、さまざまなサービスのAPIキー等のシークレット情報は、多くの場面や環境で利用されることがあると思います。これらをVault Secretsで一元管理することで、セキュリティリスクの一因となるシークレットの散在を効果的に防ぐことができます。
Discussion