Files
secret-manager/README.md
T
claude f374cd2e49
Helm Chart Release / release-chart (push) Successful in 6s
Secrets 一元管理チャート secret-manager v0.1.0 を実装
クラスター横断で使う認証情報を1チャートで管理し、複数ネームスペースへ
同一 Secret として配布する仕組みを実装。

- distribution: namespaces への複製 / kubernetes-reflector 自動同期 /
  stakater-Reloader 連携を選択可能
- common: namePrefix・共通ラベル/アノテーション・immutable
- mail.providers: SMTP/IMAP/POP3 のサーバ情報 + パスワードを一元管理。
  source 可能な smtp.env と任意の msmtprc スニペットを Secret に同梱
- api.credentials: 任意のキー/値集合を Opaque Secret 化(envFrom 向け)
- generic.secrets: 任意 type(dockerconfigjson / tls / basic-auth 等)
- values.schema.json による厳格バリデーション
- examples/(Gmail・M365・SendGrid・SES・Mailgun・reflector)
- .gitea/workflows/helm-release.yaml で Package Registry へ自動発行
- README.md にセキュリティモデル・values リファレンス・参照方法を記載
- 平文資格情報の混入防止に .gitignore を整備
2026-08-31 09:41:41 +09:00

333 lines
17 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-の構造)
- [他プロジェクトからの参照方法](#他プロジェクトからの参照方法)
- [代表的なメールプロバイダの設定値](#代表的なメールプロバイダの設定値)
- [変更の反映(伝播)とライフサイクル](#変更の反映伝播とライフサイクル)
- [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`。 |
| `imap.host` ほか | | | 受信 (IMAP) 情報。`host` があれば `IMAP_*` キーを追加。`username`/`password` 省略時は SMTP の値を流用。既定ポート `993`。 |
| `pop3.host` ほか | | | 受信 (POP3) 情報。同上。既定ポート `995`。 |
| `renderMsmtprc` | `bool` | | `true` で Secret に `msmtprc`(msmtp / PHP sendmail 用設定)を含める。 |
| `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_USERNAME=no-reply@example.com
SMTP_PASSWORD=********
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 # ↓ source して使える env ファイル
export SMTP_HOST="smtp.gmail.com"
export SMTP_PORT="587"
...
msmtprc # renderMsmtprc: true のとき。msmtp / PHP sendmail 用
```
### 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 更新時に自動でローリング再起動されます。
## トラブルシューティング
| 症状 | 原因 / 対処 |
|---|---|
| `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.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/`・
Gitea リリースワークフローを同梱。