Terraform Provider(Vault/Kubernetes編)

みんな大好きTerraform。知名度も上がって様々なResourceのProvisioningに利用されるようになりました。OfficialのProviderはたくさんあります。GCP,AWS,Azure… 中でも最近便利だなーと思ったVaultとKubernetes Providerについてご紹介します。

Vault Provider

Vault構築する人には絶対使って欲しいProvider。

Vaultの設定は次の理由により煩雑な処理が必要で、手順を準備したりshell化していても誰でも設定できるようなシロモノではない。

  • 構築時のみ実行することが多い
  • Policy,Auth Backend,PKIなど様々な設定が存在する
  • CLIやcURLでの操作はTokenを設定したり出力をParseするのが大変
  • なんなら出力は別のリソースで利用したい

Policy

次の例はPolicyをtfファイルに記述したものです。

provider vault {
  version         = "~> 2.9.0"
  address         = var.vault.addr
  token           = var.vault.token
  skip_tls_verify = true
}

/*
Setup Vault policies.
*/
resource vault_policy consul-template {
  name   = "consul-template"
  policy = file("./policies/consul-template.hcl")
}

上記で参照しているPolicyは次のとおり。

# Update internal certificates.
path "pki_int/issue/default-dot-svc-dot-cluster-dot-local"
{
  capabilities = ["create", "update"]
}
# renew
path "auth/token/renew-self"
{
  capabilities = ["create", "update"]
}

つまりこれを利用すれば

  • Policyのコード化
  • Policyの適用手順をコード化

が可能になり、誰が実行しても同じ設定が可能になります。policiesディレクトリを環境ごとに作成し、環境ごとにtfvarsを用意するかworkspaceを利用することで別環境への設定が容易になるでしょう。またVaultのTechnical Transferとしても有効でしょう。

AppRole Auth Backend

次の例はAppRole Auth Backendをtfに記述したものです。

/*
AppRole Auth Backend.
*/
resource vault_auth_backend approle {
  type = "approle"
}

resource vault_approle_auth_backend_role pxc {
  backend   = vault_auth_backend.approle.path
  role_name = "pxc"
  token_policies = [
    "pxc",
  ]
  token_ttl               = 600
  token_max_ttl           = 1800
  token_no_default_policy = true
  token_bound_cidrs = [
    "10.31.128.0/18",
  ]
  secret_id_num_uses = 1
  secret_id_ttl      = 3600
  secret_id_bound_cidrs = [
    "10.31.128.0/18",
  ]
}

Kubernetes Auth Backend

次の例はKubernetes Auth Backendをtfに記述したものです。先にKubernetes Provider使ってしまってますが、気にしないでください。

/*
Generate Service Account and Cluster Role Binding for the Vault Auth Backend.
*/
resource kubernetes_service_account vault-auth {
  metadata {
    name = "vault-auth"
  }
}

resource kubernetes_cluster_role_binding vault-auth {
  depends_on = [kubernetes_service_account.vault-auth]
  metadata {
    name = "vault-tokenreview-binding"
  }
  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = "system:auth-delegator"
  }
  subject {
    kind      = "ServiceAccount"
    name      = "vault-auth"
    namespace = "default"
  }
}

/*
Retrive k8s secrets
*/
data external k8s-secrets {
  depends_on = [kubernetes_cluster_role_binding.vault-auth]
  program    = ["./scripts/k8s-secrets.sh"]
  query = {
    service_account = "vault-auth"
  }
}

/*
Kubernetes Auth Backend.
*/
resource vault_auth_backend kubernetes {
  type = "kubernetes"
}

resource vault_kubernetes_auth_backend_config vault-primary {
  depends_on = [
    data.external.k8s-secrets,
    vault_auth_backend.kubernetes,
  ]
  backend            = vault_auth_backend.kubernetes.path
  kubernetes_host    = var.kubernetes.host
  kubernetes_ca_cert = data.external.k8s-secrets.result["cacert"]
  token_reviewer_jwt = data.external.k8s-secrets.result["jwt"]
}

上記でData External Provider参照している./scripts/k8s-secrets.shは次のとおり。jqやらkubectlを利用していてキモいですが、かなり柔軟に値を取得することができるので重宝します。

#! /usr/bin/env bash
set -xe
set -o pipefail

# Extract args into shell variables
eval "$(jq -r '@sh "SERVICE_ACCOUNT=\(.service_account)"')"

K8S_API_CERT=$(kubectl get secrets -o yaml | \
  yq -r ".items[] | select(.metadata.annotations[\"kubernetes.io/service-account.name\"] == \"${SERVICE_ACCOUNT}\").data | .[\"ca.crt\"]")

K8S_API_TOKEN_JWT=$(kubectl get secrets -o yaml | \
  yq -r ".items[] | select(.metadata.annotations[\"kubernetes.io/service-account.name\"] == \"${SERVICE_ACCOUNT}\").data.token" | \
  base64 -D)

jq -n \
  --arg cacert "${K8S_API_CERT}" \
  --arg jwt "${K8S_API_TOKEN_JWT}" \
  '{"cacert": $cacert, "jwt": $jwt}'

Policyのとこで書きましたが、手順書やshellを書くよりもずっといいですよ。

こんなノリでVaultに設定したいtfを用意してやればいいのです。便利ですよね?

Kubernetes Provider

Kubernetesのリソースデプロイに関してはHelm,Kustomize,jsonnet,Replicated Ship…などなどいろいろありいまして、正直言ってこれの利用価値わかりませんでしたが、利用してみると他のツールとは一味違ったことが可能になります。

例えばHelmはValuesを指定したSolid Stateなデプロイに向いていますが、Kubernets Providerを使うと動的なデプロイが可能になります。揮発性の高いSecretとは親和性が高いと思います。Operatorを利用した自動運用化と、静的デプロイの間ぐらいの印象です。

Secrets

Vaultの設定を実施したら、別のtfでVaultから読み取ったデータをKubernetes Secretに埋め込めます。

provider vault {
  version         = "~> 2.9.0"
  address         = var.vault.addr
  token           = var.vault.token
  skip_tls_verify = true
}

data vault_approle_auth_backend_role_id migration-db {
  backend   = "approle"
  role_name = "pxc"
}

resource vault_token db-migrator {
  policies = ["db-migrator"]

  no_parent         = true
  no_default_policy = true
  renewable         = true
  ttl               = "768h"
  explicit_max_ttl  = "92160h"
}

resource vault_token job-generator {
  policies = ["job-generator"]

  no_parent         = true
  no_default_policy = true
  renewable         = true
  ttl               = "768h"
  explicit_max_ttl  = "92160h"
}

provider kubernetes {
  config_context_auth_info = var.vault.auth_info
  config_context_cluster   = var.vault.cluster
}

resource kubernetes_secret db-migrator {
  metadata {
    name = "db-migrator"
  }

  data = {
    role-id     = data.vault_approle_auth_backend_role_id.migration-db.role_id
    vault-token = vault_token.db-migrator.client_token
  }

  type = "kubernetes.io/opaque"
}

resource kubernetes_secret job-generator {
  metadata {
    name = "job-generator"
  }

  data = {
    vault-token = vault_token.job-generator.client_token
  }

  type = "kubernetes.io/opaque"
}

上記applyすると次のようにSecretが作成されています。

$ kubectl get secrets
NAME                     TYPE                                  DATA   AGE
db-migrator              kubernetes.io/opaque                  2      5s
default-token-ndnzq      kubernetes.io/service-account-token   3      6d
job-generator            kubernetes.io/opaque                  1      5s
vault-auth-token-5tnhn   kubernetes.io/service-account-token   3      122m

これはかなり強力な機能です。

実を言うとこれがやりたくて理由でTerraformでVaultと一緒にVault ProviderとKubernetes Providerを使いたくなったのです。もちろんGCPのリソースもTerraformで構築しています。Nomadごめんよ・・・

See u next time!