これは何
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が出来てしまうこともある。
この記事では現時点では良いと考えられる権限移譲の方法について記述する。
事前知識
- EKS: AWS managed kubernetes
- kubernetes-external-secrets
- IRSA: sts:AssumeRoleWithWebIdentity により、k8sのServiceAccountに対してIAM Roleの権限を利用させるもの
- Kubernetes サービスアカウントに対するきめ細やかな IAM ロール割り当ての紹介 | Amazon Web Services ブログ
- 1年以上前に出た機能ではあるが、この機能が出る前まではEKSではinstance profileしか利用出来なかったためpodに対するIAMの付与を雑にしか行えなかった
権限移譲
先に答えから述べると、現状では以下のような実装を行うのが望ましい。
- サービス毎に専用のroleを作成する(service-role)
- サービスのパスワードは、そのサービス毎/パスワード毎のkms keyを用意し、service-roleでdecrypt出来る権限を付与する
- kubernetes-external-secretのpodに対してservice account(external-secret-sa)を付与する
- external-secret-saがIRSA出来るようなrole(external-secret-role)を作成する
- external-secret-roleからservice-roleへassume role出来る信頼関係を付与する
- 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
- kubernetes-external-secrets側の設定
ここまでの設定を行えば、あとは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すると、以下の順序で処理が行われる。
- serviceAccountにexternal-secrets-saをもつ、external-secretsのpodが起動する
- sts:AssumeRoleWithWebIdentityにより、そのpodでexternal-secrets-roleの権限が利用出来るようになる
- my-service-secretのExternalSecretを処理する際に、spec.roleArnを見て、my-service-roleへassume roleする
- my-service-roleにはparameter storeの/path/to/my/service/some/secretに対するSecureStringの権限が付与されているので値を取得出来る
- 取得した値を、k8s resourceのSecretとして登録する
これにより、権限の分離がrole/sa毎に行われたことで、以下のような課題が解決された
- assume role等をしない場合には、external-secrets-roleがなぜかmy-serviceのパスワードを見れる権限をそのまま持たせてしまうことがある
- IRSAをしない場合には、EKS worker nodeのinstance profile自体に権限を付与してしまう
- そもそもIDMSv2に制限して、put hop limitを1に下げて、instance profile自体を使わせないようにしよう
- aws-eks-best-practices/iam.md at 9b0dcd7c04a5bd18d66bfdc3929bbdbf312ddb80 · aws/aws-eks-best-practices · GitHub
取得されたSecretは、volume mount等をして使っていけばよい。そして、k8s apiからSecretを参照出来る権限を絞るだけである。
インフラもパスワードもアプリケーションも全て同じチームで扱う場合には冗長な設定となるが、このような設定を行うことで、権限分離を行いたい際の各々の責任分界点・どのresourceまで管理をするべきか、が明確となるのが良いのでは、と考える。
本記事は FOLIO Advent Calendar 2020 - Adventar として書きました。
弊社では、EKSやargoを用いた開発基盤等の導入などがすすめられています。 興味のある方は Ken Kaizu (@krrrr38) | Twitter に声をかけるか、以下のサイトから応募をお願いします。