EKS on FargateでAWSのParameter Storeの情報を取得する
皆さん、こんにちは。技術開発グループのn-ozawanです。
最近、朝が起きれません。早起きは3文の徳と言いますが、3文とは100円ぐらいだそうです。1年間早起きすれば36,500円の徳ですね。早起き頑張ろう。
本題です。
EKSでSSMのParameter Storeへアクセスするには、AWS Secrets and Configuration Provider (ASCP) を利用することで、Parameter Storeの値を取得することが出来るようになります。しかしこのASCPですが、DaemonSetとしてデプロイされるため、DaemonSetをサポートしていないFargate環境では動作しません(参考)。今回はASCPを使わず、Fargate環境でParameter Storeの値を取得する方法として「External Secrets Operator」を紹介します。
目次
External Secrets Operator
DBへ接続するための必要な情報としてパスワードなどがありますが、これら機密情報は直接コードに記述せずに、外部で管理した方がセキュリティの関係で良いです。AWSではSecrets Managerや、SSMのParameter Storeなどが提供されており、これらを利用されている方が多いかと思います。
AWSでは、EKSからParameter Storeの値を取得するためにASCPを提供していますが、先ほど述べた通り、Fargate環境では使えません。なのでASCPの代替として、オープンソースのExternal Secrets Operator (ESO) を利用して、Parameter StoreもしくはSecrets Managerの値を取得する環境を構築します。
なお、ESOの構築にはこちらのブログ記事を参考にしました。
ゴール
ESOを利用するために必要なことは、ESOのインストール、Secrets ManagerおよびParameter Storeへのアクセス権限とService Accountの作成となります。今回はこれらをTerraformで構築します。あと、実際にParameter Storeの値を取得するマニフェストを定義します。

ESOのインストール
ESOをインストールするにはHELMを利用します。HELMはKubernetes用のパッケージ管理ツールで、よく使う構成やアドオンなどをインストールすることが出来る優れものです。
resource "helm_release" "external-secrets" {
name = "external-secrets"
chart = "external-secrets"
repository = "https://charts.external-secrets.io"
namespace = "kube-system"
version = "0.8.5"
dynamic "set" {
for_each = {
"webhook.port" = 9443
}
content {
name = set.key
value = set.value
}
}
}
TerraformでHelmによるインストールにはhelm_releaseを利用します。name
はリリース名で、chart
はインストールするチャートを指定します。repository
はESOのチャートが管理されているリポジトリです。namespace
はPODが動作する名前空間で、kube-system
を指定しています。
Fargate環境ではwebhook.port
に9443番ポートを指定します。ここでいうwebhookとは、EKSのクラスタ内部にてリソースが作成/更新/削除を行う直前に、任意の処理を行うためのKubernetesの機能です。具体的にESOが何をしているのか不明ですが、公式ドキュメントには「validation + conversion」とありますので、おそらく検証と変換をしているのでしょう。
サービスアカウントの作成
Kubernetesのサービスアカウントは、PODと紐づけて動作するアカウントです。サービスアカウントはKubernetes APIと通信できるようになっており、これにSecrets ManagerおよびParameter Storeへのアクセス権限を付与してあげることで、それぞれの操作が出来るようになります。
作成手順については、以前の記事「EKSでロードバランサーを構築する」で記載した「サービスアカウントの作成」と同じです。以前の記事と異なるのはポリシーの定義ぐらいですので、本稿ではポリシーの定義だけを紹介します。
以下はParameter Storeの値を取得するポリシーになります。
data "aws_caller_identity" "current" {}
resource "aws_iam_policy" "parameter_store_readonly" {
name = "parameter-store-readonly-policy"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [ {
"Effect": "Allow",
"Action": ["ssm:GetParameter", "ssm:GetParameters"],
"Resource": ["arn:aws:ssm:ap-northeast-1:${data.aws_caller_identity.current.account_id}:parameter/*"]
} ]
}
POLICY
}
以下はSecrets Managerの値を取得するポリシーになります。
data "aws_caller_identity" "current" {}
resource "aws_iam_policy" "secrets_manager_readonly" {
name = "secrets-manager-readonly-policy"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [ {
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
"Resource": ["arn:aws:secretsmanager:ap-northeast-1:${data.aws_caller_identity.current.account_id}:secret:*"]
} ]
}
POLICY
}
Terraformによる構築は以上になります。次からはKubernetes側で実際に使うときの定義になります。
SecretStoreの定義
Kubernetesでシークレットストアにアクセスするには、まず、そのシークレットストアに関する情報を定義する必要があります。
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: sample-secret-store
spec:
provider:
aws:
service: ParameterStore
region: ap-northeast-1
auth:
jwt:
serviceAccountRef:
name: parameter-store-readonly
kind
は「SecretStore」になります。metadata.name
はこのSecretStoreの名前です。
spec.provider.aws
にはAWSが提供するシークレットストアに関する情報を定義します。上記のマニフェストはSSM Parameter Storeに関する情報になります。service
には「ParameterStore」を指定しています。Secret Managerから値を取得する場合は「SecretsManager」を指定します。auth.jwt.serviceAccountRef.name
にはアクセスする際のサービスアカウントを指定します。
ExternalSecretの定義
SecretStoreに関する情報を定義しましたので、次は、そのSecretStoreから何の値を取得
するのか?を定義します。
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: sample-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: sample-secret-store
kind: SecretStore
target:
name: sample-secret
creationPolicy: Owner
data:
- secretKey: database_password
remoteRef:
key: /prod/sample/db_password
kind
は「ExternalSecret」になります。metadata.name
はこのExternalSecretの名前です。
spec.refreshInterval
にはSecretStoreから最新の値を取得する間隔です。「1h」は1時間ごとにSecretStoreから値を取得するということを示しています。
spec.secretStoreRef
には、参照先のSecretStoreを指定します。name
には先ほど定義したSecretStoreの名前を指定します。
spec.target
にはESOが作成するSecretの情報を定義します。name
はSecretの名前です。PODなどのリソースから参照する際に利用します。creationPolicy
には「Owner」を指定します。これは新規に作成することを意味します。他には「Merge」などがあり、既にあるSecretとマージするかどうかを指定することも可能です。
spec.data
には実際に取得する値が定義されています。remoteRef
はSecretStoreから取得したいKey名を指定します。secretKey
はSecretStoreから取得した値に対して、Key名を新たに付与します。
SecretStoreの値を環境変数に指定する
SecretStoreから取得した値を、環境変数としてPODへ渡す方法は以下の通りです。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod
spec:
containers:
- name: nginx-container
image: nginx:latest
env:
- name: HOGE
valueFrom:
secretKeyRef:
name: sample-secret
key: database_password
spec.env.valueFrom.secretKeyRef
のname
にはESOが作成するSecretを指定します。具体的には先ほど定義したExternalSecretのspec.target.name
になります。key
にはExternalSecretで定義した対象のKeyを指定します。これによりSecretStoreからの値を取得することが出来ます。
おわりに
ESOによる、Parameter Storeの値を参照する方法を紹介しました。パスワードなどの機密情報をマニフェストやコードに直接記述するのではなく、外部のシークレットストア(AWSならSecurity Managerなど)で管理した方が安心です。もしそうした場合に今回の記事が少しでも参考になれば幸いです。
ではまた。