--- id: security-secrets-management title: Secrets Management — 코드 / 로그 / repo 에 안 새게 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [security, secrets, env, vault, vibe-coding] tech_stack: { language: "Any", applicable_to: ["Backend", "DevOps"] } applied_in: [] aliases: [env vars, vault, secret rotation, gitleaks] --- # Secrets Management > 비밀은 (1) 코드에 절대 X, (2) 환경 변수 또는 secret manager, (3) 로그 / 응답 / error 에 redact, (4) 정기 rotate. **`.env.example` 만 commit, `.env` 는 gitignore**. ## 📖 핵심 개념 - 비밀: API key, JWT secret, DB password, OAuth client secret, SMTP password, signing key. - 환경 분리: dev / staging / prod 각각 다른 값. - Rotation: 정기 변경. compromise 시 즉시. - Audit: 누가 언제 access 했는지. ## 💻 코드 패턴 ### 환경 변수 + 부팅 시 검증 ```ts import { z } from 'zod'; const Env = z.object({ DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), STRIPE_SECRET_KEY: z.string().regex(/^sk_(test|live)_/), AWS_ACCESS_KEY_ID: z.string().optional(), AWS_SECRET_ACCESS_KEY: z.string().optional(), REDIS_URL: z.string().url(), }); export const env = Env.parse(process.env); // 부팅 시 누락 secret 발견 → 즉시 종료. silent failure 방지. ``` ### .env 가족 ``` .env # 로컬, gitignore .env.example # 키 이름만, 값은 placeholder, commit .env.production # 절대 commit X. CI/Vault 에서 주입 ``` `.gitignore`: ``` .env .env.* !.env.example ``` ### Cloud secret manager ```ts // AWS Secrets Manager import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; const sm = new SecretsManagerClient({}); const res = await sm.send(new GetSecretValueCommand({ SecretId: 'prod/db' })); const secret = JSON.parse(res.SecretString!); // 또는 부팅 시 secret manager → process.env 주입 ``` ### Logger redact (pino) ```ts import pino from 'pino'; export const logger = pino({ redact: { paths: [ 'req.headers.authorization', 'req.headers.cookie', '*.password', '*.token', '*.secret', '*.creditCard', 'env.JWT_SECRET', ], censor: '[REDACTED]', }, }); ``` ### Pre-commit — gitleaks ```yaml # .pre-commit-config.yaml repos: - repo: https://github.com/zricethezav/gitleaks rev: v8.18.0 hooks: - id: gitleaks ``` ### Rotation 자동화 ```ts // Stripe key rotation 예시 const KEYS = [process.env.STRIPE_SECRET_KEY_NEW, process.env.STRIPE_SECRET_KEY_OLD].filter(Boolean); // 새 키 deploy 전후 짧은 기간 둘 다 valid. ``` ## 🤔 의사결정 기준 | 환경 | 저장 | |---|---| | 로컬 dev | `.env` (gitignore) | | CI | CI 자체 secret store (GitHub Actions Secrets, GitLab CI variables) | | Cloud (AWS/GCP/Azure) | Secrets Manager / Parameter Store + IAM | | Kubernetes | Sealed Secrets / External Secrets Operator | | 클라이언트 (모바일 / SPA) | API key 노출 금지 → BFF 또는 OAuth flow | | Edge function | 환경 변수 + 짧은 TTL key | ## ❌ 안티패턴 - **하드코딩**: `const KEY = "sk_live_..."`. git history 영구 노출. - **`.env` commit**: 즉시 공개. rotate 필요. - **Slack / 이메일로 비밀 전송**: 평문 보관. 사용 후도 남음. - **모든 사람이 prod secret 접근**: 최소 권한 원칙. break-glass 절차. - **rotation 없음**: 한 번 leak 후 영구 노출. - **secret 을 응답에 echo**: error 메시지에 connection string. catch 후 generic. - **client-side env (`NEXT_PUBLIC_*`) 에 진짜 secret**: 번들에 들어가 누구나 봄. - **build-time 에 inline**: 빌드 아티팩트에 박힘. ## 🤖 LLM 활용 힌트 - "secret 은 항상 process.env. 부팅 시 zod parse" 강제. - 클라이언트 코드에 secret 보내지 마라 — BFF 패턴. - gitleaks pre-commit + CI gate. ## 🔗 관련 문서 - [[Security_Input_Validation]] - [[Web_JWT_Patterns]]