セキュリティ・シークレット¶
シークレットの SoT (Source of Truth) は AWS Secrets Manager(spin-dd account,
ap-northeast-1)。provider 単位の JSON 形式 で管理し、テナント別 prefix/automedia/<tenant>/<provider>に格納する。各 Lambda は IAM ロール経由で provider 単位の secret を read →JSON.parseで値を取り出す。Claude API キーは 不要(Claude Platform on AWS の IAM 統合)。
命名規約¶
1 provider = 1 secret = 1 JSON object。AWS Secrets Manager のベストプラクティス(関連する値を 1 つにまとめる)に従う。
| Secret name | JSON keys |
|---|---|
/automedia/<tenant>/line |
channel_id, channel_secret, access_token |
/automedia/<tenant>/hubspot |
access_token (Private App), portal_id? |
/automedia/<tenant>/backlog |
space_key, api_key, webhook_secret |
/automedia/_meta/github |
webhook_secret, pat? |
? = optional。
管理対象シークレット¶
| 用途 | secret | 含まれる JSON keys |
|---|---|---|
| LINE 配信 / Webhook 検証 | /automedia/<tenant>/line |
channel_id / channel_secret(Webhook 検証) / access_token(Reply / Push / Broadcast) |
| HubSpot CMS Blog Post | /automedia/<tenant>/hubspot |
access_token(Private App、scope: content) / portal_id(任意、監査用) |
| Backlog Webhook / API | /automedia/<tenant>/backlog |
space_key / api_key / webhook_secret(URL path token として使う) |
| GitHub sync (Webhook + Raw fetch) | /automedia/_meta/github |
webhook_secret / pat(private repo の Raw fetch 用) |
不要になったもの:
- ANTHROPIC_API_KEY: Claude Platform on AWS の IAM 統合で不要
- DISPATCH_PAT:
repository_dispatchを使わない (Phase 1b で API Gateway + Lambda に統合) - CLOUDFLARE_API_TOKEN: Cloudflare Workers relay を使わない
GH Actions 側に残るもの:
- AWS OIDC Role ARN(automedia リポ
vars.AWS_DEPLOY_ROLE_ARN、変数、機密ではない)
なぜ AWS Secrets Manager か¶
| 観点 | 評価 |
|---|---|
| 新規 SaaS アカウント | 不要(既存 spin-dd AWS account 内) |
| 月額コスト | $0.40/secret/月 × ~10 secrets = ~$4/月(JSON 形式で個別キー方式比 ⅓ に圧縮) |
| Runtime モデルとの整合 | 完全に整合。Lambda 群が IAM ロールで read |
| 監査ログ | CloudTrail に集約 |
| 複数運用者の共有 | IAM ユーザー / ロール単位で grant |
| Lambda 連携 | aws-sdk の SecretsManagerClient で read → JSON.parse(IAM ロール経由、API キー不要) |
| ローテーション | provider 単位で同時更新(LINE は channel_secret と access_token を同時発行するので自然) |
なぜ provider 単位 JSON か¶
「同じローテーションサイクル・同じプロバイダ管理画面で発行する値は 1 つにまとめる」 が AWS の公式推奨。具体例:
- LINE:
channel_secretとaccess_tokenは LINE Developers Console の同じ channel から発行され、同じローテサイクルで更新する → 1 secret にまとめる方が運用に合う - Backlog:
api_keyとwebhook_secretは同じ space に紐付き、同じ運用画面で扱う - HubSpot: Private App 単位で 1 access_token が発行される
provider 単位より細かい粒度(key 単位)にすると secret 数が膨らみコスト増 + ローテーション漏れリスクが上がる。逆に粗い粒度(tenant 単位 = 全 provider 1 JSON)にすると IAM 粒度がテナント単位までしか取れない。→ provider 単位 がバランスとしてベスト。
配置¶
AWS Secrets Manager (spin-dd account, ap-northeast-1)
├── /automedia/_meta/github
│ { webhook_secret, pat }
├── /automedia/spindd/line
│ { channel_id, channel_secret, access_token }
├── /automedia/spindd/hubspot
│ { access_token, portal_id }
├── /automedia/spindd/backlog
│ { space_key, api_key, webhook_secret }
├── /automedia/bato/line
│ { channel_id, channel_secret, access_token }
├── /automedia/bato/hubspot
│ { access_token, portal_id }
├── /automedia/bato/backlog
│ { space_key, api_key, webhook_secret }
└── /automedia/<tenant>/...
キー命名規約: /automedia/<tenant>/<provider>(共通は /automedia/_meta/<provider>)。IAM ポリシーで tenant 別アクセス制限。
IAM ポリシー(テナント分離)¶
# aws/tofu/iam.tf
data "aws_iam_policy_document" "secrets_read" {
statement {
effect = "Allow"
actions = ["secretsmanager:GetSecretValue"]
resources = ["arn:aws:secretsmanager:${var.region}:*:secret:${local.secrets_prefix}/*"]
}
}
各 Lambda Role はこの secrets_read を共有 + 用途別 statement を追加。詳細は Claude Code ランタイムモデル §リソース命名規約 を参照。
Lambda での利用例¶
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const sm = new SecretsManagerClient({});
const SECRETS_PREFIX = process.env.SECRETS_PREFIX ?? '/automedia';
async function getProviderSecret<T>(tenant: string, provider: string): Promise<T> {
const id = `${SECRETS_PREFIX}/${tenant}/${provider}`;
const res = await sm.send(new GetSecretValueCommand({ SecretId: id }));
if (!res.SecretString) throw new Error(`Secret ${id} has no SecretString`);
return JSON.parse(res.SecretString) as T;
}
// 利用
interface LineSecrets {
channel_id: string;
channel_secret: string;
access_token: string;
}
const line = await getProviderSecret<LineSecrets>('spindd', 'line');
verifyLineSignature(event, line.channel_secret);
await lineBroadcast({ accessToken: line.access_token, messages });
Lambda コンテナ再利用で getProviderSecret の結果は実質的に短期キャッシュされる(コンテナ生存中)。
ローテーション¶
- 既定 90 日(CloudWatch Alarm で期限通知)
- provider 単位で JSON 内の全 key を同時に更新 する(部分 update はできない)
- LINE:
channel_secretを回す時はaccess_tokenも同時に再発行が必要 → 自然な運用 - ローテ履歴は CloudTrail + Secrets Manager のバージョン履歴 で追跡
誤コミット防止¶
- GitHub org で secret scanning + push protection を有効化(暗号化前の平文流入を検知)
.gitignoreで*.secrets.json等の慣用名を除外- pre-commit hook で平文トークンの commit を拒否
開発(ローカル)¶
- 本番 secret をローカルに降ろさない
- 開発専用 IAM ユーザーに
secretsmanager:GetSecretValue限定で grant - 取得例:
aws secretsmanager get-secret-value \
--secret-id /automedia/spindd/line \
--profile spindd --region ap-northeast-1 \
| jq -r .SecretString | jq
- ローカル Lambda テスト時は
AWS_PROFILE=spinddで SM から直接読む
Wiki の役割(Backlog TTRTAGSPIN)¶
Wiki は runtime トークンの値を持たない。代わりに以下を持つ:
- ポインタ: 「LINE spindd channel token は AWS Secrets Manager
/automedia/spindd/lineのaccess_tokenkey」 - 取得・再発行手順: どの管理画面でどのアカウントで発行するか、スコープ、有効期限
- 緊急停止手順: トークン失効・OA 一時停止・ロールバックの操作手順
- 人間用ログイン情報: 管理画面パスワード等は当面 Wiki が SoT(ブラウザ貼付用途には Secrets Manager の UX が合わない)
取り扱い原則¶
- 平文での DB 保存禁止
- ログにトークンを出さない(Lambda 側で構造化ログに include しない、masking layer を挟む)
- 権限最小化: Lambda は自分のテナント分のみ参照可能(IAM)
- ローテーション: provider 単位の JSON で同時更新。期限が迫ったら CloudWatch Alarm
- アクセス監査: シークレット参照は CloudTrail に自動記録
認可¶
| 操作 | 認可方式 |
|---|---|
| Backlog Webhook 受信 | URL path token (/backlog/webhook/<token>) を Lambda 側で timing-safe 比較 (Backlog plan が webhook secret 非対応のため) |
| GitHub Webhook 受信 | X-Hub-Signature-256 検証 (/automedia/_meta/github.webhook_secret) |
| LINE Webhook 受信 | X-Line-Signature 検証 (/automedia/<tenant>/line.channel_secret) |
| API Gateway → Lambda | aws_lambda_permission で API Gateway を principal に許可 |
| Lambda → Secrets Manager | IAM ロール |
| Lambda → Claude Platform on AWS | IAM ロール (aws-external-anthropic:CreateInference on workspace ARN + sts:GetWebIdentityToken on self) |
RBAC(最小セット)¶
| ロール | 権限 |
|---|---|
| viewer | 課題・結果の閲覧 |
| operator | 起票・編集 |
| approver | 承認・キャンセル |
| admin | テンプレ管理・チャネルアカウント管理・シークレット参照 |
個人情報¶
- 友だち ID / フォロワー ID は外部 ID として扱う
- 個人特定可能データ(氏名・連絡先)は automedia DB に置かない(HubSpot SoT)
- アンケート自由記述は要件に応じて取り扱い方針を明文化