🙆

External Secrets WebhookProviderを試してみた

2024/07/13に公開

Kubernetesを使用したアプリケーション開発において、シークレットの管理は非常に重要です。
特に、AWSやGCP等の複数のプロバイダからシークレットを取得する必要がある場合、その管理はさらに複雑になります。そこで役立つのがExternal Secretsです。

今回は、WebhookProviderを使用してExternal Secretsを試してみたのでその手順を共有します。

はじめに

External Secretsは、Kubernetesクラスタ内でシークレットを管理するためのオープンソースソリューションです。

External Secretsを使用することで、AWSやGCP、Azureといった主要クラウドプロバイダのシークレット管理サービスと簡単に統合できます。例えば、AWS Secrets Manager、GCP Secret Manager、Azure Key Vaultなどのサービスから直接シークレットを取得し、KubernetesのSecretリソースとして管理することが可能です。

今回は、WebhookProviderを使用してシークレットを取得する方法を紹介します。WebhookProviderを使用することで、独自で用意したWebhookサーバーを介してシークレットを取得することができます。

GitHubリポジトリ

このデモのソースコードは以下のGitHubリポジトリにあります。詳細な手順や設定ファイルもこちらで確認できます。

demo-external-secret-webhook

External Secretsとは

External Secretsは、Kubernetesクラスター内で外部シークレットを管理するためのオープンソースツールです。

  • 外部シークレットストア(AWS Secrets Manager、GCP Secret Manager、Azure Key Vaultなど)からシークレットを取得
  • KubernetesのSecretリソースとしてシークレットを管理
  • 定期的なシークレットの同期

この記事では、WebhookProviderを使用して独自に用意したWebhookサーバからシークレットを取得します。

使用するWebhookサーバとKubernetesリソース

Wehbookサーバ

今回Webhookサーバとして、Flaskを使用します。

from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route('/api/secrets', methods=['POST'])
def secrets():
    data = request.json
    if not data or 'key' not in data:
        return jsonify({"error": "Invalid request"}), 400
    if data['key'] == 'my-secret-key':
        return jsonify({"secret": "my-secret-value"})
    return jsonify({"error": "Secret not found"}), 404

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

requirements.txtとDockerfileも用意します。

requirements.txt

Flask==2.1.1
gunicorn==20.1.0
werkzeug==2.0.3

Dockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

COPY src app

EXPOSE 5000

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app.server:app"]

Kubernetesリソース

今回シンプルな構成として以下の4つのリソースを定義します。

SecretStoreリソース

SecretStoreリソースは、WebhookProviderの設定を定義します。

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: webhook-secret-store
spec:
  provider:
    webhook:
      method: POST
      url: "http://server:5000/api/secrets"
      result:
        jsonPath: "$.secret"
      headers:
        Content-Type: application/json
      body: '{ "key": "{{ .remoteRef.key }}" }'

ExternalSecretリソース

ExternalSecretリソースは、Webhookから取得するシークレットの設定を定義します。
secretStoreRefで先ほど定義したSecretStoreのwebhook-secret-storeを設定します。

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-external-secret
spec:
  secretStoreRef:
    name: webhook-secret-store
    kind: SecretStore
  target:
    name: my-kubernetes-secret
  data:
  - secretKey: my-secret-key
    remoteRef:
      key: my-secret-key
  refreshInterval: 10s

Service/Deploymentリソース

Webhookサーバデプロイ用としてServiceリソースとDeploymentリソースを用意します。

apiVersion: v1
kind: Service
metadata:
  name: server
spec:
  selector:
    app: server
  ports:
    - protocol: TCP
      port: 5000
      targetPort: 5000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: server
  template:
    metadata:
      labels:
        app: server
    spec:
      containers:
      - name: server
        image: server:latest
        imagePullPolicy: Never
        ports:
        - containerPort: 5000

動作確認

環境

今回はローカル環境でminikubeを使用して検証します。

動作確認するには以下のツールが必要です。
インストールされていない場合は、以下のリンクからインストールしてください。

1. Minikubeクラスターの起動

Minikubeクラスターを起動します。

minikube start

2. External Secrets Operatorのインストール

External Secrets OperatorをHelmを使用してインストールします。

helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets --version 0.9.18

3. Flaskサーバーのセットアップ

MinikubeのDocker環境を使用してDockerイメージをビルドします。

eval $(minikube -p minikube docker-env)
docker build -t server:latest .

4. Kubernetesのリソースをデプロイ

KubernetesのDeployment、Secret Store、およびExternal Secretをデプロイします。

kubectl apply -f deployment.yaml
kubectl apply -f secret-store.yaml
kubectl apply -f external-secret.yaml

5. 動作確認

最後に、シークレットが正しく作成されていることを確認します。

% kubectl get secret my-kubernetes-secret -o yaml


apiVersion: v1
data:
  my-secret-key: bXktc2VjcmV0LXZhbHVl
immutable: false
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"external-secrets.io/v1beta1","kind":"ExternalSecret","metadata":{"annotations":{},"name":"my-external-secret","namespace":"default"},"spec":{"data":[{"remoteRef":{"key":"my-secret-key"},"secretKey":"my-secret-key"}],"refreshInterval":"11s","secretStoreRef":{"kind":"SecretStore","name":"webhook-secret-store"},"target":{"name":"my-kubernetes-secret"}}}
    reconcile.external-secrets.io/data-hash: 3803218de0772602ac9de333ed87f8c9
  creationTimestamp: "2024-07-12T15:56:53Z"
  labels:
    reconcile.external-secrets.io/created-by: f88fb63048a779ad5a120c9da32bb288
  name: my-kubernetes-secret
  namespace: default
  ownerReferences:
  - apiVersion: external-secrets.io/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: ExternalSecret
    name: my-external-secret
    uid: 7833495e-1141-46f6-94c0-6ab236126303
  resourceVersion: "42328"
  uid: 22622cfa-f67e-4d4c-b97b-79b47baf45b3
type: Opaque

External Secrets OperatorがFlaskサーバー(Webhook)からシークレットを取得し、KubernetesのSecretリソースとして作成していることが確認できました。

まとめ

External Secrets Operatorを使用することで、Webhookを利用してカスタムのシークレットストアと簡単に統合することができました。
今回は、FlaskサーバーをWebhookとして使用しましたが、他のサーバーやサービスと統合することも可能です。これにより、シークレット管理の柔軟性とセキュリティが向上します。
ぜひ試してみてください。

Discussion