Files
secret-manager/README.md
T
claude 2b17088fae
Helm Chart Release / release-chart (push) Successful in 7s
fix: mail プロバイダの msmtprc を既定で常に出力 + SMTP メタ項目を拡充
phpfpm の smtp.existingMsmtprcSecret が「references non-existent secret key:
msmtprc」でマウント失敗する件の対処。renderMsmtprc が opt-in で忘れやすかった。

- renderMsmtprc を既定 true に(明示 false のときだけ抑止)
- smtp.tlsVerify(既定 true)を追加。false で msmtprc に tls_certcheck off
  values.schema.json の smtp にも tlsVerify を追加
- 派生キー SMTP_TLS / SMTP_SSL / SMTP_TLS_VERIFY / SMTP_AUTH を Secret に追加
- from 未設定時に msmtprc へ空の from 行を出さないよう修正
- values.yaml / examples / README を更新。0.1.2。
2026-09-08 18:47:25 +09:00

21 KiB
Raw Blame History

secret-manager

クラスター内の複数プロジェクトで横断的に使う 認証情報(Secrets)を一元管理 する Helm チャートです。

外部メールの SMTP/IMAP 設定、外部 API の認証キー、その他の汎用資格情報を 1 箇所(この チャートの values.yaml)で定義し、指定した 複数のネームスペースへ同一の Kubernetes Secret として配布 します。

設計目標 — 認証情報の変更が発生したら、このチャートの values を更新して helm upgrade するだけで、クラスター上の全プロジェクトに反映される。

  • Gitea リポジトリ: ssh://git@192.168.9.65/helmchart/secret-manager
  • Helm リポジトリ: https://git.cafepieters.com/api/packages/helmchart/helm

目次


なぜ必要か

Kubernetes の Secret は ネームスペース単位 です。同じ SMTP パスワードや API キーを 10 個のプロジェクトで使う場合、通常は 10 個のネームスペースそれぞれに Secret を作り、 変更のたびに 10 箇所を更新することになります。

このチャートは「認証情報の定義」を 1 リポジトリに集約し、配布はチャートに任せます。 パスワードローテーションやエンドポイント変更は このチャートの 1 回の更新 で全体に 行き渡ります。

仕組み

                値の定義(1箇所)
        ┌──────────────────────────────┐
        │  secret-manager / values.yaml│
        │   mail.providers.gmail       │
        │   api.credentials.stripe     │
        │   generic.secrets.registry   │
        └───────────────┬──────────────┘
                        │ helm upgrade
        ┌───────────────┼───────────────┐
        ▼               ▼               ▼
  ns: web           ns: batch       ns: ml
  Secret cred-gmail Secret cred-... Secret cred-...
  Secret cred-stripe               ...
  • 1 つの論理エントリ(mail.providers.gmail など)から、配布先ネームスペースの数だけ 同名の Secret が生成されます(common.namePrefix + エントリ名)。
  • 配布方式は 2 通り。既定は Helm による複製(外部依存なし)。オプションで kubernetes-reflector による自動同期 も選べます。

セキュリティモデル

この values.yaml には平文の資格情報を書かないでください。Git にコミットしないでください。

Kubernetes の Secret は etcd 上では base64 で「エンコード」されているだけで「暗号化」では ありません。同様に Helm values に平文で書けば、それが Git・CI ログ・Helm リリース履歴 (helm get values)に残ります。実値は次のいずれかで注入します。

方法 用途 例
CI から --set / --set-file Gitea Actions が Gitea Secrets を注入(推奨) --set mail.providers.gmail.smtp.password="$GMAIL_APP_PASSWORD"
SOPS 暗号化 values 暗号化したまま Git 管理したい場合(helm secrets プラグイン) helm secrets upgrade -f secrets.enc.yaml ...
git 管理外の values ファイル 手元運用 -f secrets.values.yaml(.gitignore 済み)

追加の推奨:

  • etcd の保存時暗号化(EncryptionConfiguration) をクラスターで有効化する。
  • 配布先を必要なネームスペースだけに絞る(distribution.namespaces を最小化、または reflector.namespaceRegex で限定)。default への配布は避ける。
  • 変更頻度が低い資格情報は common.immutable: true にして誤更新を防ぐ。
  • Secret を参照する RBAC(get secrets)を各ネームスペースで最小権限に保つ。
  • このチャートは Secret 以外のリソース(Deployment 等)を一切作りません。監査が容易です。

インストール

# Helm リポジトリ経由
helm repo add cafepieters https://git.cafepieters.com/api/packages/helmchart/helm
helm repo update
helm upgrade --install secrets cafepieters/secret-manager \
  -n secret-manager --create-namespace \
  -f my-secrets.values.yaml
# ローカルのチャートディレクトリから
helm upgrade --install secrets ./secret-manager \
  -n secret-manager --create-namespace \
  -f examples/mail-providers.values.yaml \
  --set mail.providers.gmail.smtp.password="$GMAIL_APP_PASSWORD" \
  --set mail.providers.m365.smtp.password="$M365_PASSWORD"

配布先ネームスペース(distribution.namespaces)は 事前に存在している必要があります。

values リファレンス

distribution — 配布先

キー 型 既定 説明
distribution.namespaces []string ["default"] 同一 Secret を複製配置するネームスペース。リストから外して helm upgrade すると、そのコピーは削除される。
distribution.includeReleaseNamespace bool true リリース先ネームスペース(-n の値)も配布対象へ自動追加する。
distribution.reflector.enabled bool false kubernetes-reflector による自動同期を使う。有効時、Secret はリリース先のみに作成し reflector アノテーションを付与する。
distribution.reflector.namespaceRegex string ".*" 同期先ネームスペースを絞る正規表現。例 "prod-.*|shared-services"。
distribution.reloader.enabled bool false 各 Secret に reloader.stakater.com/match: "true" を付与。参照側 Deployment に reloader.stakater.com/auto: "true" があれば Secret 変更時に自動ローリング再起動される。

common — 共通設定

キー 型 既定 説明
common.namePrefix string "cred-" 生成される Secret 名のプレフィックス。cred- + gmail → cred-gmail。
common.labels map {} 全 Secret に付与する追加ラベル。
common.annotations map {} 全 Secret に付与する追加アノテーション。
common.immutable bool false Secret を immutable: true にする。値変更には一度 kubectl delete secret が必要。

mail — 外部メール接続情報

mail.providers.<名前> ごとに 1 つの Secret を生成します。パスワードだけでなく SMTP/IMAP/POP3 のサーバ情報も一元管理 できます。

キー 型 必須 説明
enabled bool ✓ true で生成対象。
smtp.host string ✓ SMTP サーバホスト名。
smtp.port int 既定 587。
smtp.security string none / starttls(既定) / tls。
smtp.username string 認証ユーザー名。空なら認証なし。
smtp.password string 認証パスワード。--set / SOPS で注入。
smtp.from string 既定送信元アドレス。省略時は username。
smtp.fromName string 送信者表示名。
smtp.replyTo string Reply-To アドレス。
smtp.authMethod string plain(既定) / login / cram-md5。
smtp.tlsVerify bool 既定 true。false で TLS 証明書検証を無効化(社内リレー・自己署名向け。生成される msmtprc に tls_certcheck off が入る)。
imap.host ほか 受信 (IMAP) 情報。host があれば IMAP_* キーを追加。username/password 省略時は SMTP の値を流用。既定ポート 993。
pop3.host ほか 受信 (POP3) 情報。同上。既定ポート 995。
renderMsmtprc bool 既定 true。Secret に完成済み msmtprc(msmtp / PHP sendmail 用)を含める。IMAP だけ使う等で不要なら false。
namespacesOverride []string このエントリだけ別のネームスペースへ配布する。
annotations map このエントリの Secret だけに付ける追加アノテーション。

api — API 認証情報

api.credentials.<名前> ごとに 1 つの Opaque Secret を生成します。data 配下のキーが そのまま Secret のキーになります(envFrom でまとめて注入しやすい形)。

キー 型 必須 説明
enabled bool ✓ true で生成対象。
data map ✓ 1 つ以上のキー/値。値は文字列化される。
namespacesOverride []string 配布先の上書き。
annotations map 追加アノテーション。

generic — 汎用 Secret

generic.secrets.<名前> ごとに 1 つの Secret を生成します。任意の Secret type を 指定できます(kubernetes.io/dockerconfigjson、kubernetes.io/tls、 kubernetes.io/basic-auth など)。

キー 型 必須 説明
enabled bool ✓ true で生成対象。
type string Secret の type。既定 Opaque。
data map ✓ 1 つ以上のキー/値。kubernetes.io/tls なら tls.crt / tls.key を入れる。
namespacesOverride []string 配布先の上書き。
annotations map 追加アノテーション。

生成される Secret の構造

mail エントリ(例: cred-gmail)

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_TLS=true                     # security=starttls のとき true(派生フラグ)
SMTP_SSL=false                    # security=tls(暗黙TLS)のとき true
SMTP_TLS_VERIFY=true              # smtp.tlsVerify の値
SMTP_USERNAME=no-reply@example.com
SMTP_PASSWORD=********
SMTP_AUTH=true                    # username があれば true
SMTP_FROM=no-reply@example.com
SMTP_FROM_NAME=Example
SMTP_REPLY_TO=
SMTP_AUTH_METHOD=login
IMAP_HOST=imap.gmail.com          # imap 定義時のみ
IMAP_PORT=993
IMAP_SECURITY=tls
IMAP_USERNAME=no-reply@example.com
IMAP_PASSWORD=********
smtp.env                          # ↑ 全キーを `export KEY="value"` にした source 可能なファイル
    export SMTP_HOST="smtp.gmail.com"
    ...
msmtprc                           # 完成済み msmtp 設定(既定で常に含む。renderMsmtprc: false で抑止)

api / generic エントリ

data に書いたキーがそのまま Secret のキーになります。

他プロジェクトからの参照方法

配布先ネームスペースにいる Deployment から、通常の Secret として参照します。 このチャートに依存関係を張る必要はありません(Secret が既にそのネームスペースにある)。

# まとめて環境変数へ(api / generic 向き)
envFrom:
  - secretRef:
      name: cred-stripe

# 個別キーを参照(mail 向き)
env:
  - name: SMTP_HOST
    valueFrom: { secretKeyRef: { name: cred-gmail, key: SMTP_HOST } }
  - name: SMTP_PASSWORD
    valueFrom: { secretKeyRef: { name: cred-gmail, key: SMTP_PASSWORD } }

# env ファイル / msmtprc をファイルとしてマウント
volumes:
  - name: smtp
    secret:
      secretName: cred-gmail
      items:
        - { key: smtp.env, path: smtp.env }
        - { key: msmtprc,  path: msmtprc, mode: 0600 }
volumeMounts:
  - { name: smtp, mountPath: /etc/smtp, readOnly: true }
# コンテナ起動時に env ファイルを読み込む例
set -a; . /etc/smtp/smtp.env; set +a

helmchart/phpfpm チャートと組み合わせる場合は、phpfpm 側の phpEnv や既存の SMTP セクションに、ここで配布した cred-* Secret を secretKeyRef で渡してください。

代表的なメールプロバイダの設定値

プロバイダ SMTP host port security username 備考
Gmail / Google Workspace smtp.gmail.com 587 starttls メールアドレス 2 段階認証 + アプリ パスワードが必要
Microsoft 365 smtp.office365.com 587 starttls メールアドレス SMTP AUTH の有効化が必要な場合あり
SendGrid smtp.sendgrid.net 587 starttls apikey(固定) password が API キー本体
Amazon SES email-smtp.<region>.amazonaws.com 587 starttls SES SMTP 認証情報 IAM キーとは別物。要リージョン
Mailgun smtp.mailgun.org 587 starttls postmaster@mg.<domain>
社内 SMTP リレー クラスター内 Service 等 25 none (なし) 認証・TLS なし構成

IMAP 受信の代表値: Gmail imap.gmail.com:993/tls、M365 outlook.office365.com:993/tls。

そのまま使えるサンプル: examples/mail-providers.values.yaml、 examples/api-and-generic.values.yaml、 examples/reflector.values.yaml。

変更の反映(伝播)とライフサイクル

  • 値の変更 — values を更新して helm upgrade すると、全配布先の Secret が同時に 更新されます。実行中の Pod の環境変数は再起動するまで変わりません。ファイル マウント(smtp.env 等)は数十秒〜で更新されます。自動再起動が必要なら distribution.reloader.enabled: true + 参照側の Reloader アノテーションを使います。
  • 配布先の追加 — distribution.namespaces に足して helm upgrade。
  • 配布先の削除 — distribution.namespaces から外して helm upgrade すると、 そのネームスペースのコピーは Helm が削除します。
  • エントリの削除 — enabled: false にするか、エントリごと削除して helm upgrade。 全ネームスペースから当該 Secret が削除されます。
  • アンインストール — helm uninstall で、このチャートが作った全ネームスペースの Secret が削除されます。

reflector / Reloader 連携

kubernetes-reflector(配布の自動化)

ネームスペースが頻繁に増減する環境では、distribution.namespaces の列挙が手間になります。 emberstack/kubernetes-reflector を クラスターに導入し、distribution.reflector.enabled: true にすると、Secret はリリース先 ネームスペースにのみ作られ、namespaceRegex に一致する全ネームスペースへ reflector が 複製・追従します。

distribution:
  namespaces: []
  reflector:
    enabled: true
    namespaceRegex: "prod-.*|shared-services"

stakater/Reloader(参照側の自動再起動)

stakater/Reloader 導入済みなら distribution.reloader.enabled: true で各 Secret に reloader.stakater.com/match: "true" が付きます。参照側 Deployment に reloader.stakater.com/auto: "true" を付けておくと、 Secret 更新時に自動でローリング再起動されます。

自動化ワークフロー(Gitea Actions)

.gitea/workflows/helm-release.yaml の 1 本のみ。main への push(Chart.yaml / values.yaml / values.schema.json / templates/** 変更時)と手動実行で発火し、 helm lint --strict のあと、次の 3 つの成果物を それぞれ独立に冪等チェックして、 不足しているものだけを作る:

  1. Package Registry — Chart.yaml の version が未登録なら .tgz を発行
  2. Git タグ v<version> — リモートに無ければ作成して push
  3. Gitea リリース — 同名リリースが無ければ作成(.tgz 添付)

3 つすべて揃っていれば何もしない(README・examples だけの変更ではリリースされない)。 独立チェックなので、いずれかのステップだけ失敗しても次の push で不足分だけが 再実行されて自己修復する。

上流イメージの週次自動追従は行わない — 他チャート(phpfpm・n8n 等)は image-update-and-release.yaml で Docker Hub の最新イメージを毎週チェックしバージョンを 自動採番するが、secret-manager は Secret を生成するだけで追従対象の上流イメージが 無いため、このワークフロー自体を持たない。バージョンは常に手動で Chart.yaml を更新する。

新しいバージョンを出す手順: Chart.yaml の version と appVersion を上げて main に push するだけ。あとは helm-release.yaml がリリースする。

必要な Gitea Secrets: GITEA_TOKEN(リリース・タグ)、REGISTRY_USER / REGISTRY_TOKEN(Package Registry)。

トラブルシューティング

症状 原因 / 対処
namespaces "xxx" not found で install 失敗 配布先ネームスペースは事前作成が必要。kubectl create namespace xxx。
helm upgrade しても Pod の挙動が変わらない Pod の環境変数は再起動まで不変。Pod を再起動するか Reloader 連携を使う。
reflector 有効なのに他 ns に複製されない reflector コントローラー未導入 / namespaceRegex 不一致 / 対象 ns がまだ無い。
cannot convert int64 to string 等の型エラー 通常は発生しない(テンプレートで文字列化済み)。values の該当箇所を確認し Issue へ。
Secret に平文が見える Secret は設計上、参照側から平文で読める。RBAC で get secrets を最小化し、etcd 保存時暗号化を有効化する。
helm lint で metadata.namespace 警告 意図的(クロス ns 配布)。エラーではない。

変更履歴

各リリースの詳細は CLAUDE.md の「チャート改修履歴」を参照。

v0.1.2(2026-09-08)

  • renderMsmtprc の既定を true に変更。mail.providers.<名前> を定義すれば msmtprc キーが常に生成されるようになった(従来は明示 true が必要で、忘れると phpfpm 等の smtp.existingMsmtprcSecret が references non-existent secret key: msmtprc でマウント失敗した)。不要なら renderMsmtprc: false。
  • smtp.tlsVerify(既定 true)を追加。false で msmtprc に tls_certcheck off を出力。
  • 派生キー SMTP_TLS / SMTP_SSL / SMTP_TLS_VERIFY / SMTP_AUTH を Secret に追加 (フレームワーク側が 3 値 enum を解釈しなくて済むよう真偽値文字列で提供)。

v0.1.1(2026-09-08)

  • values.schema.json の $schema 行を削除。新しめの JSON Schema バリデータを内蔵する デプロイ環境(Helm / Argo CD / Flux 等)で no schema with key or ref "https://json-schema.org/draft-07/schema#" エラーが出て デプロイできない問題を修正。helm lint --strict の検証内容は不変。

v0.1.0(2026-08-31)

  • 初版。distribution(namespaces 複製 / reflector / reloader)、common、 mail.providers(SMTP/IMAP/POP3 + smtp.env + 任意 msmtprc)、api.credentials、 generic.secrets(任意 type)を実装。values.schema.json・examples/ を同梱。
  • CI: helm-release.yaml(push 時・未公開バージョンのみリリース)を追加。 Package Registry 発行 + Git タグ + Gitea リリースまで実施。上流イメージが無いため 週次の自動更新ワークフローは持たない。