column

コラム

AWS Secrets Managerを使用したKubernetes Secret管理

 

こんにちは。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で利用する(マウント/環境変数)という流れを試してみます。

基本的には公式ブログと、公式docgithubのリポジトリを参考にやっていきます。

 
 

コマンドセットアップ

kubectlやeksctl、helmをeksworkshopを参考にセットアップします。
 
 

クラスタ作成

 
まずはeksctlをつかってEKSクラスタを作成します。
 
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

 

 

 

テスト用シークレットを作成

 
AWS Secrets Mangerでシークレットを作成します。
  • 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プロバイダー確認

今やEKSを使う上で必須レベルです。後続でIRSAを利用するために必要です。
クラスタ作成時に既に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ポリシー作成

 

後々secrets用のIRSAを作成するために、まずはカスタムのIAMポリシーを作成します。
IAMポリシーは、github のREADME公式docを参考にしながら以下のような必要最小限なポリシーを定義します。
 
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においてはいわば弱点な秘匿情報の管理において、マネージドサービスの恩恵を手軽に受ける事ができるのは大きなメリットだなあと感じました。

 

是非実務でも使ってみたいと思います。