학습 목표
시크릿(비밀번호·API 키·토큰·TLS key)을 코드·Git·로그에서 분리하는 이유를 설명할 수 있다.
환경변수 주입의 장단점과 K8s Secret 한계(base64·etcd)를 설명할 수 있다.
Vault·클라우드 Secret Manager·External Secrets Operator 패턴을 설명할 수 있다.
Rotation·least privilege·audit이 시크릿 수명주기에서 맡는 역할을 설명할 수 있다.
앞서 Platform ConfigMap·Secret과 Security 관점 연계를 설명할 수 있다.
문제 상황
const API_KEY = "sk-live-..."— GitHub public push 한 번에 키 폐기.env를 커밋 — history에 영구 남음 (git filter-repo필요)- K8s Secret YAML을 repo에 평문 — base64는 인코딩일 뿐
kubectl logs에DB_PASSWORD=...출력- dev·staging·prod 같은 DB root 비밀번호
- 퇴사자 개인 API key — 회수·로테이션 프로세스 없음
- CI에 kubeconfig 평문 variable — fork PR에서 탈취
Part 4 운영 보안 시작 — OWASP·접근 제어 다음은 자격증명이 새는 경로를 막는다.
본 글의 예제 값은 가짜이다. 유출된 키는 즉시 폐기·재발급한다.
1. 시크릿이란
시크릿 — 인증·암호화·API 호출에 쓰이는 비밀 (CWE-798 Hard-coded Credentials).

| 종류 | 예 |
|---|---|
| Password | DB·admin |
| API key / token | Stripe, AWS, SaaS |
| Private key | TLS, JWT signing, SSH |
| Connection string | postgres://user:pass@... |
| Encryption key | data at rest, pepper |
시크릿 ≠ 설정
LOG_LEVEL=info → ConfigMap
DB_PASSWORD=... → Secret manager / K8s Secret
- 앞서 least privilege — 시크릿도 범위·TTL 최소
- 앞서 misconfiguration — default cred·debug가 시크릿 유출과 겹침
2. 하면 안 되는 것
# 취약 — code/hardcoded-secret-vulnerable.example
STRIPE_KEY = "sk_live_51AbCdEf..."
conn = psycopg2.connect("postgresql://admin:s3cr3t@db/prod")
| 안티패턴 | 위험 |
|---|---|
| 소스·이미지에 박기 | repo·registry 영구 |
| Git 평문 | history·fork·CI log |
| Slack/티켓 붙여넣기 | 검색·백업 유출 |
| 클라이언트(SPA) | 누구나 읽음 |
| 로그·에러 | Observability 전파 |
| 동일 secret dev/prod | dev 유출 → prod 침해 |
# code/gitignore-secrets.example
.env
.env.*
*.pem
secrets/
kubeconfig
- pre-commit — gitleaks, truffleHog, detect-secrets
- 유출 시 rotate first — "나중에" ❌
3. 저장 계층

| 계층 | 적합 | 한계 |
|---|---|---|
| env / .env (local) | 개발자 로컬 | Git·백업 유출 |
| CI secret variable | pipeline | scope·rotation 수동 |
| K8s Secret | Pod 주입 | base64·etcd — 앞서 Platform |
| Vault / AWS SM / GCP SM | 중앙·audit·rotation | 운영 복잡도 |
| HSM / Cloud KMS | signing key | 비용·latency |
권장 흐름:
Source of truth (Vault/SM) → sync → K8s Secret → env/volume → app
- K8s Secret — 전달 수단; 마스터는 vault·SM (앞서 Platform ConfigMap·Secret 주입)
- etcd encryption at rest — K8s 2차 방어
4. Vault 개요
HashiCorp Vault — 시크릿 저장·동적 발급·audit (개념은 AWS Secrets Manager·GCP Secret Manager와 유사).

| 기능 | 설명 |
|---|---|
| Static secret | KV — API key 버전 |
| Dynamic secret | DB 임시 user·TTL |
| Encryption as a service | transit — 앱이 키 보관 안 함 |
| Policy | path·operation 최소 |
| Audit log | 누가 언제 read |
# 개념 — code/vault-kv.example
vault kv put secret/myapp/db password='...' username='app'
vault kv get -field=password secret/myapp/db
# 앱 startup — short-lived token or sidecar (발췌)
# code/vault-app-init.example
import os
import hvac
client = hvac.Client(url=os.environ["VAULT_ADDR"], token=os.environ["VAULT_TOKEN"])
secret = client.secrets.kv.v2.read_secret_version(path="myapp/db")
os.environ["DB_PASSWORD"] = secret["data"]["data"]["password"]
| 패턴 | |
|---|---|
| Init container / sidecar | Vault Agent — token 갱신 |
| AppRole / K8s auth | Pod identity → Vault role |
| Never root token in prod | policy 분리 |
- 앞서 Cloud IAM·workload identity — Pod → Vault 인증 (시리즈 후반 확장)
- dynamic DB cred — SQL 장기 password 불필요
5. Kubernetes 연계
Platform에서 ConfigMap·Secret env/volume 주입을 봤다. Security는 Git·repo에 Secret 없게.
# External Secrets Operator — code/external-secret.example
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: api-db
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: api-db
data:
- secretKey: DB_PASSWORD
remoteRef:
key: secret/myapp/db
property: password
| 도구 | 역할 |
|---|---|
| External Secrets Operator | Vault/SM → K8s Secret 동기화 |
| Sealed Secrets | 공개 Git에 암호화 Secret |
| SOPS | YAML age/KMS 암호화 |
| cert-manager | TLS Secret 자동 (Platform Ingress) |
# SealedSecret — cluster public key로 seal, Git OK
# kubeseal --format yaml < secret.yaml > sealed-secret.yaml
| 규칙 | |
|---|---|
| Git | Sealed/External/SOPS — 평문 Secret 금지 |
| RBAC | secrets get/list 최소 |
| namespace | prod Secret dev 복사 ❌ |
| rotation | ESO refreshInterval + rollout |
- CI/CD kubeconfig — OIDC·short-lived token (Platform CI/CD)
- projected volume — SA token + Secret 한 mount
6. Rotation과 폐기
| 단계 | 내용 |
|---|---|
| Inventory | 어떤 key가 어디 쓰이는지 |
| Dual active | 구·신 key 동시 유효 기간 |
| Deploy | 새 Secret → rollout |
| Revoke old | Vault/SM disable version |
| Break-glass | emergency root — 감사·즉시 rotate |
Rotation trigger:
scheduled (90d) · employee offboard · suspected leak · CVE on key material
- 앞서 JWT — signing key rotate → kid·JWKS
- DB password — connection pool drain 후 old user drop
- audit — Secret read 이벤트 (로깅·감사 편)
7. CI/CD·로컬
| 위치 | 권장 |
|---|---|
| GitHub Actions | secrets.* — masked, environment approval |
| GitLab | protected variables, environment scope |
| 로컬 | .env gitignore, 1Password·direnv |
| Docker build | ARG secret mount — BuildKit secret, ENV에 남기지 않음 |
# GitHub Actions — masked secret (발췌)
# code/ci-secret-usage.example
env:
KUBECONFIG_DATA: ${{ secrets.KUBECONFIG_DATA }}
# never echo secrets; fork PR — secrets unavailable by default
- PR from fork — secret 없음 — workflow 설계 주의
- artifact·test report에 env dump 금지
8. 체크리스트
| 항목 | 확인 |
|---|---|
| Source | Vault/SM single source of truth |
| Git | gitleaks CI · no .env commit |
| K8s | External Secrets / Sealed · etcd encrypt |
| App | no log of secrets · no client-side keys |
| Scope | dev/stage/prod 분리 |
| Rotation | SLA·offboard runbook |
| Access | RBAC·policy read audit |
| 안티패턴 | |
|---|---|
| base64 = secure | 디코드 trivial |
| ConfigMap에 password | Secret 계열 |
| shared team password | 개인·service account |
| infinite-lived API key | TTL·rotate |
9. 정리
- 시크릿 — 코드·Git·로그·클라이언트에서 분리
- K8s Secret — 주입; 마스터는 Vault/SM + GitOps 암호화
- Rotation·least privilege·audit — 유출 가정 설계
- Platform ConfigMap·Secret 주입 + Security 저장·유출 방지
다음에 다룰 것
- API 보안
- rate limit, input validation, CORS
해당 내용은 OWASP Secrets Management Cheat Sheet, HashiCorp Vault Documentation, Kubernetes Documentation (Secrets), CWE-798 (Use of Hard-coded Credentials) 의 내용을 기반으로 합니다.
'애플리케이션 보안' 카테고리의 다른 글
| 로깅과 감사 (0) | 2026.07.02 |
|---|---|
| API 보안 (0) | 2026.07.01 |
| OWASP Top 10 (0) | 2026.06.29 |
| Broken Access Control (0) | 2026.06.28 |
| XSS와 CSRF (0) | 2026.06.27 |