TECHSTEP

ITインフラ関連の記事を公開してます。

External Secrets Operatorを試す

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の情報を使用します。

今回使用したファイルは以下の通りです。

※参考: ESO Doc - SecretStore

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 の情報などを指定します。

使用したファイルは以下の通りです。

※参考: ESO Doc - ExternalSecret

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を使用する場合、先ほど使用した SecretStorespec.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