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

383 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
---
## 目次
- [なぜ必要か](#なぜ必要か)
- [仕組み](#仕組み)
- [セキュリティモデル](#セキュリティモデル)
- [インストール](#インストール)
- [values リファレンス](#values-リファレンス)
- [distribution — 配布先](#distribution--配布先)
- [common — 共通設定](#common--共通設定)
- [mail — 外部メール接続情報](#mail--外部メール接続情報)
- [api — API 認証情報](#api--api-認証情報)
- [generic — 汎用 Secret](#generic--汎用-secret)
- [生成される Secret の構造](#生成される-secret-の構造)
- [他プロジェクトからの参照方法](#他プロジェクトからの参照方法)
- [代表的なメールプロバイダの設定値](#代表的なメールプロバイダの設定値)
- [変更の反映(伝播)とライフサイクル](#変更の反映伝播とライフサイクル)
- [自動化ワークフロー(Gitea Actions)](#自動化ワークフローgitea-actions)
- [reflector / Reloader 連携](#reflector--reloader-連携)
- [トラブルシューティング](#トラブルシューティング)
---
## なぜ必要か
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 等)を一切作りません。監査が容易です。
## インストール
```bash
# 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
```
```bash
# ローカルのチャートディレクトリから
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 が既にそのネームスペースにある)。
```yaml
# まとめて環境変数へ(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 }
```
```sh
# コンテナ起動時に 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/mail-providers.values.yaml)、
[`examples/api-and-generic.values.yaml`](examples/api-and-generic.values.yaml)、
[`examples/reflector.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](https://github.com/emberstack/kubernetes-reflector) を
クラスターに導入し、`distribution.reflector.enabled: true` にすると、Secret はリリース先
ネームスペースにのみ作られ、`namespaceRegex` に一致する全ネームスペースへ reflector が
複製・追従します。
```yaml
distribution:
namespaces: []
reflector:
enabled: true
namespaceRegex: "prod-.*|shared-services"
```
### stakater/Reloader(参照側の自動再起動)
[stakater/Reloader](https://github.com/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 リリースまで実施。上流イメージが無いため
週次の自動更新ワークフローは持たない。