お疲れ様です。おれちゃんでござる!
Vaultは繊細情報保管ソリューションです。ユーザーがログインしたらクレデンシャルの読み書きができます。でもユーザーではなくアプリケーションだったらどうすればいいのですか?
様々な方法がありますが今回はk8sのService Accountという方法を紹介したいと思います。
この記事に使われている例としてAtlantisのk8sスタックをあげます。
Service Accountって何?
Kubernetes(k8s)サービスアカウントは、ポッドがKubernetes APIサーバーと認証および通信するために使用するオブジェクトです。クラスタ内でのアイデンティティとして機能し、ポッドがクラスタ内でさまざまなリソースに安全にアクセスするための抽象化を提供します。
AWSと比較したらService AccountはAWS的にIAM Rolesみたいなものです。アプリケーションの連帯に使われています。
具体的に何をやりたい
VaultとAtlantisは両方k8sのスタックなのでk8sのService Account連帯できます。尚、Vaultはk8sのService Accountを認証方法として対応できます。
図に書いてるように:
-AtlantisはService Accountを与える
-このService Accountはk8sのAPIでトークンを発行する(JWTタイプのトークン)
-Vaultはk8sという承認バックエンドを追加することで、Service AccountのトークンでVaultのトークンが発行できる
-VaultトークンはService Accountのお陰でポッドで使えます(/var/run/secrets/kubernetes.io/serviceaccount/tokenに現る)
ちなみにトークンの交換やVaultを使ってSecretを書き読みなどは全てk8sクラスター内なのでセキュリティが高いです。
やってみよう!
まずはService Accountを作ります。JWT Reviewerとして使われます
resource "kubernetes_service_account" "vault_account" {
metadata {
name = "vault-reviewer"
namespace = "vault"
}
}
次はService AccountにCluster Role bindingを付けます。Cluster Role bindingはあるService Accountなどに権限与えるリソースです。
resource "kubernetes_cluster_role_binding" "vault_binding" {
metadata {
name = "vault-auth-role-token-review-binding"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "system:auth-delegator"
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account.vault_account.metadata.0.name
namespace = "vault"
}
}
次はVaultにk8sバックエンドを有効します。Atlantisポッド用なので名前をAtlantisにします。
resource "vault_auth_backend" "this" {
type = "kubernetes"
path = "atlantis"
}
バックエンドが有効していたら設定をします。
data "kubernetes_service_account" "vault_account" {
metadata {
name = kubernetes_service_account.vault_account.metadata.0.name
namespace = "vault"
}
}
data "kubernetes_secret" "vault_account_token" {
metadata {
name = data.kubernetes_service_account.vault_account.default_secret_name
namespace = "vault"
}
}
data "aws_eks_cluster" "this" {
name = "eks-test"
}
data "kubernetes_config_map" "cacert" {
metadata {
name = "kube-root-ca.crt"
namespace = "kube-system"
}
}
resource "kubernetes_secret_v1" "vault_account_token" {
metadata {
name = "vault-token"
namespace = "atlantis"
annotations = {
"kubernetes.io/service-account.name" = kubernetes_service_account.vault_account.metadata.0.name
}
}
type = "kubernetes.io/service-account-token"
}
resource "vault_kubernetes_auth_backend_config" "this" {
backend = vault_auth_backend.this.path
kubernetes_host = "https://k8s-endpoint.domain.com:6443"
kubernetes_ca_cert = data.kubernetes_config_map.cacert.data["ca.crt"]
token_reviewer_jwt = lookup(kubernetes_secret_v1.vault_account_token.data, "token", "")
disable_iss_validation = "true"
}
Vaultでpolicyも書く必要があります。こうするとAtlantisポッドを使用しているk8sの認証に権限も加わります。
data "vault_policy_document" "admin" {
rule {
path = "*"
capabilities = ["sudo", "read", "create", "update", "delete", "list", "patch"]
description = "allow all"
}
}
resource "vault_policy" "admin" {
name = "admin"
policy = data.vault_policy_document.admin.hcl
}
最後にVaultの認証方法をRole bindingでAtlantisポッドのNamespaceと名前を一致します。
resource "vault_kubernetes_auth_backend_role" "this" {
role_name = "atlantis"
backend = vault_auth_backend.this.path
bound_service_account_names = ["atlantis"]
bound_service_account_namespaces = ["atlantis"]
token_policies = [vault_policy.admin.name]
token_period = 1800
}
以上です!
簡単に仕組みを説明すると以下になります:
この仕組みでVaultのトークンがポッドから回収できるのでVaultをアクセスできます。AtlantisがAWSアカウントにTerraformを起動しているので簡単にVaultからAWSアカウントのクレデンシャルを利用できます。
VaultトークンでVaultを使う
単なcurlコマンドでVaultトークンをAtlantisポッド回収できます。以下のcurlリクエストの中のURLはVault内のネットワークのURLなので外からはアクセスできますんのでセキュリティは高いです。
以上のトークンを環境変数に保管したら、例えばVAULT_TOKEN。以下のcurlコマンドでSecretを回収できます。(以下ではmyscrtの中のaccess_keyを回収します)
最後に
紹介した仕組みで安全にVaultをk8sにホストされたアプリケーションからアクセスできます。これでAtlantisをもっと楽に使えます。
なかなか難しい話してたんですけど設定終わったらあんまり触る必要がありなせんので一瞬頑張ったらすぐ終わりそうな仕組みです。
また次お楽しみにしてね ^^💦