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.secretKeyRefnameにはESOが作成するSecretを指定します。具体的には先ほど定義したExternalSecretのspec.target.nameになります。keyにはExternalSecretで定義した対象のKeyを指定します。これによりSecretStoreからの値を取得することが出来ます。

おわりに

ESOによる、Parameter Storeの値を参照する方法を紹介しました。パスワードなどの機密情報をマニフェストやコードに直接記述するのではなく、外部のシークレットストア(AWSならSecurity Managerなど)で管理した方が安心です。もしそうした場合に今回の記事が少しでも参考になれば幸いです。

ではまた。

Recommendおすすめブログ