コンテンツにスキップ

セキュリティ・シークレット

シークレットの 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 つにまとめる)に従う。

/automedia/<tenant>/<provider>   →   JSON { key1: value1, key2: value2, ... }
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-sdkSecretsManagerClient で read → JSON.parse(IAM ロール経由、API キー不要)
ローテーション provider 単位で同時更新(LINE は channel_secretaccess_token を同時発行するので自然)

なぜ provider 単位 JSON か

「同じローテーションサイクル・同じプロバイダ管理画面で発行する値は 1 つにまとめる」 が AWS の公式推奨。具体例:

  • LINE: channel_secretaccess_token は LINE Developers Console の同じ channel から発行され、同じローテサイクルで更新する → 1 secret にまとめる方が運用に合う
  • Backlog: api_keywebhook_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/lineaccess_token key」
  • 取得・再発行手順: どの管理画面でどのアカウントで発行するか、スコープ、有効期限
  • 緊急停止手順: トークン失効・OA 一時停止・ロールバックの操作手順
  • 人間用ログイン情報: 管理画面パスワード等は当面 Wiki が SoT(ブラウザ貼付用途には Secrets Manager の UX が合わない)

取り扱い原則

  1. 平文での DB 保存禁止
  2. ログにトークンを出さない(Lambda 側で構造化ログに include しない、masking layer を挟む)
  3. 権限最小化: Lambda は自分のテナント分のみ参照可能(IAM)
  4. ローテーション: provider 単位の JSON で同時更新。期限が迫ったら CloudWatch Alarm
  5. アクセス監査: シークレット参照は 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
  • アンケート自由記述は要件に応じて取り扱い方針を明文化