d8a80f6272
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해 끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은 과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업. 도구: Datacollect/scripts/link_reconcile_apply.mjs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.6 KiB
5.6 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-secret-management | Secret Management | 10_Wiki/Topics | verified | self |
|
none | A | 0.95 | applied |
|
2026-05-10 | pending |
|
Secret Management
매 한 줄
"매 secret 은 매 git 에 절대 — 매 vault 에". Secret management 는 매 API key, DB password, certificate, signing key 의 매 lifecycle (issue, store, rotate, revoke, audit) 의 매 centralized control. 2026 현재 매 HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Doppler, Infisical 가 매 dominant; 매 SPIFFE/SPIRE workload identity, 매 short-lived (15min) tokens 가 매 long-lived API key 를 매 replace.
매 핵심
매 Anti-secrets
- Hardcoded in source.
- Plain in
.envcommitted. - Shared via Slack DM.
- Long-lived (years) static API keys.
매 Pillars
- Encryption at rest: KMS-backed.
- Encryption in transit: TLS-only.
- Access control: RBAC + audit log.
- Rotation: automated (DB pwd, KMS key).
- Workload identity: 매 service ≠ user — 매 ephemeral token 의 매 cloud IAM.
- Detection: 매 git pre-commit (gitleaks, trufflehog) + 매 GitHub secret scanning.
매 응용
- App → DB: dynamic creds.
- CI → cloud: OIDC federation, no static keys.
- K8s pod → AWS: IRSA / Workload Identity.
- Cross-service: SPIFFE SVID.
💻 패턴
Vault dynamic DB cred
vault write database/roles/app-readonly \
db_name=postgres-prod \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl=1h max_ttl=24h
# App requests cred
vault read database/creds/app-readonly
# username: v-token-app-readonly-x9a..., password: A1b2C3..., lease_id: ..., lease_duration: 3600
GitHub Actions OIDC → AWS (no static keys)
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/github-deploy
aws-region: us-east-1
- run: aws s3 sync ./build s3://prod-bucket/
Pre-commit secret scan
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
App-side fetch with caching
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const sm = new SecretsManagerClient({});
const cache = new Map<string, { value: any; expires: number }>();
async function getSecret(name: string): Promise<any> {
const cached = cache.get(name);
if (cached && cached.expires > Date.now()) return cached.value;
const res = await sm.send(new GetSecretValueCommand({ SecretId: name }));
const value = JSON.parse(res.SecretString!);
cache.set(name, { value, expires: Date.now() + 5 * 60_000 });
return value;
}
K8s External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: db-creds }
spec:
refreshInterval: 1h
secretStoreRef: { name: vault-backend, kind: ClusterSecretStore }
target: { name: db-creds }
data:
- secretKey: password
remoteRef: { key: database/creds/app, property: password }
Rotation Lambda
export async function rotateApiKey(event) {
const step = event.Step;
if (step === "createSecret") {
const newKey = await crypto.randomUUID();
await sm.putSecretValue({ SecretId: event.SecretId, ClientRequestToken: event.ClientRequestToken, SecretString: newKey, VersionStages: ["AWSPENDING"] });
} else if (step === "setSecret") { /* configure target */ }
else if (step === "testSecret") { /* test */ }
else if (step === "finishSecret") {
await sm.updateSecretVersionStage({ SecretId: event.SecretId, VersionStage: "AWSCURRENT", MoveToVersionId: event.ClientRequestToken });
}
}
매 결정 기준
| 상황 | Tool |
|---|---|
| 매 multi-cloud, 매 self-host | HashiCorp Vault |
| 매 AWS-only | Secrets Manager + Parameter Store |
| 매 dev-friendly UX | Doppler / Infisical |
| 매 K8s | External Secrets Operator + cloud KMS |
| 매 workload-to-workload | SPIFFE/SPIRE |
기본값: Cloud-native (Secrets Manager) + OIDC for CI + ESO for K8s.
🔗 Graph
- 부모: DevSecOps_Framework · Application Security
- 변형: KMS · PKI
- 응용: CI_CD_Pipeline · 보안 및 시스템 신뢰성 표준
- Adjacent: 보안 및 시스템 신뢰성 표준 · OAuth 2.0
🤖 LLM 활용
언제: Secret-scanner triage (매 actual secret vs 매 test fixture?), rotation runbook generation, IAM policy synthesis from natural-language requirement. 언제 X: 매 secret 자체를 매 LLM context 에 매 넣지 마. 매 leak risk.
❌ 안티패턴
.envin git: 매 even private repo — 매 contributor leak.- Long-lived keys: 매 5-year IAM access key — 매 incident blast-radius huge.
- Shared service account: 매 audit trail 의 매 useless.
- Plain ENV var visible to all containers: 매 sidecar / multi-tenant — 매 leak.
🧪 검증 / 중복
- Verified (NIST SP 800-57, OWASP ASVS V6, CIS Benchmarks).
- 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — OIDC federation + workload identity 2026 |