external-secretsのPushSecretを使ってKubernetesのSecretをAWS Secrets Managerに同期する

Kubernetesで機密情報を管理する際、[external-secrets](https://external-secrets.io/)というオペレータを使用することで、AWS Secrets Managerなどの外部シークレットプロバイダーと連携できます。

一般的な使用方法では、AWS Secrets Managerに保存された機密情報をKubernetesのSecretとして同期します。一方で、今回紹介する`PushSecret`を使うと、逆方向の同期、つまりKubernetesのSecretをAWS Secrets Managerに同期することができます。

この記事では、`PushSecret`の基本的な使い方を紹介します。

## 前提条件

- Kubernetes クラスター
- AWS アカウント
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
- [Helm](https://helm.sh/)
- [AWS CLI](https://aws.amazon.com/cli/)

## external-secretsのインストール

まず、external-secretsをHelmを使用してインストールします：

```bash
helm repo add external-secrets https://charts.external-secrets.io

helm install external-secrets \
   external-secrets/external-secrets \
    -n external-secrets \
    --create-namespace
```

## AWS認証情報の設定

AWSの認証情報を環境変数に設定し、正しく設定されているか確認します：

```bash
export AWS_ACCESS_KEY_ID=xxxx
export AWS_SECRET_ACCESS_KEY=xxxx
export AWS_REGION=ap-northeast-1

# 認証情報の確認
aws sts get-caller-identity
```

次に、この認証情報をKubernetesのSecretとして保存します：

```bash
kubectl create secret generic aws-credentials \
  --from-literal=access-key-id=${AWS_ACCESS_KEY_ID} \
  --from-literal=secret-access-key=${AWS_SECRET_ACCESS_KEY}
```

## SecretStoreの設定

AWS Secrets Managerに接続するための`SecretStore`を作成します：

```yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secretsmanager
  namespace: default
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key-id
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-access-key
```

```bash
kubectl apply -f secret-store.yaml
```

## PushSecretの設定

KubernetesのSecretをAWS Secrets Managerに同期するための`PushSecret`を作成します：

```yaml
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: pushsecret-example
  namespace: default
spec:
  # 同期時にプロバイダーの既存のシークレットを上書き
  updatePolicy: Replace
  # PushSecret削除時にプロバイダーのシークレットも削除
  deletionPolicy: Delete
  # 再同期の間隔
  refreshInterval: 10s
  # シークレットをプッシュするSecretStore
  secretStoreRefs:
    - name: aws-secretsmanager
      kind: SecretStore
  # 同期対象のSecret
  selector:
    secret:
      name: my-secret
  # 同期するキーの設定
  data:
    - match:
        secretKey: foo  # Secretのキー
        remoteRef:
          remoteKey: my-secret-foo  # AWS Secrets Managerのシークレット名
      metadata:
        secretPushFormat: string
```

```bash
kubectl apply -f push-secret.yaml
```

## 動作確認

### 1. Secretの作成

まず、同期対象のSecretを作成します：

```bash
kubectl create secret generic my-secret \
  --from-literal=foo=bar
```

AWS Secrets Managerに同期されたことを確認します：

```bash
aws secretsmanager get-secret-value \
  --secret-id my-secret-foo
```

次のようなレスポンスが返ってくれば成功です：

```json
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:my-secret-foo-rUBCkr",
    "Name": "my-secret-foo",
    "VersionId": "00000000-0000-0000-0000-000000000001",
    "SecretString": "bar",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "2024-11-14T09:50:34.787000+09:00"
}
```

### 2. Secretの更新

Secretの値を更新してみます：

```bash
kubectl create secret generic my-secret \
  --from-literal=foo=baz \
  --dry-run=client -o yaml | kubectl apply -f -
```

AWS Secrets Managerで値が更新されたことを確認します：

```bash
aws secretsmanager get-secret-value \
  --secret-id my-secret-foo
```

`SecretString`が`bar`から`baz`に変更されていることが確認できます：

```json
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:my-secret-foo-rUBCkr",
    "Name": "my-secret-foo",
    "VersionId": "00000000-0000-0000-0000-000000000002",
    "SecretString": "baz",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "2024-11-14T10:03:41.913000+09:00"
}
```

### 3. 削除時の動作確認

Secretを削除してみます：

```bash
kubectl delete secret my-secret
```

この時点では、AWS Secrets Managerのシークレットは削除されません。

次に、PushSecretを削除します：

```bash
kubectl delete pushsecret pushsecret-example
```

この操作により、AWS Secrets Managerのシークレットも削除されます：

```bash
aws secretsmanager list-secrets
```

```json
{
    "SecretList": []
}
```

## クリーンアップ

external-secretsの`PushSecret`を削除しただけでは、`my-secret-foo`はAWS Secrets Managerに「削除予定」として残っているので、AWS Secrets Managerのシークレットの即時削除を実施します：

```bash
aws secretsmanager delete-secret \
  --secret-id my-secret-foo \
  --force-delete-without-recovery
```

external-secretsの削除：

```bash
helm uninstall external-secrets -n external-secrets
```

AWS認証情報の削除：

```bash
kubectl delete secret aws-credentials
```

## まとめ

external-secretsの`PushSecret`を使うことで、KubernetesのSecretをAWS Secrets Managerに同期できることを見てきました。この機能は以下のような用途に活用できると思います。

- Kubernetes上で管理している機密情報を、他のAWSサービスと共有する
- AWS Secrets Manager経由によるKubernetesクラスター間でのSecret共有
- KubernetesのSecretのバックアップをAWS Secrets Managerに保存する

今回は、external-secrets × AWS Secrets Managerの組み合わせでやってみましたが、他にも様々なプロバイダーをSecretStoreを提供しています。プロバイダーの中にはKubernetesもあって、個人的に特に気になっているところです。Kubernetesクラスター間のSecretの直接的な同期ができるのか試してみて、また記事にしてみようと思います。

## 参考リンク

- [external-secrets公式ドキュメント](https://external-secrets.io/)
- [PushSecret Reference](https://external-secrets.io/latest/api/pushsecret/)