今回はKubernetes上で扱う秘匿情報を管理するExternal Secretsを試してみました。
注意事項:本記事はExternal SecretsのリポジトリがGoDaddyからexternal-secretsに移管する前に検証をしていたものです。そのため、一部マニフェストファイルにはgodaddy/kubernetes-external-secrets
と記載されております。
リポジトリ移管の経緯はGitHub Issueに記載されております。
Kubernetesで利用するSecretの課題と関連プロダクト
Kubernetesはログインパスワードなどの秘匿情報をSecretリソースで管理することが一般的な方法です。Secretリソースはbase64で暗号化した値を設定する必要があり、これはSecretリソースにアクセスできてしまえば、簡単に復号化できてしまう情報になります。
apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: username: YWRtaW4= # admin password: YWRtaW5wYXNzd29yZAo= # adminpassword
$ echo YWRtaW4= | base64 --decode admin $ echo YWRtaW5wYXNzd29yZAo= | base64 --decode adminpassword
一方でKubernetesのマニフェストファイルをGitHubで管理してGitOpsを行う場合、Secretを含むマニフェストファイルをGitHubリポジトリに保管するため、リポジトリにアクセスできるユーザーは誰でも秘匿情報を知ることができてしまいます。
このようなケースに対応するため、KubernetesのSecret情報を管理するプロダクトは複数開発されております。代表的なものとして、以下のものが挙げられます。
- kubesec: gpg / AWS KMS / Google Cloud KMSなどの鍵情報を利用して
Secret
リソースを暗号化する。kubesec
コマンドを実行して新規・既存のSecretリソースを作成することができる。 - Sealed Secret:
SealedSecret
Operatorとkubeseal
コマンド、デプロイ時にクラスター上に作成される公開鍵・秘密鍵を組み合わせる。kubeseal
コマンドによってSecretリソースを暗号化したテンプレートが用意され、そのテンプレートを利用してSealedSecret
リソースを作成することでSecret
リソースが作成される。 - External Secrets:
ExternalSecrets
Controllerを利用して、外部のサービスに保管された秘匿情報からSecretリソースを作成する。 - Berglas: Google Cloud Storage / Google Cloud KMS等を利用して秘匿情報を管理するツール。Kubernetes Secretリソースに対しては
MutatingWebhookConfiguration
を利用して、Google Cloud Secret Managerに保管した秘匿情報をDeploymentなどのリソースに渡す。
上記ツールの比較については、以下の記事を参照してください。
※参考ドキュメント:
External Secretsとは
このうちExternal Secretsは、以下のような特徴を備えたツールです。
外部の秘匿情報管理サービスから情報を取得
External Secretsは、クラウド上のサービスや、秘匿情報管理ツールなどから情報を取得し、それを用いてKubernetes上でSecret
リソースを作成します。秘匿情報の保管はKubernetes以外の外部サービスを利用することで、Kubernetes向けのマニフェストファイル内に直接データを設定する必要がなくなります。
またExternal Secretsはデフォルトで10sごとに外部サービスを確認し、外部サービスの設定が変更された場合、その変更をSecret
に反映します。そのため、Kubernetes側での設定変更は不要となります。
※任意のタイミングでSecret
を更新したい場合は、以下のリンク先を参照ください。
ExternalSecret
リソースの利用
External SecretsではExternalSecret
という独自リソースをCRDで定義し利用します。ExternalSecret
を作成すると、External Secretsの起動したPodから、指定した外部サービスにアクセスし、データを取得します。取得後はそのデータを用いてSecret
リソースを作成することで、Kubernetes上のリソースから秘匿情報を利用することが可能になります。
複数のクラウドプロバイダーに対応
External Secretsは現在以下のサービス・製品に対応しています。複数のクラウドプロバイダーに対応しているため、様々な環境に対して共通利用できることが期待できます。
- AWS Secrets Manager
- Hashicorp Vault
- Azure Key Vault
- Alibaba Cloud KMS Secret Manager
- GCP Secret Manager
※External Secrets概要図(GitHubより)
External Secretsを使ってみる
ここからは実際にExternal Secretsを利用する方法を紹介します。利用自体はとてもシンプルなので、公式のリポジトリにある手順に従って操作します。
検証環境
今回の検証は以下の環境で行いました。
- Kubernetesマネージドサービス: Amazon EKS
- 構築方法: eksctlによる構築
- ローカル環境: WSL (Ubuntu 18.04.4)
- 利用する秘匿情報保管サービス: AWS Secrets Manager
Secrets Managerへのアクセス権限を付与
External Secretsは、Controllerが起動したPodから、外部サービスにアクセスしてデータを取得します。そのため、Podが対象のサービスにアクセスするためのアクセス権限を、クラスターまたはPodに付与する必要があります。ここではEKSクラスターのData Planeに対して、Secrets ManagerへのReadWrite権限を付与します。
eksctl
コマンドを使用してEKSクラスターを作成すると、EKSを利用するために必要なIAMロールがデフォルトで付与されるので、AWSコンソール等から対象のロールに対してポリシーを追加することができます。
またEKSクラスター作成時にIAMポリシーを指定することも可能です。今回は以下のようなマニフェストファイルを指定してクラスターを作成しました。
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: eks-cluster region: ap-northeast-1 vpc: id: "vpc-xxxxxxxxxxxxxxxx" subnets: public: ap-northeast-1a: { id: subnet-xxxxxxxxxxxxxxxx } ap-northeast-1c: { id: subnet-xxxxxxxxxxxxxxxx } ap-northeast-1d: { id: subnet-xxxxxxxxxxxxxxxx } nodeGroups: - name: eks-nodegroup-1 instanceType: t2.medium desiredCapacity: 2 minSize: 2 maxSize: 5 securityGroups: attachIDs: [sg-xxxxxxxxxxxxxxxx] targetGroupARNs: - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy - arn:aws:iam::aws:policy/SecretsManagerReadWrite # 新規追加
External Secretsのデプロイ
続いてExternal Secretsをデプロイします。デプロイはKubernetesのパッケージ管理ツールであるHelmを利用します。今回はhelm template
を利用して必要なマニフェストファイルを生成し、それらを利用することでデプロイを行いました。
$ git clone https://github.com/external-secrets/kubernetes-external-secrets.git $ cd kubernetes-external-secrets $ helm template --output-dir ./testdir ./charts/kubernetes-external-secrets/ wrote ./testdir/kubernetes-external-secrets/templates/serviceaccount.yaml wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml wrote ./testdir/kubernetes-external-secrets/templates/service.yaml wrote ./testdir/kubernetes-external-secrets/templates/deployment.yaml
生成されたマニフェストファイルは以下の通りです。Helmチャートのvalues.yaml
で指定する必要のあるパラメータがRELEASE-NOTE-
と表記されており、これを適宜修正しないとデプロイに失敗します。
$ kubectl apply -f ./testdir/kubernetes-external-secrets/templates/deployment.yaml The Deployment "RELEASE-NAME-kubernetes-external-secrets" is invalid: * metadata.name: Invalid value: "RELEASE-NAME-kubernetes-external-secrets": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*') * spec.template.spec.serviceAccountName: Invalid value: "RELEASE-NAME-kubernetes-external-secrets": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
修正は手動のほか、オプションを指定することで生成時に指定することも可能です。
ただ私が試した限り--generate-name
オプションを追加してもRELEASE-NOTE-
が変更されなかったため、そちらは手動で修正しました(こちらのIssueと関連する?)
serviceaccount.yaml
--- # Source: kubernetes-external-secrets/templates/serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: RELEASE-NAME-kubernetes-external-secrets # 要修正 namespace: "default" labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: RELEASE-NAME # 要修正 app.kubernetes.io/managed-by: Helm
rbac.yaml
--- # Source: kubernetes-external-secrets/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: RELEASE-NAME-kubernetes-external-secrets # 要修正 labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: RELEASE-NAME # 要修正 app.kubernetes.io/managed-by: Helm rules: - apiGroups: [""] resources: ["secrets"] verbs: ["create", "update"] - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "watch", "list"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] resourceNames: ["externalsecrets.kubernetes-client.io"] verbs: ["get", "update"] - apiGroups: ["kubernetes-client.io"] resources: ["externalsecrets"] verbs: ["get", "watch", "list"] - apiGroups: ["kubernetes-client.io"] resources: ["externalsecrets/status"] verbs: ["get", "update"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["create"] --- # Source: kubernetes-external-secrets/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: RELEASE-NAME-kubernetes-external-secrets # 要修正 labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: RELEASE-NAME # 要修正 app.kubernetes.io/managed-by: Helm roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: RELEASE-NAME-kubernetes-external-secrets # 要修正 subjects: - name: RELEASE-NAME-kubernetes-external-secrets # 要修正 namespace: "default" kind: ServiceAccount --- # Source: kubernetes-external-secrets/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: RELEASE-NAME-kubernetes-external-secrets-auth # 要修正 labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: RELEASE-NAME # 要修正 app.kubernetes.io/managed-by: Helm roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - name: RELEASE-NAME-kubernetes-external-secrets # 要修正 namespace: "default" kind: ServiceAccount
service.yaml
--- # Source: kubernetes-external-secrets/templates/service.yaml apiVersion: v1 kind: Service metadata: name: RELEASE-NAME-kubernetes-external-secrets # 要修正 namespace: "default" labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: RELEASE-NAME # 要修正 app.kubernetes.io/managed-by: Helm spec: selector: app.kubernetes.io/name: kubernetes-external-secrets ports: - protocol: TCP port: 3001 name: prometheus targetPort: prometheus
deployment.yaml
--- # Source: kubernetes-external-secrets/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: RELEASE-NAME-kubernetes-external-secrets # 要修正 namespace: "default" labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: RELEASE-NAME # 要修正 app.kubernetes.io/managed-by: Helm spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: kubernetes-external-secrets app.kubernetes.io/instance: RELEASE-NAME # 要修正 template: metadata: labels: app.kubernetes.io/name: kubernetes-external-secrets app.kubernetes.io/instance: RELEASE-NAME # 要修正 spec: serviceAccountName: RELEASE-NAME-kubernetes-external-secrets # 要修正 containers: - name: kubernetes-external-secrets image: "godaddy/kubernetes-external-secrets:6.0.0" ports: - name: prometheus containerPort: 3001 imagePullPolicy: IfNotPresent resources: {} env: - name: "AWS_DEFAULT_REGION" value: "us-west-2" - name: "AWS_REGION" value: "us-west-2" - name: "LOG_LEVEL" value: "info" - name: "LOG_MESSAGE_KEY" value: "msg" - name: "METRICS_PORT" value: "3001" - name: "POLLER_INTERVAL_MILLISECONDS" value: "10000" - name: "VAULT_ADDR" value: "http://127.0.0.1:8200" # Params for env vars populated from k8s secrets securityContext: runAsNonRoot: true
# オプションの指定例 $ helm template --output-dir ./testdir ./charts/kubernetes-external-secrets/ --set env.AWS_REGION="ap-northeast-1" wrote ./testdir/kubernetes-external-secrets/templates/serviceaccount.yaml wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml wrote ./testdir/kubernetes-external-secrets/templates/service.yaml wrote ./testdir/kubernetes-external-secrets/templates/deployment.yaml $ cat testdir/kubernetes-external-secrets/templates/deployment.yaml | grep -3 AWS_REGION env: - name: "AWS_DEFAULT_REGION" value: "us-west-2" - name: "AWS_REGION" value: "ap-northeast-1" - name: "LOG_LEVEL" value: "info"
※参考ドキュメント:
デプロイ後は以下のようにリソースが作成されます。
# External Secretsデプロイ $ kubectl apply -f ./output_dir/kubernetes-external-secrets/templates/ deployment.apps/kubernetes-external-secrets created clusterrole.rbac.authorization.k8s.io/kubernetes-external-secrets created clusterrolebinding.rbac.authorization.k8s.io/kubernetes-external-secrets created clusterrolebinding.rbac.authorization.k8s.io/kubernetes-external-secrets-auth created service/kubernetes-external-secrets created serviceaccount/kubernetes-external-secrets created # デプロイ後の確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-external-secrets-59b8db67c5-9scxm 1/1 Running 0 44s $ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 172.20.0.1 <none> 443/TCP 35h kubernetes-external-secrets ClusterIP 172.20.177.142 <none> 3001/TCP 76s
AWS Secrets Managerへの登録
続いてAWS Secrets Managerのほうにテストデータを登録します。ここではAWS CLIからデータを登録します。
# 秘匿データの設定 $ aws secretsmanager create-secret --name hello-service/password --secret-string "5678" # 作成後の確認 $ aws secretsmanager get-secret-value --secret-id hello-service/password { "ARN": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:hello-service/password-YmCbrt", "Name": "hello-service/password", "VersionId": "a298c3ea-1e27-4e05-b379-a13856e6f8c8", "SecretString": "5678", "VersionStages": [ "AWSCURRENT" ], "CreatedDate": "2020-11-17T13:53:59.130000+09:00" }
ExternalSecret
リソースの作成
ここまででExternal Secretsを利用する準備が整ったので、ExternalSecret
リソースをデプロイします。今回は以下のマニフェストファイルを利用しました。
hello-service-es.yaml
apiVersion: kubernetes-client.io/v1 kind: ExternalSecret metadata: name: hello-service spec: backendType: secretsManager region: ap-northeast-1 data: - key: hello-service/password name: password # optional: specify a template with any additional markup you would like added to the downstream Secret resource. # This template will be deep merged without mutating any existing fields. For example: you cannot override metadata.name. template: metadata: annotations: cat: cheese labels: dog: farfel
上記ファイルを用いてリソースを作成します。以下の通り、ExternalSecret
リソースを作成することで(外部データを参照したのちに)Secretリソースが作成されます。
# ExternalSecretリソースの作成 $ kubectl apply -f hello-service-es.yaml externalsecret.kubernetes-client.io/hello-service created # 作成後の確認 $ kubectl get externalsecret NAME LAST SYNC STATUS AGE hello-service 7s SUCCESS 7s $ kubectl describe externalsecret hello-service Name: hello-service Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"kubernetes-client.io/v1","kind":"ExternalSecret","metadata":{"annotations":{},"name":"hello-service","namespace":"default"}... API Version: kubernetes-client.io/v1 Kind: ExternalSecret Metadata: Creation Timestamp: 2020-11-20T11:25:20Z Generation: 1 Resource Version: 410381 Self Link: /apis/kubernetes-client.io/v1/namespaces/default/externalsecrets/hello-service UID: 181d2db7-8162-4758-bb60-3b08c3fc4219 Spec: Backend Type: secretsManager Data: Key: hello-service/password Name: password Region: ap-northeast-1 Status: Last Sync: 2020-11-20T11:27:01.865Z Observed Generation: 1 Status: SUCCESS Events: <none> # Secretリソースが作成される $ kubectl get secret NAME TYPE DATA AGE default-token-88vp5 kubernetes.io/service-account-token 3 35h hello-service Opaque 1 15s kubernetes-external-secrets-token-v7prp kubernetes.io/service-account-token 3 14m $ kubectl get secret hello-service -o yaml apiVersion: v1 data: password: NTY3OA== # “5678” kind: Secret metadata: creationTimestamp: "2020-11-20T11:25:20Z" name: hello-service namespace: default ownerReferences: - apiVersion: kubernetes-client.io/v1 controller: true kind: ExternalSecret name: hello-service uid: 181d2db7-8162-4758-bb60-3b08c3fc4219 resourceVersion: "410070" selfLink: /api/v1/namespaces/default/secrets/hello-service uid: 4975494d-a2ce-4395-a4bb-cc79c7af8d04 type: Opaque
IAM Role for Service Account (IRSA)を利用する場合に必要な設定
ここまでの手順では、EKSクラスターのData Planeに対して、AWS Secrets ManagerへのReadWrite権限を付与していました。この設定ではExternalSecret
Controller以外のPodもSecrets Managerにアクセスすることができてしまうため、セキュリティ的に問題となる場合があります。
AWSではIAM Role for Service Account (IRSA)
という機能があります。これはKubernetes上で利用するアカウントであるService Account
に対してIAMロールを付与し、Pod起動時に特定のService Accountを指定することで、Pod単位での権限付与を実現する機能です。
AWS IAMは外部IDプロバイダーと連携することで、外部IDに対してAWSリソースへのアクセス権を付与することができます。IRSAでは、EKS上でのOpenID Connect(OIDC)プロバイダーを作成・有効化し、これを利用します。
一方KubernetesではVolume
リソースのprojected
プラグインを利用して、Service Account Tokenを利用することができます。projected
VolumeはKubernetes Volumeリソースで利用できるプラグインの種類の一つで、Secret / ConfigMap / downwardAPI / Service Accoutn Tokenの4種類のリソースを1か所のディレクトリに集約してマウントする機能を提供します。このトークンにAWSへのアクセス権を付与することで、特定のService AccountがAWSリソースへアクセスすることを許可します。
Amazon EKS上でExternal Secretsを利用する際も、このIRSAを利用することで、必要最小限の権限付与に抑え、セキュリティを向上することが期待できます。
External Secretsを利用する場合、ExternalSecrets
Controller Podを作成すると、Amazon EKS Pod Identity Webhookを介して、Podに必要な環境変数を追加します。この環境変数にはIAMロールのARNとトークンの情報が含まれ、Podに対してprojected
プラグインのVolumeを設定します。
次にExternalSecret
リソースを作成すると、PodがAWSリソースにアクセスしようとし、OIDCプロバイダーに対するトークンの検証をSTS経由で行います。認証に成功すればアクセス権を付与(ここではAWS Secrets Managerへのアクセス)し、AWS Secrets Managerに保管された秘匿データを取得、Secretリソースの作成を行うことができます。
※External SecretsをIRSAで利用する場合の簡略図。図中の数字は処理のステップ順を表す。
IRSAを利用するには、以下のような操作が必要となります。なお、以降の作業は、AWS Secrets Managerへのアクセスを許可するIAMロールが無い状況を想定していますので、必要があればEKSクラスターから指定のロールの削除を実行します。
OpenID Connectプロバイダーの有効化
IRSAを利用するには、まずクラスター上でOpenID Connect (OIDC) IDプロバイダーを有効にする必要があります。IDプロバイダーを有効にするにはeksctl utils associate-iam-oidc-provider
コマンドを実行します。
$ eksctl utils associate-iam-oidc-provider --name eks-cluster --approve Flag --name has been deprecated, use --cluster [ℹ] eksctl version 0.29.2 [ℹ] using region ap-northeast-1 [ℹ] will create IAM Open ID Connect provider for cluster "eks-cluster" in "ap-northeast-1" [✔] created IAM Open ID Connect provider for cluster "eks-cluster" in "ap-northeast-1" # 有効化後の確認 $ aws iam list-open-id-connect-providers { "OpenIDConnectProviderList": [ { "Arn": "arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111" } ] }
IAMロールの作成
次にService Accountに付与するIAMロールを用意します。今回はAWS Secrets Managerへアクセスするために、以下のようなCloudFormationテンプレートを利用してIAMロールを作成します。
AWSTemplateFormatVersion: 2010-09-09 Resources: eksirsa: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Federated: - arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111 Action: - 'sts:AssumeRoleWithWebIdentity' Condition: StringEquals: oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:sub: system:serviceaccount:default:kubernetes-external-secrets oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:aud: sts.amazonaws.com Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/SecretsManagerReadWrite RoleName: eks-irsa Tags: - Key: alpha.eksctl.io/cluster-name Value: eks-cluster - Key: eksctl.cluster.k8s.io/v1alpha1/cluster-name Value: eks-cluster - Key: alpha.eksctl.io/iamserviceaccount-name Value: default/kubernetes-external-secrets - Key: alpha.eksctl.io/eksctl-version Value: 0.29.2
# エラーが発生する場合は”--capabilities CAPABILITY_NAMED_IAM”を追加 $ aws cloudformation deploy --stack-name eks-irsa --template aws-iam-irsa.yaml --capabilities CAPABILITY_NAMED_IAM Waiting for changeset to be created.. Waiting for stack create/update to complete Successfully created/updated stack - eks-irsa # 作成後の確認 $ aws iam get-role --role-name eks-irsa { "Role": { "Path": "/", "RoleName": "eks-irsa", "RoleId": "AROATYRF5FWUEKGK4AIKJ", "Arn": "arn:aws:iam::111111111111:role/eks-irsa", "CreateDate": "2020-12-12T23:05:21+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:aud": "sts.amazonaws.com", "oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:sub": "system:serviceaccount:default:kubernetes-external-secrets" } } } ] }, "Description": "", "MaxSessionDuration": 3600, "Tags": [ { "Key": "alpha.eksctl.io/cluster-name", "Value": "eks-cluster" }, { "Key": "eksctl.cluster.k8s.io/v1alpha1/cluster-name", "Value": "eks-cluster" }, { "Key": "alpha.eksctl.io/iamserviceaccount-name", "Value": "default/kubernetes-external-secrets" }, { "Key": "alpha.eksctl.io/eksctl-version", "Value": "0.29.2" } ], "RoleLastUsed": {} } }
Service Accountに設定を追加
次に、作成したIAMロールをService Accountで利用するよう、設定を変更します。
--- # Source: kubernetes-external-secrets/templates/serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: kubernetes-external-secrets namespace: "default" labels: app.kubernetes.io/name: kubernetes-external-secrets helm.sh/chart: kubernetes-external-secrets-6.0.0 app.kubernetes.io/instance: 6.0.0 app.kubernetes.io/managed-by: Helm # 以降の記述を追加 annotations: eks.amazonaws.com/role-arn: arn:aws:iam::111111111111:role/eks-irsa
# Service Account更新 $ kubectl apply -f output_dir/kubernetes-external-secrets/templates/serviceaccount.yaml serviceaccount/kubernetes-external-secrets configured # 更新後はAnnotationsが付与されている $ kubectl get sa NAME SECRETS AGE default 1 6d22h kubernetes-external-secrets 1 6d21h $ kubectl describe sa kubernetes-external-secrets Name: kubernetes-external-secrets Namespace: default Labels: app.kubernetes.io/instance=6.0.0 app.kubernetes.io/managed-by=Helm app.kubernetes.io/name=kubernetes-external-secrets helm.sh/chart=kubernetes-external-secrets-6.0.0 Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::111111111111:role/eks-irsa kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{"eks.amazonaws.com/role-arn":"arn:aws:iam::111111111111:role/eks-irs... Image pull secrets: <none> Mountable secrets: kubernetes-external-secrets-token-wggsc Tokens: kubernetes-external-secrets-token-wggsc Events: <none>
Deploymentリソースの設定変更
これでIAMロールを付与したService Accountを利用できるようになったので、Deploymentなどのリソースを作成します。
Deploymentリソース作成後に内容を確認すると、aws-iam-token
という名称のProjected Volumeが付与された様子が確認できます。
# Deployment再作成 $ kubectl apply -f output_dir/kubernetes-external-secrets/templates/deployment.yaml deployment.apps/kubernetes-external-secrets configured # 作成後の確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-external-secrets-59859579b6-xqkg4 1/1 Running 0 7s kubernetes-external-secrets-59b8db67c5-r74k8 0/1 Terminating 0 6d21h $ kubectl describe pod kubernetes-external-secrets-59859579b6-xqkg4 Name: kubernetes-external-secrets-59859579b6-xqkg4 Namespace: default Priority: 0 Node: ip-10-0-3-64.ap-northeast-1.compute.internal/10.0.3.64 Start Time: Sun, 13 Dec 2020 08:10:52 +0900 Labels: app.kubernetes.io/instance=6.0.0 app.kubernetes.io/name=kubernetes-external-secrets pod-template-hash=59859579b6 Annotations: kubernetes.io/psp: eks.privileged Status: Running IP: 10.0.3.247 IPs: IP: 10.0.3.247 Controlled By: ReplicaSet/kubernetes-external-secrets-59859579b6 Containers: kubernetes-external-secrets: Container ID: docker://f6813c88c87e065be47fa29b1de1eb2434928eeb30f866137034212832d8bdf2 Image: godaddy/kubernetes-external-secrets:6.0.0 Image ID: docker-pullable://godaddy/kubernetes-external-secrets@sha256:e827f7bc272117a97a8562ac70e37810266467f07d2fce798c8abc360b892c08 Port: 3001/TCP Host Port: 0/TCP State: Running Started: Sun, 13 Dec 2020 08:10:53 +0900 Ready: True Restart Count: 0 Environment: AWS_DEFAULT_REGION: ap-northeast-1 AWS_REGION: ap-northeast-1 LOG_LEVEL: info LOG_MESSAGE_KEY: msg METRICS_PORT: 3001 POLLER_INTERVAL_MILLISECONDS: 10000 VAULT_ADDR: http://127.0.0.1:8200 AWS_ROLE_ARN: arn:aws:iam::111111111111:role/eks-irsa AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token Mounts: /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro) /var/run/secrets/kubernetes.io/serviceaccount from kubernetes-external-secrets-token-wggsc (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: aws-iam-token: # 追加されている Type: Projected (a volume that contains injected data from multiple sources) TokenExpirationSeconds: 86400 kubernetes-external-secrets-token-wggsc: Type: Secret (a volume populated by a Secret) SecretName: kubernetes-external-secrets-token-wggsc Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 3s default-scheduler Successfully assigned default/kubernetes-external-secrets-59859579b6-xqkg4 to ip-10-0-3-64.ap-northeast-1.compute.internal Normal Pulled 2s kubelet, ip-10-0-3-64.ap-northeast-1.compute.internal Container image "godaddy/kubernetes-external-secrets:6.0.0" already present on machine Normal Created 2s kubelet, ip-10-0-3-64.ap-northeast-1.compute.internal Created container kubernetes-external-secrets Normal Started 2s kubelet, ip-10-0-3-64.ap-northeast-1.compute.internal Started container kubernetes-external-secrets
これでExternalSecret
リソースが作成できるかと思いましたが、この状態でリソースを作成しようとすると、以下のようなエラーが発生し、Secrets Managerからデータを取得することができませんでした。
$ kubectl apply -f hello-service-es.yaml
externalsecret.kubernetes-client.io/hello-service created
$ kubectl get externalsecret
NAME LAST SYNC STATUS
AGE
hello-service 5s ERROR, User: arn:aws:sts::111111111111:assumed-role/eksctl-eks-cluster-nodegroup-eks-NodeInstanceRole-1R5YXUU3GJMP5/i-09defe966bee6cb58 is not authorized
to perform: secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:hello-service/password-YmCbrt 6s
エラーメッセージを見る限り、EKSクラスターのNodeGroupに対して付与されるIAMロールを利用しようとしているように見えます。ExternalSecret
リソースの定義では、利用するARNを指定することもできるため、こちらを変更してみます。
apiVersion: kubernetes-client.io/v1 kind: ExternalSecret metadata: name: hello-service spec: backendType: secretsManager # optional: specify role to assume when retrieving the data roleArn: arn:aws:iam::111111111111:role/eks-irsa region: ap-northeast-1 data: - key: hello-service/password name: password # optional: specify a template with any additional markup you would like added to the downstream Secret resource. # This template will be deep merged without mutating any existing fields. For example: you cannot override metadata.name. template: metadata: annotations: cat: cheese labels: dog: farfel
上記ファイルを用いてリソースを作成すると、メッセージは変更されたものの、やはりエラーメッセージが確認できます。
# 作成済みのリソースを一度削除する $ kubectl delete -f hello-service-es.yaml externalsecret.kubernetes-client.io "hello-service" deleted # 再作成 $ kubectl apply -f hello-service-es.yaml externalsecret.kubernetes-client.io/hello-service created # 作成後の確認 $ kubectl get externalsecret NAME LAST SYNC STATUS AGE hello-service 4s ERROR, Missing credentials in config 4s
いろいろと調べてみたところ、External SecretsでIRSAを利用するには、DeploymentのSecurity Cntextの設定を変更し、fsGroupを以下のように変更する必要があるようです。
(中略) securityContext: runAsNonRoot: true fsGroup: 65534
こちらの情報は、現在一緒にお仕事をさせて頂いている方から教えて頂きました。この場を借りて感謝申し上げます。
※参考リンク:
上記設定を追加したところ、無事にデータを取得し、Secret
リソースが作成されることを確認できました。
# 設定変更後、Controllerを再作成 $ kubectl apply -f output_dir/kubernetes-external-secrets/templates/deployment.yaml deployment.apps/kubernetes-external-secrets created # 作成後の確認 $ kubectl get deployment kubernetes-external-secrets -o yaml (中略) dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: fsGroup: 65534 ★ runAsNonRoot: true serviceAccount: kubernetes-external-secrets serviceAccountName: kubernetes-external-secrets terminationGracePeriodSeconds: 30 (中略) # ExternalSecretリソースの作成 $ kubectl apply -f hello-service-es.yaml externalsecret.kubernetes-client.io/hello-service created # 作成後の確認 $ kubectl get externalsecret NAME LAST SYNC STATUS AGE hello-service 5s SUCCESS 8s $ kubectl get secret NAME TYPE DATA AGE default-token-875qc kubernetes.io/service-account-token 3 6d22h hello-service Opaque 1 14s kubernetes-external-secrets-token-wggsc kubernetes.io/service-account-token 3 6d21h $ kubectl get secret hello-service -o yaml apiVersion: v1 data: password: NTY3OA== # “5678” kind: Secret metadata: creationTimestamp: "2020-12-12T23:27:03Z" name: hello-service namespace: default ownerReferences: - apiVersion: kubernetes-client.io/v1 controller: true kind: ExternalSecret name: hello-service uid: 44e6b1f7-3c91-4a7c-b316-a2cec040a114 resourceVersion: "1762644" selfLink: /api/v1/namespaces/default/secrets/hello-service uid: 34b88216-3814-4d32-873c-f10103504d75 type: Opaque