External Secrets Operatorとは
External Secrets Operator (以降ESO) は、外部の秘匿情報管理サービスと連携するKubernetes Operatorで、外部サービスから取得した秘匿情報からKubernetes Secretリソースを作成・更新します。これにより、Secretリソースのマニフェストファイルを、GitHubなどのソースコード管理ツール上で直接管理せずに扱うことで、秘匿情報をセキュアに管理することが可能となります。
※図: ESO Docより
ESOと同様の機能を提供するものとして Kubernetes External Secrets (以降KES) がありましたが、メンテナの不在や技術的負債の解消の難しさなどからdeprecatedされ、現在はESOの利用が推奨されています。
ESOはKESと異なりGo言語で書かれていますが、それ以外に複数のCustom Resourceを組み合わせて機能を実現する、という点で大きく異なります。
利用する主なCustom Resourceは以下の2つです。
SecretStore
: 利用する秘匿情報管理サービスやアクセス方法を指定するExternalSecret
: 取得する対象のデータや生成するSecretなどを指定する
これ以外にも、Cluster-scopeなリソースである ClusterSecretStore
ClusterExternalSecret
なども存在します。
なお、Kubernetes Secretの課題感などは以前の投稿にも記載しているので、ここでは割愛します。
External Secrets Operatorを動かす
ここからESOを使ってみます。今回は ESOのドキュメントに記載のある方法を Amazon EKS
上で行います。また外部の秘匿情報管理サービスには AWS Secrets Manager
を使用しました。
$ kubectl version Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.0", GitCommit:"c2b5237ccd9c0f1d600d3072634ca66cefdf272f", GitTreeState:"clean", BuildDate:"2021-08-04T18:03:20Z", GoVersion:"go1.16.6", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"22+", GitVersion:"v1.22.16-eks-ffeb93d", GitCommit:"52e500d139bdef42fbc4540c357f0565c7867a81", GitTreeState:"clean", BuildDate:"2022-11-29T18:41:42Z", GoVersion:"go1.16.15", Compiler:"gc", Platform:"linux/amd64"}
AWS Secrets Managerに秘匿情報を登録する
ESOを使う前に、取得する秘匿情報を登録しておきます。今回はAWSマネジメントコンソールから、以下のような値を追加しました。
$ aws secretsmanager get-secret-value --secret-id test-secret { "ARN": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:test-secret-Rn6ufG", "Name": "test-secret", "VersionId": "93ed3aed-6048-41da-afec-48da94566aec", "SecretString": "{\"password\":\"1234\"}", # 使用する秘匿情報 "VersionStages": [ "AWSCURRENT" ], "CreatedDate": "2023-01-07T20:54:39.190000+09:00" }
ESOをインストールする
次にESOをインストールします。ESOにはHelm Chartが用意されているのでこちらを使います。ESOが使用するリソースなどを知りたかったので、今回は helm template
コマンドでマニフェストファイルを出力し、そちらを使いました。
# Helmリポジトリの追加 $ helm repo add external-secrets https://charts.external-secrets.io # (既に追加されている場合)Helmリポジトリの更新 $ helm repo list NAME URL external-secrets https://charts.external-secrets.io $ helm repo update external-secrets # テンプレートの生成 $ helm template external-secrets external-secrets/external-secrets --output-dir .
なお、生成されたファイルは以下のようになります。
helm template
によって生成されたファイル一覧
$ tree external-secrets/templates/ external-secrets/templates/ ├── cert-controller-deployment.yaml ├── cert-controller-rbac.yaml ├── cert-controller-serviceaccount.yaml ├── crds │ ├── acraccesstoken.yaml │ ├── clusterexternalsecret.yaml │ ├── clustersecretstore.yaml │ ├── ecrauthorizationtoken.yaml │ ├── externalsecret.yaml │ ├── fake.yaml │ ├── gcraccesstoken.yaml │ ├── password.yaml │ ├── pushsecret.yaml │ └── secretstore.yaml ├── deployment.yaml ├── rbac.yaml ├── serviceaccount.yaml ├── validatingwebhook.yaml ├── webhook-deployment.yaml ├── webhook-secret.yaml ├── webhook-service.yaml └── webhook-serviceaccount.yaml
生成されたファイルを使い、インストールします。
# ESOのインストール $ kubectl apply -f external-secrets/templates/ $ kubectl apply -f external-secrets/templates/crds/ # Podの起動を確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE external-secrets-66dbbc7b45-rdgxw 1/1 Running 0 113s external-secrets-cert-controller-6c95b96976-fj9zt 1/1 Running 0 113s external-secrets-webhook-647478f6dc-zdftz 1/1 Running 0 113s
AWSアクセス情報を登録する
ESOがSecretを作成するには、AWS Secrets Manager
にアクセスし、秘匿情報を読み取る必要があります。ESOがAWSにアクセスする方法は2つ用意されていますが、ここではAWSへのアクセス情報を記載したSecretリソースを用意します (2つ目の方法も後述します)。
awssm-secret.yaml
apiVersion: v1 kind: Secret metadata: name: awssm-secret type: Opaque stringData: access-key: XXXXXXXXXXXXXXXXXXXX secret-access-key: xxxxxxxxxxxxxxxxxxxx
上記ファイルをデプロイします。
$ kubectl apply -f awssm-secret.yaml $ kubectl describe secret awssm-secret Name: awssm-secret Namespace: default Labels: <none> Annotations: <none> Type: Opaque Data ==== access-key: 20 bytes secret-access-key: 40 bytes
SecretStoreのデプロイ
次に SecretStore
を作成します。SecretStore
は前述の通り、利用する秘匿情報管理サービスやアクセス方法を指定するリソースです。今回は AWS Secrets Manager
へのアクセス方法を指定しますが、先ほど作成した awssm-secret
Secretの情報を使用します。
今回使用したファイルは以下の通りです。
secretstore-sample.yaml
apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: secretstore-sample spec: provider: # 使用するサービスのプロバイダーを指定。同時に1つまで指定可能 aws: service: SecretsManager region: ap-northeast-1 auth: secretRef: # AWSへのアクセスで使用する認証情報を指定。ここでは Kubernetes Secretを使用 accessKeyIDSecretRef: # AWSアクセスキーを登録したSecretとキーを指定 name: awssm-secret key: access-key secretAccessKeySecretRef: # AWSシークレットアクセスキーを登録したSecretとキーを指定 name: awssm-secret key: secret-access-key
上記ファイルをデプロイします。設定などに問題がなければ、リソースのステータスは Valid
となるはずです。
$ kubectl apply -f secretstore-sample.yaml
secretstore.external-secrets.io/secretstore-sample created
$ kubectl get secretstore
NAME AGE STATUS CAPABILITIES READY
secretstore-sample 10s Valid ReadWrite True
ExternalSecretのデプロイ
次に ExternalSecret
を作成します。 ExternalSecret
は取得する対象のデータや生成するSecretなどを指定するリソースで、AWS Secrets Manager
上のkey/valueの情報や、前項で作成した SecretStore
の情報などを指定します。
使用したファイルは以下の通りです。
externalsecret-sample.yaml
apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: example spec: refreshInterval: 5m # SecretStoreプロバイダーから再度値を読み込む時間を指定 secretStoreRef: # 参照する SecretStore を指定 name: secretstore-sample kind: SecretStore target: # ExternalSecret から作成されるKubernetes Secretを指定する name: secret-to-be-created creationPolicy: Owner data: # 取得する秘匿情報を指定する - secretKey: secret-sample # 作成するSecretのkeyを指定 remoteRef: key: test-secret # AWS Secrets Managerに登録したシークレット名 property: password # シークレットに登録したキーを指定
上記ファイルをデプロイします。AWS Secrets Manager
から秘匿情報を取れていれば、 ExternalSecret
リソースは SecretSynced
というステータスになり、Secretリソースが作成されます。
# ExternalSecretの作成 $ kubectl apply -f externalsecret-sample.yaml externalsecret.external-secrets.io/example created $ kubectl get externalsecret NAME STORE REFRESH INTERVAL STATUS READY example secretstore-sample 5m SecretSynced True # Secretの確認 $ kubectl get secret NAME TYPE DATA AGE awssm-secret Opaque 2 116s default-token-hxtb6 kubernetes.io/service-account-token 3 22m external-secrets-cert-controller-token-k6ttv kubernetes.io/service-account-token 3 4m24s external-secrets-token-k4v7g kubernetes.io/service-account-token 3 4m24s external-secrets-webhook Opaque 4 4m24s external-secrets-webhook-token-cn4mm kubernetes.io/service-account-token 3 4m24s secret-to-be-created Opaque 1 17s # 新規作成されたSecret $ kubectl get secret secret-to-be-created -oyaml apiVersion: v1 data: secret-sample: MTIzNA== (以降割愛) $ echo "MTIzNA==" | base64 --decode 1234
その他
EKS: IRSAを使用する場合
EKSの場合、IAM Role for Service Account (IRSA)
を使用し、AWSリソースにアクセスする権限をPodだけに持たせることができます。ESOはIRSAにも対応しており、AWSアクセスキーを使用するよりもセキュアに運用することが可能になります。
ここからIRSAを使用する場合も試してみます。IRSAを使用するため、まずは以下のようなJSONファイルを使用してIAMポリシーを作成します。
iam-policy.json
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret" ], "Resource": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:*" } ] }
上記JSONファイルを使用してIAMポリシーを作成します。
# IAM policyの作成 $ aws iam create-policy --policy-name eso-policy --policy-document file://iam-policy.json
続いて eksctl
コマンドを使用し、IAM OIDCプロバイダーの作成と、IAMポリシーに紐づいたService Accountの作成を行います。
# OIDCプロバイダーの作成 $ eksctl get clusters NAME REGION EKSCTL CREATED eks-cluster ap-northeast-1 True $ eksctl utils associate-iam-oidc-provider \ --cluster eks-cluster \ --approve # Service Accountの作成 $ eksctl create iamserviceaccount \ --name eso-irsa \ --namespace default \ --cluster eks-cluster \ --role-name "eso-role" \ --attach-policy-arn arn:aws:iam::111111111111:policy/eso-policy \ --approve # Service Accountの確認 $ kubectl get sa NAME SECRETS AGE default 1 23m eso-irsa 1 13s # 作成したService Account external-secrets 1 5m27s external-secrets-cert-controller 1 5m27s external-secrets-webhook 1 5m27s $ kubectl describe sa eso-irsa Name: eso-irsa Namespace: default Labels: app.kubernetes.io/managed-by=eksctl Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::111111111111:role/eso-role Image pull secrets: <none> Mountable secrets: eso-irsa-token-mp7kf Tokens: eso-irsa-token-mp7kf Events: <none>
IRSAを使用する場合、先ほど使用した SecretStore
の spec.provider.aws.auth
を修正する必要があります。eksctl
コマンドで新しく作成したService Accountを指定することで、IRSA経由でAWS Secrets Managerにアクセスできるようになります。
ここで使用した SecretStore
は以下の通りです。
※参考: ESO Doc - AWS Secrets Manager
secretstore-sample-irsa.yaml
apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: secretstore-sample spec: provider: aws: service: SecretsManager region: ap-northeast-1 auth: # 以降を修正 jwt: serviceAccountRef: name: eso-irsa
こちらのファイルを使用し、リソースを作成します。なお、デプロイ前にはクラスターにある SecretStore
ExternalSecret
をあらかじめ削除しておきました。
$ kubectl apply -f secretstore-sample-irsa.yaml
secretstore.external-secrets.io/secretstore-sample created
$ kubectl get secretstore
NAME AGE STATUS CAPABILITIES READY
secretstore-sample 8s Valid ReadWrite True
あとは、すでに作成していた ExternalSecret
用のファイルをデプロイすれば、先ほどと同様にSecrets Managerから値を取得し、Secretリソースを作成することが確認できます。
$ kubectl apply -f externalsecret-sample.yaml externalsecret.external-secrets.io/example created $ kubectl get externalsecret NAME STORE REFRESH INTERVAL STATUS READY example secretstore-sample 5m SecretSynced True $ kubectl get secret NAME TYPE DATA AGE default-token-dpgft kubernetes.io/service-account-token 3 27m eso-irsa-token-mp7kf kubernetes.io/service-account-token 3 4m external-secrets-cert-controller-token-z7pqx kubernetes.io/service-account-token 3 9m14s external-secrets-token-572s4 kubernetes.io/service-account-token 3 9m14s external-secrets-webhook Opaque 4 9m14s external-secrets-webhook-token-7v49c kubernetes.io/service-account-token 3 9m13s secret-to-be-created Opaque 1 23s