decadence

個人のメモ帳

EKSにおけるkubernetes-external-secretsとIRSAによる権限移譲

これは何

EKSでパスワード等のsecret情報を利用するにあたり、EKS x parameter store/secret secret manager x kubernetes-external-secretを利用する際に、IRSA+assume roleで権限を絞る方法について

背景

EKSを利用する際の課題として、Secretの管理をどのようにして行うのか、といった問題がある。 1つの解として、AWS Parameter Store / Secret Manager / etcによるパスワード等の管理を行い、kubernetes-external-secretsを利用して、ExternalSecret resourceからSecretを作成する、といった方法が考えられる。 一方で、正しい使い方をしないと、意図せず強い権限を持ったroleが出来てしまうこともある。

この記事では現時点では良いと考えられる権限移譲の方法について記述する。

事前知識

権限移譲

先に答えから述べると、現状では以下のような実装を行うのが望ましい。

  1. サービス毎に専用のroleを作成する(service-role)
  2. サービスのパスワードは、そのサービス毎/パスワード毎のkms keyを用意し、service-roleでdecrypt出来る権限を付与する
  3. kubernetes-external-secretのpodに対してservice account(external-secret-sa)を付与する
  4. external-secret-saがIRSA出来るようなrole(external-secret-role)を作成する
  5. external-secret-roleからservice-roleへassume role出来る信頼関係を付与する
  6. ExternalSecretに対してservice-roleへassumeしてSecretを作成出来る設定を付与する

各種設定例

ここでは parameter store に対して、kms keyを用いたSecureStringを登録した上で、external-secretsによりSecretを作成する方法を記述する。resource名称等には、わかりやすさのために-role,-saといったsuffixを付与している。

  • aws resource for external secrets

kubernetes-external-secret podで利用するservice account用のrole作成

locals { oidc_issuer_host_path = replace(var.eks.oidc_issuer_url, "https://", "") }
data "aws_iam_policy_document" "assume_role_with_oidc" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type = "Federated"
      identifiers = ["arn:aws:iam::${var.aws_account_id}:oidc-provider/${local.oidc_issuer_host_path}"]
    }
    condition {
      test = "StringEquals"
      variable = "${local.oidc_issuer_host_path}:sub"
      values = ["system:serviceaccount:${var.k8s_namespace}:external-secrets-sa"]
   }
}
resource "aws_iam_role" "external_secrets" {
  name = "external-secrets-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role_with_oidc.json
}
  • aws resource for services

同じclusterの中に複数のserviceが入る場合は、この設定が複数増えることになる

// roleの作成
data "aws_iam_policy_document" "my_service_assume" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type = "AWS"
      identifiers = [aws_iam_role.external_secrets.arn] // external-secrets用のroleのための信頼関係を付与
    }
  }
}
resource "aws_iam_role" "my_service" {
  name = "my-service-role"
  assume_role_policy = data.aws_iam_policy_document.my_service_assume.json
}

// サービス用のsecret key
resource "aws_kms_key" "my_service" {
  enable_key_rotation = true
}
resource "aws_ssm_parameter" "my_service_secret" {
  name   = "/path/to/my/service/some/secret"
  type   = "SecureString"
  key_id = aws_kms_key.my_service.arn
  value  = "blah blah" // terraform state等に保存しない場合は別途定義が必須
}
  • k8s 側のresource定義

ExternalSecretではspec.roleArnを記述することで、指定のroleを用いて値を取得することが可能になる。

apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
  name: my-service-secret
spec:
  backendType: systemManager
  data:
  - key: /path/to/my/service/some/secret
    name: some-secret
  roleArn: arn:aws:iam::11111111:role/my-service-role

ここまでの設定を行えば、あとはexternal-secretsのpodをexternal-secrets-saのserviceAccountで動かすだけで良い。例として、external-secretsのhelm chartを利用している場合には、以下のような設定をvalues.yamlに書けばよい。

# external-secrets以下のobjectが渡される前提の設定を記述
external-secrets:
  serviceAccount:
    name: external-secrets-sa
    annotations:
      eks.amazonaws.com/role-arn: arn:aws:iam::11111111:role/external-secrets-role
  securityContext:
    # IRSAで /var/run/secrets/eks.amazonaws.com/serviceaccount/token をnon-root userで読むための設定
    # k8s 1.19から不要 https://github.com/kubernetes/enhancements/pull/1598
    fsGroup: 65534

動作確認

上記のaws resourceを適用後に、k8s resourceをapplyすると、以下の順序で処理が行われる。

  1. serviceAccountにexternal-secrets-saをもつ、external-secretsのpodが起動する
  2. sts:AssumeRoleWithWebIdentityにより、そのpodでexternal-secrets-roleの権限が利用出来るようになる
  3. my-service-secretのExternalSecretを処理する際に、spec.roleArnを見て、my-service-roleへassume roleする
  4. my-service-roleにはparameter storeの/path/to/my/service/some/secretに対するSecureStringの権限が付与されているので値を取得出来る
  5. 取得した値を、k8s resourceのSecretとして登録する

これにより、権限の分離がrole/sa毎に行われたことで、以下のような課題が解決された

取得されたSecretは、volume mount等をして使っていけばよい。そして、k8s apiからSecretを参照出来る権限を絞るだけである。

Use volume mounts instead of environment variables

インフラもパスワードもアプリケーションも全て同じチームで扱う場合には冗長な設定となるが、このような設定を行うことで、権限分離を行いたい際の各々の責任分界点・どのresourceまで管理をするべきか、が明確となるのが良いのでは、と考える。


本記事は FOLIO Advent Calendar 2020 - Adventar として書きました。

弊社では、EKSやargoを用いた開発基盤等の導入などがすすめられています。 興味のある方は Ken Kaizu (@krrrr38) | Twitter に声をかけるか、以下のサイトから応募をお願いします。

corp.folio-sec.com