External Secrets Operator ＋ GCP Secret Manager でSecretを管理する

GitOpsを進める中でSecretの管理にExternal Secrets Operatorを採用したので導入手順を簡単に書いていきます。
※実際のコードから一部抜粋しています

IaC: Pulumi (TypeScript)
k8s構成管理: Kustomize
k8s Cluster: GKE Autopilot ※Standardの場合はクラスターの設定が別途必要に見える

https://external-secrets.io/v0.9.0/introduction/getting-started/
https://external-secrets.io/latest/provider/google-secrets-manager/


### 1. Pulumi で Workload Identity の設定
```pulumi.ts
import * as pulumi from '@pulumi/pulumi';
import * as gcp from '@pulumi/gcp';

const externalSecretsNamespaceName = 'external-secrets';
const gcpSaName = 'external-secrets';
const gcpSaEmail = `${gcpSaName}@${gcpProject}.iam.gserviceaccount.com`;
const k8sSaName = 'external-secrets';

// IAM サービス アカウントを作成
const gcpSa = new gcp.serviceaccount.Account(`${gcpSaName}-sa`, {
  accountId: gcpSaName,
  displayName: gcpSaName,
  project: gcpProject,
});

// IAM サービス アカウントにロールを付与
const gcpSaSecretManagerBinding = new gcp.projects.IAMBinding(
  `${gcpSaName}-iam-binding`,
  {
    members: [pulumi.interpolate`serviceAccount:${gcpSaEmail}`],
    role: 'roles/secretmanager.secretAccessor',
    project: gcpProject,
  },
  { dependsOn: [gcpSa] }
);

// Kubernetes サービス アカウントが IAM サービス アカウントの権限を借用できるようにする
const k8sSaAndGcpSaWorkloadIdentityUserBinding =
  new gcp.serviceaccount.IAMBinding(
    'external-secrets-workload-identity-iam-binding',
    {
      serviceAccountId: gcpSa.name,
      role: 'roles/iam.workloadIdentityUser',
      members: [
        pulumi.interpolate`serviceAccount:${gcpProject}.svc.id.goog[${externalSecretsNamespaceName}/${k8sSaName}]`,
      ],
    },
    { dependsOn: [gcpSa] }
  );
```

### 2. External Secrets Operatorをインストール
External Secrets Operator用のnamespace
```namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: external-secrets
```
今回はhelmを使います
```$ kustomize build kustomization-dir/pass --enable-helm | kubectl apply -f -```
```kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - namespace.yaml
helmCharts:
  - name: external-secrets
    repo: https://charts.external-secrets.io
    version: v0.9.0
    namespace: external-secrets
```

### 3. ServiceAccount & ClusterSecretStore
```external-secrets-operator.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets
  namespace: external-secrets
  annotations:
    iam.gke.io/gcp-service-account: example@example-project.iam.gserviceaccount.com

---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: gcp-cluster-secret-store
spec:
  provider:
    gcpsm:
      projectID: example-project
      auth:
        workloadIdentity:
          clusterLocation: asia-northeast1
          clusterName: example-cluster
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets
```

### 4. ExternalSecretでSecretを作成
あとはSecret Managerに登録してあるシークレットを使ってk8s Secretを生成するだけです
```secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: example-es
spec:
  secretStoreRef:
    kind: ClusterSecretStore
    name: gcp-cluster-secret-store
  target:
    name: example
    creationPolicy: Owner
  dataFrom:
    - extract:
        key: sm-key
```