CDK で External Secrets Operator をインストールし Secrets Manager のデータを Kubernetes の Secret として読めるようにする

kubernetesaws

External Secrets Operator (ESO) は外部のサービスに保存されているシークレットを Kubernetes の Secret として読めるようにする Operator で、 deprecated になった Kubernetes External Secrets (KES)後継。 今回はこれを CDK でインストールし、Secrets Manager のデータを含む Secret を作る。

まずは ServiceAccount に与える IAM Role を作成し Helm Chart でインストールする。

const externalSecretsRole = new iam.Role(this, 'ExternalSecretsRole', {
  roleName: `ExternalSecretsRole-${cluster.clusterName}`,
  assumedBy: new iam.WebIdentityPrincipal(
    cluster.openIdConnectProvider.openIdConnectProviderArn,
    {
      "StringEquals": new cdk.CfnJson(this, 'SecretStoreSecretsmanagerRoleStringEquals', { value: {
          [`${cluster.clusterOpenIdConnectIssuer}:aud`]: "sts.amazonaws.com",
          [`${cluster.clusterOpenIdConnectIssuer}:sub`]: `system:serviceaccount:external-secrets:secret-store-sa`
        }})
    }
  ),
  inlinePolicies: {
    'SecretsAccess': new iam.PolicyDocument({
      statements: [
        new iam.PolicyStatement({
          actions: [
            "secretsmanager:GetSecretValue",
          ],
          resources: [
            `arn:${cdk.Aws.PARTITION}:secretsmanager:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:secret:some-secret-*`
          ]
        })
      ]
    })
  }
})

const externalSecrets = cluster.addHelmChart('ExternalSecrets', {
  release: 'external-secrets',
  chart: 'external-secrets',
  repository: 'https://charts.external-secrets.io',
  version: '0.9.9',
  namespace: 'external-secrets',
  createNamespace: true,
  values: {
    serviceAccount: {
      name: "secret-store-sa",
      annotations: {
        "eks.amazonaws.com/role-arn": externalSecretsRole.roleArn
      }
    },
  },
  wait: true,
})

次に SecretsManager 用の SecretStore を作成する。 複数の namespace から読まれることを考えて クラスタスコープの ClusterSecretStore にしている。

const secretStoreSecretsManager = cluster.addManifest('SecretStoreSecretsmanager', {
  apiVersion: 'external-secrets.io/v1beta1',
  kind: 'ClusterSecretStore',
  metadata: {
    name: 'secretsmanager',
  },
  spec: {
    provider: {
      aws: {
        service: 'SecretsManager',
        region: 'us-east-1',
      }
    }
  }
})

secretStoreSecretsManager.node.addDependency(externalSecrets)

最後に ExternalSecret で読むシークレット名を secretKey として、key-value のデータの場合その key を property として指定する。

const someExternalSecret = cluster.addManifest('SomeExternalSecret', {
  apiVersion: 'external-secrets.io/v1beta1',
  kind: 'ExternalSecret',
  metadata: {
    name: `some-external-secret`,
  },
  spec: {
    refreshInterval: '1h',
    secretStoreRef: {
      name: 'secretsmanager',
      kind: 'ClusterSecretStore'
    },
    data: [{
      secretKey:  'kubernetes-secret-key',
      remoteRef: {
        key: secretName,
        property: 'secrets-manager-field-name' // {"secrets-manager-field-name": "xxxx"}
      }
    }]
  }
})

someExternalSecret.node.addDependency(secretStoreSecretsManager)

結果、次のような Secret が作成される。取得できなかった場合はスタックがロールバックされるため、クラスタ作成とまとめて行っていると時間がとてもかかってしまう。試す場合は別々に行った方がよい。

$ kubectl describe secret some-external-secret
Name:         some-external-secret
Namespace:    default
...
Type:  Opaque

Data
====
kubernetes-secret-key:  4 bytes

参考

EKSにおけるkubernetes-external-secretsとIRSAによる権限移譲 - decadence