External Secrets WebhookProviderを試してみた
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リポジトリにあります。詳細な手順や設定ファイルもこちらで確認できます。
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