🐙

AWS SecretsManagerからSecretリソースを作成するHelmチャートを作成してみた

2022/09/04に公開

1. はじめに

1-1. 記事について

External Secretsを利用してAWS SecretsManagerの秘匿情報からSecretリソースを作成するHelmチャート」を作成したので、実装例として紹介します。

1-2. External Secretsとは

External Secretsはクラウドプロバイダが提供するシークレット管理サービスからSecretリソースを作成するCRDです。Kubernetes(=以下k8s)のSecret管理方法には他にも色々あります[1]が、この記事ではExternal Secretsを採用しています。

2. 実装

2-1. ディレクトリ構成

$ tree .
.
└── secretsmanager
    ├── Chart.lock
    ├── Chart.yaml
    ├── charts
    │   └── external-secrets-0.5.9.tgz
    ├── templates
    │   ├── _helpers.tpl
    │   ├── externalsecret.yaml
    │   ├── secretstore.yaml
    │   └── serviceaccount.yaml
    └── values.yaml

2-2. Chart.yaml

secretsmanager/Chart.yamlhelm create secretsmanagerコマンドにより生成したデフォルトからコメントを削除したものをベースとしています。ここでは、External Secretsを利用してSecretリソースを作成するので、.dependenciesとしてexternal-secretsを指定しています。

secretsmanager/Chart.yaml
apiVersion: v2
name: secretsmanager
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"

dependencies:
- name: external-secrets
  version: "0.5.9"
  repository: "https://charts.external-secrets.io"

そして、上記の状態でhelm dependency updateコマンド[2]を実行し、指定バージョンのExternal Secretsチャートをリポジトリからダウンロードします。これにより、secretsmanager/charts/external-secrets-0.5.9.tgzsecretsmanager/Chart.lockが作成されます。

2-3. _helpers.tpl

テンプレート内で汎用的に使い回す定義は、tpl関数としてsecretsmanager/templates/_helpers.tplに記述しています。

secretsmanager/templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "secretsmanager.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "secretsmanager.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "secretsmanager.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "secretsmanager.labels" -}}
helm.sh/chart: {{ include "secretsmanager.chart" . }}
app.kubernetes.io/name: {{ include "secretsmanager.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

ここでは、テンプレートから直接呼び出しているtpl関数は以下の2つだけです。

  1. secretsmanager.fullname
  2. secretsmanager.labels

まず、secretsmanager.fullnameは全リソースの.metadata.nameを動的に変更させるために使用しています。こうすることで、テンプレートを変更することなくhelmコマンドからユニークな名前でリソースをデプロイすることができ、k8sの開発効率が上がります。

また、secretsmanager.labelsは全てのリソースに対して共通ラベルを付与するために使用しています。k8sでは、全てのツールで解釈可能な共通ルールに則ってラベル付けすることが推奨[3]されており、こうすることでツールに依存せずオブジェクトの管理と可視化が可能になるようです。

2-4. ServiceAccount

External SecretsがAWS SecretsManagerから秘匿情報を取得してSecretリソースを作成するためのServiceAccountを作成しています。このServiceAccountにはcreate-secret-from-secrets-manager-roleという名のロールを紐づけています。

secretsmanager/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "secretsmanager.fullname" . }}
  labels:
    {{- include "secretsmanager.labels" . | nindent 4 }}
    app.kubernetes.io/component: account
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.awsAccount }}:role/{{ .Values.roleName }} # ロール名はcreate-secret-from-secrets-manager-role

このIAMロール(=create-secret-from-secrets-manager-role)はmainネームスペース内の任意のServiceAccountがAssumeRoleできるように定義しています。

create-secret-from-secrets-manager-roleの信頼されたエンティティ
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::AWSアカウント:oidc-provider/oidc.eks.リージョン名.amazonaws.com/id/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "oidc.eks.リージョン名.amazonaws.com/id/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:sub": "system:serviceaccount:main:*"
                }
            }
        }
    ]
}

また、SecretsManagerから任意のリソースを取得できるポリシーを先述のIAMロールにアタッチしています。

create-secret-from-secrets-manager-roleにアタッチされたポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "secretsmanager:DescribeSecret",
                "secretsmanager:GetResourcePolicy",
                "secretsmanager:GetSecretValue",
                "secretsmanager:ListSecretVersionIds"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

2-5. SecretStore

2-4節で作成したServiceAccountを用いてAWS SecretsManagerへアクセスするよう定義しています。

secretsmanager/templates/secretstore.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: {{ include "secretsmanager.fullname" . }}
  labels:
    {{- include "secretsmanager.labels" . | nindent 4 }}
spec:
  provider:
    aws:
      service: SecretsManager
      region: {{ .Values.awsRegion | quote }}
      auth:
        jwt:
          serviceAccountRef:
            name: {{ include "secretsmanager.fullname" . }}

2-6. ExternalSecret

2-5節で定義したアクセス情報を利用して、指定のシークレット/シークレットキーからシークレットの値を取得して、Secretリソースを作成しています。

secretsmanager/templates/externalsecret.yaml
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
  name: {{ include "secretsmanager.fullname" . }}
  labels:
    {{- include "secretsmanager.labels" . | nindent 4 }}
spec:
  refreshInterval: {{ .Values.secretRefreshInterval | quote }}
  secretStoreRef:
    name: {{ include "secretsmanager.fullname" . }}
    kind: SecretStore
  target:
    name: {{ .Values.secretsManagerName | quote }}
    creationPolicy: Owner
  data:
  - secretKey: password
    remoteRef:
      key: {{ .Values.secretsManagerName | quote }}
      property: password
  - secretKey: dbname
    remoteRef:
      key: {{ .Values.secretsManagerName | quote }}
      property: dbname
  - secretKey: port
    remoteRef:
      key: {{ .Values.secretsManagerName | quote }}
      property: port
  - secretKey: host
    remoteRef:
      key: {{ .Values.secretsManagerName | quote }}
      property: host
  - secretKey: username
    remoteRef:
      key: {{ .Values.secretsManagerName | quote }}
      property: username

AWS SecretsManagerに登録されているシークレット情報は以下の通りです。

2-7. values.yaml

secretsmanager/values.yaml
fullnameOverride: ""
nameOverride: ""
awsRegion: us-east-1
awsAccount: "123456789012"
secretsManagerName: database-secrets
secretRefreshInterval: 1h

3. デプロイ後の確認

デプロイ後、database-secretsというSecretリソースが作成されていることを確認できます。

$ kubectl describe secret/database-secrets -n main
Name:         database-secrets
Namespace:    main
Labels:       app.kubernetes.io/instance=secretsmanager
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=secretsmanager
              app.kubernetes.io/version=1.16.0
              helm.sh/chart=secretsmanager-0.1.0
Annotations:  reconcile.external-secrets.io/data-hash: 8d2b82dda5c193f29cd8e510be4a8801

Type:  Opaque

Data
====
username:  8 bytes
dbname:    15 bytes
host:      72 bytes
password:  30 bytes
port:      4 bytes

4. まとめ

この記事では、External Secretsを利用してAWS SecretsManagerの秘匿情報からSecretリソースを作成するHelmチャートの作成例を紹介しました。Helmに精通しているわけではありませんが、実装例から参考になることもあるかもしれないので記録として残しておきます。誰かの何かのお役に立てれば幸いです^^

脚注
  1. 他にどのようなサービスがあるのかについては、悩みに悩んだ Kubernetes Secrets の管理方法、External Secrets を選んだ理由などが分かりやすそうです。 ↩︎

  2. 詳細はHelm Dependency Updateをご参照ください。 ↩︎

  3. 推奨ラベル(Recommended Labels) ↩︎

Discussion