コラム
AWS Secrets Managerを使用したKubernetes Secret管理
-
TAG
AWS Secrets Manager EKS -
UPDATE
2021/08/23
こんにちは。DX事業部の宮國です。
今回は、EKSでAWS Secrets Manager and Config Provider for Secret Store CSI Driver(ASCP)を試した際の手順と結果について書いていきたいと思います。
なぜ試してみたかったか
まず、なぜこのプラグインがありがたいのか、試してみたいと思ったのかについて触れます。
アプリケーションを運用していくうえで、エンドポイント情報や認証情報等の秘匿情報をどのようにセキュアに渡すかという所は悩ましい問題の一つです。
Kubernetesにおいては、Secretsリソースを利用して管理してpod/コンテナに渡していくのが一般的です。Kubernetes の Secret リソースはパスワード、OAuthトークン、SSHキーのような機密情報を保存し、管理するためのリソースです。
しかしながら、 base64 でエンコードされているだけなので、デコードは非常に簡単です。
Secretリソースにアクセスできてしまうと、あるいは、SecretリソースのマニフェストファイルをそのままGit管理してしまうと、秘匿情報を取得する事ができてしまいます。
したがって、ArgoCD等でGitOpsを実現しているけれど、SecretsだけImperativeなアプローチで適用している。みたいな事にもなり得るわけです。少し残念ですよね。
その一方で、EKSというKubernetesのマネージドサービスを提供するAWSでは、「AWS Secrets Manager」や、「AWS Systems Manager」 のParameter Storeといったシークレットやパラメータを管理するためのマネージドサービスを提供しています。
AWSを学習してからKubernetesの学習に入った身としては、マネージドでできるものはマネージドにしたくなります。扱いが悩ましいのであれば、このKubernetesのSecretsリソースもマネージドサービスで扱いたくなります。
そんな願いをかなえてくれる手段の一つが、今年の4月に登場した、AWS Secrets Manager and Config Provider for Secret Store CSI Driver(ASCP)です。
AWS Secrets Manager and Config Provider for Secret Store CSI Driver(ASCP)は、その名の通り、CSI Driverとして実装されており、Secrets Manager に格納されたシークレットや Parameter Store に格納されたパラメータをマウントする形でpodに取り込む事ができます。
このプラグインを利用することで、Secrets Manager でシークレットを安全に保管および管理し、Kubernetes 上で動作するアプリケーションからシークレットを取得することができます。
もちろん、Kubernetes Secretを管理するためのツールは他にもあります。例えば、私は試したことが無いですが、External Secretsがあります。
Controllerをクラスタにapplyし、外部のプロバイダ(AWS Secrets Manager等)から値を取得して、Secretsリソースに反映させるような構成で、ASCPと仕組みは違いますが、やりたいことは大まかに同じように実現できそうです。
ただ、公式のお墨付き(?)がある分、これからASCPの方が採用されやすくなるかなと勝手に思っています。
ASCPの導入
今回はASCPを利用してAWS Secrets Managerに格納した秘匿情報をpodで利用する(マウント/環境変数)という流れを試してみます。
基本的には公式ブログと、公式doc、githubのリポジトリを参考にやっていきます。
コマンドセットアップ
クラスタ作成
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: secret-demo region: us-east-1 version: "1.20" availabilityZones: ["us-east-1a", "us-east-1b", "us-east-1c"] iam: withOIDC: true managedNodeGroups: - name: nodegroup-20210809 instanceType: t3.medium desiredCapacity: 2
テスト用シークレットを作成
- key:test-api-key
- value:hogehoge
aws --region us-east-1 secretsmanager create-secret --name test-api-key --secret-string 'hogehoge'
{ "ARN": "arn:aws:secretsmanager:us-east-1:<accountid>:secret:test-api-key-zPhUg5", "Name": "test-api-key", "VersionId": "a0f789a6-3e7d-4805-85ef-8f9d00ffcc43" }
OIDCプロバイダー確認
aws eks describe-cluster --name secret-demo --query "cluster.identity.oidc.issuer" --output text --region us-east-1
https://oidc.eks.us-east-1.amazonaws.com/id/HOGEHOGE
IAMポリシー作成
POLICY_ARN=$(aws --region us-east-1 --query Policy.Arn --output text iam create-policy --policy-name secret-demo-policy --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"], "Resource": ["arn:*:secretsmanager:us-east-1:<accountid>:secret:test-api-key-zPhUg5"] } ] }')
名前空間作成
ここからはEKSクラスタに対する指示です。まず、名前空間を作成します。
kubectl create ns secret-ns
$ kubectl get ns secret-ns NAME STATUS AGE secret-ns Active 20s
IRSA作成
次にpodがAWS Secrets ManagerにアクセスできるようにするためにIRSAを作成します。
この後Secretsを利用するpodがデプロイされる名前空間に対して作成する必要があります。
※IRSAについてはいつかコラムで触れたのでリンクを貼ります。(https://www.bigtreetc.com/column/eks-irsa/)
eksctl create iamserviceaccount --name secret-demo-sa --region us-east-1 --cluster secret-demo --namespace secret-ns --attach-policy-arn "$POLICY_ARN" --override-existing-serviceaccounts --approve
これによって、裏でCFnが動いて、先ほど作成したIAMポリシーを付与したIAMロールが作成されるとともに、secret-demo-saというサービスアカウントリソースがsecret-ns名前空間の中に作成されます。
サービスアカウントリソースにはアノテーションとしてIAMロールのARNが付与されます。
$ kubectl get sa -n secret-ns NAME SECRETS AGE default 1 115s secret-demo-sa 1 38s
$ kubectl describe sa secret-demo-sa -n secret-ns | grep eksctl Labels: app.kubernetes.io/managed-by=eksctl Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/eksctl-secret-demo-addon-iamserviceaccount-hogehoge
Secrets Store CSI driverのインストール
最初にSecrets Store CSI driverをインストールします。
CSIドライバーを使用すると、EKSKubernetesポッドにシークレットをマウントできます。
Secrets Store CSI driverを入れる方法については、kubectlで実施する方法もありましたが、helmの方が楽なので今回はhelmを利用します。
https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation.html
https://github.com/kubernetes-sigs/secrets-store-csi-driver/tree/master/deploy
以下の helm コマンドを実行して CSI ドライバーをインストールします。
$ helm repo add secrets-store-csi-driver https://raw.githubusercontent.com/kubernetes-sigs/secrets-store-csi-driver/master/charts "secrets-store-csi-driver" has been added to your repositories
$ helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system NAME: csi-secrets-store LAST DEPLOYED: Tue Aug 10 14:37:48 2021 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: The Secrets Store CSI Driver is getting deployed to your cluster. To verify that Secrets Store CSI Driver has started, run: kubectl --namespace=kube-system get pods -l "app=secrets-store-csi-driver" Now you can follow these steps https://secrets-store-csi-driver.sigs.k8s.io/getting-started/usage.html to create a SecretProviderClass resource, and a deployment using the SecretProviderClass.
csi-secrets-store-secrets-store-csi-driverがapplyされていることを確認します。
$ kubectl get pod -n kube-system NAME READY STATUS RESTARTS AGE aws-node-8qf89 1/1 Running 0 4h53m aws-node-ztqxl 1/1 Running 0 4h53m coredns-65bfc5645f-4ssq9 1/1 Running 0 5h5m coredns-65bfc5645f-bqtl4 1/1 Running 0 5h5m csi-secrets-store-secrets-store-csi-driver-424cz 3/3 Running 0 4m30s csi-secrets-store-secrets-store-csi-driver-rbj7r 3/3 Running 0 4m30s kube-proxy-9mcw2 1/1 Running 0 4h53m kube-proxy-bzsn4 1/1 Running 0 4h53m
SecretProviderClassがCRDとしてデプロイされていることも確認できます。
$ kubectl get crd NAME CREATED AT eniconfigs.crd.k8s.amazonaws.com 2021-08-10T00:37:05Z secretproviderclasses.secrets-store.csi.x-k8s.io 2021-08-10T05:37:45Z secretproviderclasspodstatuses.secrets-store.csi.x-k8s.io 2021-08-10T05:37:45Z securitygrouppolicies.vpcresources.k8s.aws 2021-08-10T00:37:09Z
ASCPのインストール
次にASCPをインストールします。
そもそもASCPがSecrets Store CSI driver の AWS 向けプロバイダ実装となっていることから、こういう段階的なインストール方法になっていると考えられます。
シークレットマネージャーから取得した値をCSIドライバー経由でpodにマウントするために必要です。
$ kubectl apply -f https://raw.githubusercontent.com/aws/secrets-store-csi-driver-provider-aws/main/deployment/aws-provider-installer.yaml
serviceaccount/csi-secrets-store-provider-aws created clusterrole.rbac.authorization.k8s.io/csi-secrets-store-provider-aws-cluster-role created clusterrolebinding.rbac.authorization.k8s.io/csi-secrets-store-provider-aws-cluster-rolebinding created daemonset.apps/csi-secrets-store-provider-aws created
SecretProviderClassカスタムリソースの作成
次に、secretsに関する情報と podでの表示方法を定義するためにSecretProviderClassを作成します。
SecretProviderClassは、Secretsをマウントしたいpodと同じ名前空間に作ります。今回の場合secret-ns内に作ります。
cat << EOF > SecretProviderClass.yaml apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 kind: SecretProviderClass metadata: name: nginx-deployment-aws-secrets namespace: secret-ns spec: provider: aws parameters: objects: | - objectName: "test-api-key" objectType: "secretsmanager" EOF
ちなみにobjectNameは、Secrets Managerの場合、SecretIdパラメータか、シークレットのフレンドリ名または完全なARNのいずれかになるとのことです。
kubectl apply -f SecretProviderClass.yaml secretproviderclass.secrets-store.csi.x-k8s.io/nginx-deployment-aws-secrets created
$ kubectl get secretproviderclass -n secret-ns NAME AGE nginx-deployment-aws-secrets 27s
SecretsをマウントしたPodの作成
- 名前空間がserviceaccountとSecretProviderClassと同一であること
- serviceAccountNameが先ほど作成したserviceaccountと一致すること
- secretProviderClassの名前が先ほど作成したSecretProviderClassと一致すること
を確認して、Deploymentを定義します。
cat << EOF > SecretTestDeployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: secret-ns labels: app: nginx spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: serviceAccountName: secret-demo-sa volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "nginx-deployment-aws-secrets" containers: - name: nginx-deployment image: nginx ports: - containerPort: 80 volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true EOF
$ kubectl apply -f SecretTestDeployment.yaml deployment.apps/nginx-deployment created
$ kubectl get pod -n secret-ns NAME READY STATUS RESTARTS AGE nginx-deployment-6cc9f46f96-5hzkc 1/1 Running 0 7s nginx-deployment-6cc9f46f96-rmwth 1/1 Running 0 7s
podに入ると、mountPathで指定したパスにSecretsがマウントできていることを確認しました。
$ kubectl exec -it -n secret-ns nginx-deployment-6cc9f46f96-5hzkc -- ls /mnt/secrets-store/ test-api-key
$ kubectl exec -it -n secret-ns nginx-deployment-6cc9f46f96-5hzkc -- cat /mnt/secrets-store/test-api-key hogehoge
環境変数によるSecrets利用
また、 secretObjectsを定義する事によって、AWS Secrets Manager から Kubernetes Secret にシークレットを同期させる事も可能です。
これを使って環境変数としてSecretsを渡すやり方も試したいと思います。
cat << EOF > SecretfromEnvSecretProviderClass.yaml apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 kind: SecretProviderClass metadata: name: secrets-from-env namespace: secret-ns spec: provider: aws secretObjects: #K8s Secret オブジェクトの望ましい状態を定義 - secretName: secret-from-aws # K8s secret名 type: Opaque data: - objectName: test-api-key # オブジェクト名またはオブジェクトのエイリアス key: api_key parameters: objects: | - objectName: "test-api-key" # SecretManagerの名前、ID、ARN objectType: "secretsmanager" EOF
$ kubectl apply -f SecretfromEnvSecretProviderClass.yaml secretproviderclass.secrets-store.csi.x-k8s.io/secrets-from-env created
cat << EOF > SecretfromEnvDeployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: envtest-deployment namespace: secret-ns labels: app: nginx spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: serviceAccountName: secret-demo-sa volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "secrets-from-env" containers: - name: envtest-deployment image: nginx ports: - containerPort: 80 env: - name: API_KEY valueFrom: secretKeyRef: name: secret-from-aws key: api_key volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true EOF
$ kubectl apply -f SecretfromEnvDeployment.yaml deployment.apps/envtest-deployment created
$ kubectl get pod -n secret-ns NAME READY STATUS RESTARTS AGE envtest-deployment-8c948895-6xsh8 0/1 CreateContainerConfigError 0 4s envtest-deployment-8c948895-z6k2p 0/1 CreateContainerConfigError 0 4s
Warning Failed 0s (x3 over 15s) kubelet Error: secret "secret-from-aws" not found
失敗してしまいました。githubのissueを見ると同じ悩みに直面している方がいました。
https://github.com/aws/secrets-store-csi-driver-provider-aws/issues/38
現時点でKubernetesのSecretsリソースに同期する機能はオプトイン機能だったようなので、以下のような設定でhelm upgradeしました。
helm upgrade --install csi-secrets-store \ --namespace kube-system secrets-store-csi-driver/secrets-store-csi-driver \ --set grpcSupportedProviders="aws" --set syncSecret.enabled="true"
すると以下のように環境変数でSecretsを取得できました。
$ kubectl get pod -n secret-ns $ kubectl exec -it -n secret-ns envtest-deployment-dcb45bf5c-fxncz -- env | grep API_KEY $ kubectl exec -it -n secret-ns envtest-deployment-dcb45bf5c-fxncz -- env | grep API_KEY API_KEY=hogehoge
まとめ
このように、Kubernetesで悩ましいSecretsリソースの管理について、AWS Secrets Managerで値を管理し、ASCP経由で取得する事によって解決する方法を試してみました。
- 各マニフェストファイル間のマッピングでそこそこ混乱する
- 複数の名前空間のpodでAWS Secrets Managerから値を取ってきたい時に、同じようなマニフェストファイルを各名前空間に適用していくのが少し煩雑そう
といったつらみがありそうと思いつつも、プレーンなKubernetesにおいてはいわば弱点な秘匿情報の管理において、マネージドサービスの恩恵を手軽に受ける事ができるのは大きなメリットだなあと感じました。
是非実務でも使ってみたいと思います。
-
PICK UP
ピックアップ
-
ピックアップコンテンツがありません
-
RANKING
人気の記事
-
-
1
望雲彼方に ~クラウド移行その2(インフラエンジニ…
望雲彼方に ~クラウド移行その2(インフラエンジニア編)~
2020/06/12
-
2
Infrastructure as Codeを理解…
Infrastructure as Codeを理解する(第2回)AWS Clou…
2020/11/27
-
3
マルチアカウントで構成管理情報の自動収集を行う(A…
マルチアカウントで構成管理情報の自動収集を行う(AWS)
2021/06/14
-
4
ECSを理解する(第2回)
ECSを理解する(第2回)
2020/12/02
-
5
AWSのその構成、一緒に見直してみませんか vol…
AWSのその構成、一緒に見直してみませんか vol.1
2023/01/15
-
-
ARCHIVE
アーカイブ
-
- July 2024 (1)
- January 2024 (1)
- December 2023 (2)
- June 2023 (2)
- May 2023 (1)
- April 2023 (1)
- March 2023 (2)
- February 2023 (2)
- January 2023 (1)
- December 2022 (2)
- October 2022 (2)
- September 2022 (2)