はじめに
セキュリティ強化のため、定期的にデータベースのパスワードをローテーションする必要があります。長期間同じパスワードを使用すると、漏洩や攻撃によるリスクが高まるため、定期的な更新は重要です。しかし、EKS上のPodが参照するシークレットも更新する必要があるため、運用負荷が高くなりがちです。
解決策の1つとして、AWS Secrets ManagerとExternal Secrets Operatorを活用した自動パスワードパスワードローテーションを試してみました。
- AWS Secrets Manager
- データベースのパスワードを安全に保管し、定期的なローテーションを自動化することができるだけでなく、RDSのパスワードの更新と保管も実施できる
- External Secrets Operator(ESO)
- AWS Secrets Managerに保存されたパスワードをEKS上のKubernetesシークレットに同期する
今回試した内容は公式ドキュメントの手順を元にに、Secrets ManagerによるRDSパスワードローテーションを追加した内容となっています。
構成
AWS Secrets ManagerとExternal Secrets Operatorを組み合わせて、RDSのパスワードローテーションを自動化するようにしています。Secrets Managerが定期的にパスワードをローテーションし、その変更内容がExternal Secrets Operatorを通じてEKSのKubernetesシークレットに反映されます。これにより、Podが新しいパスワードでRDSに接続できるようになり、手動作業を削減できます。
今回はPostgreSQLへ接続するシンプルなアプリを利用しました。ヘルスチェックでDBに接続できるか確認するようになっており、DB接続に一定回数失敗するとpodが再起動し、新しいシークレットでDBに接続するようになります。イメージはdockerhubに格納しています。
前提
- RDS PostgreSQL環境があること
- RDSへ接続できるEKS環境があること
- aws,kubectl,eksctl,helmコマンドが利用できること
流れ
- Secrets Manager接続用のポリシーとサービスアカウント作成
- ESOをデプロイ
- SecretStoreを作成
- ExternalSecretを作成
- アプリのデプロイ
- パスワードローテーションをSecert Manager から実施
- アプリが再起動することを確認
1.Secret Manager用のポリシーを作成
1.1 IAM OIDCプロバイダー作成
(作成済みの場合は実行不要)
eksctl utils associate-iam-oidc-provider --cluster=[クラスター名] --approve
1.2 secretmanagerへのアクセス許可を付与するiamポリシーを作成する
コンソールからSecret Managerのシークレットarnとシークレット名を確認します。
secretmanager-access-policy.jsonのSecret Managerのシークレットarnをを書き換えます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:ap-northeast-1:[AWSアカウントID]:secret:[認証情報が保管されているSecret-managerのシークレットARN]"
}
]
}
以下のコマンドを実行し、Secret Managerへのアクセスを許可するポリシーを作成する(あるいはコンソールから作成することもできます。)
aws iam create-policy --policy-name SecretsManagerRDSPostgreReadWrite --policy-document file://secretmanager-access-policy.json
1.3 Secret ManagerへアクセスするIAMロールとサービスアカウントを作成する
アカウントIDとクラスター名を書き換えてから実行します。
eksctl create iamserviceaccount \
--name secretmanager-service-account \
--namespace default \
--cluster クラスター名 \
--role-name "secretmanager-service-account" \
--attach-policy-arn arn:aws:iam::[AWS アカウントID]:policy/SecretsManagerRDSPostgreReadWrite \
--approve \
--override-existing-serviceaccounts
正しく作成されているか確認します。
kubectl get sa
kubectl describe sa secretmanager-service-account
2.ESO をデプロイ
今回はHelmを使用してExternal Secrets OperatorをEKS環境にデプロイします。
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
--set installCRDs=true \
--set webhook.port=9443
kubectl get pods -n external-secrets
3.SecretStoreを作成
SecretStoreは、ESOがシークレットデータを取得するためのプロバイダ設定を定義するリソースです。今回は、AWS Secrets Managerをプロバイダとして使用し、新しく作成したサービスアカウントを利用して認証を行います。
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: secretmanager-secret-store
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-1
auth:
jwt:
serviceAccountRef:
name: secretmanager-service-account
作成
kubectl apply -f secretstore.yaml
動作確認
kubectl get secretstore
kubectl describe secretstore secretmanager-secret-store
※権限が不足している場合やシークレットARNの指定が正しくない場合にエラーが発生するので、エラー内容を確認して修正してください。
4.ExternalSecretを作成
ExternalSecretは、AWS Secrets Managerから取得したシークレットをEKSのKubernetesシークレットとして同期するための設定を行うリソースです。refreshIntervalはシークレットの同期間隔を指定し、dataでは取得するプロパティ(例: username や password)を定義します。
Secret-managerのシークレットARNを書き換えてから実行してください。
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: secretmanager-external-secret
spec:
refreshInterval: 30s # 更新間隔
secretStoreRef:
name: secretmanager-secret-store
kind: SecretStore
target:
name: secret-manager-value
creationPolicy: Owner
data:
- secretKey: postgre-mydb-username # シークレットに登録する名称
remoteRef:
key: rds!db-xxx # [認証情報が保管されているSecret-managerのシークレットARN]
property: username # [Secret-managerに保管されているプロパティ名]
- secretKey: postgre-mydb-password
remoteRef:
key: rds!db-xxx
property: password
作成
kubectl apply -f externalsecret.yaml
動作確認
kubectl get externalsecrets
kubectl describe externalsecrets secretmanager-external-secret
secret manager から取得したユーザ名, パスワードがシークレットに登録されていることを確認します。
kubectl get secrets secret-manager-value
デコードしてSecret-managerとシークレットの値が一致することを確認します。
kubectl get secret secret-manager-value -o jsonpath='{.data.postgre-mydb-username}' | base64 -d
kubectl get secret secret-manager-value -o jsonpath='{.data.postgre-mydb-password}' | base64 -d
5.アプリのデプロイ
PostgreSQLに接続するサンプルアプリをデプロイします。デプロイしたアプリは、Kubernetesシークレットに保存されたRDSの接続情報を環境変数として読み込みます。deploymentのヘルスチェックの設定で、定期的にデータベースに接続できるかを確認します。RDSのパスワードが更新されると、ヘルスチェックが失敗してPodが再起動し、更新されたシークレットを更新される仕組みです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
labels:
app: flask-app
spec:
replicas: 1
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
spec:
containers:
- name: flask-app
image: jeff0525/postgre-app:v1.0 # postgre-appイメージのパス
ports:
- containerPort: 5000
env:
- name: DB_HOST
value: xxxx.ap-northeast-1.rds.amazonaws.com # DBエンドポイント
- name: DB_NAME
value: xxx # データベース名
- name: DB_USER
valueFrom:
secretKeyRef:
name: secret-manager-value
key: postgre-mydb-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: secret-manager-value
key: postgre-mydb-password
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 10
periodSeconds: 10
デプロイ
kubectl apply -f deployment.yaml
podの起動確認
kubectl get pods
pod起動後にシークレットを確認
kubectl exec [flask-app-ポッド名] -- printenv |grep DB
healthチェックが成功していることを確認
kubectl logs [flask-app-ポッド名] -f
6. DBのパスワードローテーションを実施
Secrets Managerでパスワードをローテーションすると、External Secrets Operatorがその変更を検知し、Kubernetesシークレットを自動的に更新します。その後、Podが再起動することで、新しいパスワードが環境変数に反映されます。この一連の動作により、手動操作を必要とせずにパスワードローテーションが完結します。
Secret Managerからローテーションタブを選択し、"すぐにシークレットローテーションさせる"をクリックで実行できます。
Secret Manager上のシークレット値が更新されていることを確認します。
7. アプリが再起動することを確認
パスワードローテーションを実行すると、podのヘルスチェックでエラーが発生するようになります。しばらくすると、自動でpodが再起動し、db接続できるようになります。
シークレットが更新されていることを確認します。
kubectl get secret secret-manager-value -o jsonpath='{.data.postgre-mydb-username}' | base64 -d
kubectl get secret secret-manager-value -o jsonpath='{.data.postgre-mydb-password}' | base64 -d
おわりに
AWS Secrets ManagerとExternal Secrets Operatorを組み合わせることで、EKS環境におけるデータベースのパスワードローテーションを自動化することができました。これにより、運用負荷が軽減されるだけでなく、手動操作によるミスのリスクも低減することができます。また、パスワードローテーションをより頻繁に実施できるようになり、セキュリティが向上するメリットもあります。
Appendix
作成したリソースの削除
kubectl delete -f deployment.yaml
kubectl delete -f externalsecret.yaml
kubectl delete -f secretstore.yaml
eksctl delete iamserviceaccount \
--name secretmanager-service-account \
--namespace default \
--cluster [クラスター名]
helm uninstall external-secrets -n external-secrets