Secrets Management, 유출 이후가 아니라 유출 전의 설계
시크릿 관리는 값을 숨기는 기능 하나가 아니라, 유출 피해 반경과 회전, 감사, 런타임 주입을 함께 설계하는 문제다. AWS Secrets Manager, Parameter Store, Vault, 멀티클라우드 전이 원리까지 실무 선택 기준과 실패 지점을 중심으로 정리한다.
Script Companion
오디오와 함께 스크립트 보기
- 01
시크릿 관리는 코드에서 비밀번호를 빼내는 정도의 문제가 아니다. git에 한 번 올라간 AWS 키나 DB 비밀번호는 파일을 지워도 히스토리에 남고, 자동화된 봇이 수 분 안에 찾아낼 수 있다. 2019년 Toyota 자회사는 GitHub 공개 저장소에 AWS 키를 실수로 커밋한 뒤 5년치 고객 데이터, 약 3.1만 명 규모를 탈취당했다. 그래서 핵심은 유출 뒤에 git rm을 하는 것이 아니라, 히스토리 rewrite와 자격증명 즉시 교체까지 포함한 대응 체계를 미리 갖추는 것이다.
- 02
이 위험은 작은 실수가 아니라 대규모 자동화 공격의 대상이 된다. 2024년 8월 Nemesis와 ShinyHunters는 2.68억 개 IP 주소를 자동 스캔해 노출된 .env 파일과 설정 파일에서 AWS Access Key, GitHub 토큰, Twilio API Key, DB 비밀번호를 수집했다. 피해 데이터는 2TB 이상이었고, Palo Alto Networks는 별도 조사에서 9만 개 이상의 고유 환경변수가 노출된 .env 파일에서 유출되었다고 확인했다. 2025년 1월에는 Codefinger가 유출된 AWS 자격증명으로 S3 버킷을 SSE-C로 암호화한 뒤 복호화 키를 요구하는 공격까지 등장했다.
- 03
GitGuardian State of Secrets Sprawl 2025가 보고한 수치도 같은 방향을 가리킨다. 2024년 공개 GitHub에 노출된 시크릿은 23.8M 건으로 전년 대비 25% 증가했고, 2022년에 노출된 시크릿의 70%가 4년 가까이 지난 시점에도 여전히 유효했다. 여기서 중요한 결론은 유출이 일시적 사고가 아니라 영구 자산화된다는 점이다. 새로 push된 AWS Key에 수 분 내 첫 자동 접근이 발생할 수 있다면, 시크릿은 코드 리뷰 뒤에 챙길 부속물이 아니라 플랫폼의 기본 경계로 다뤄야 한다.
- 04
환경변수만 쓰면 안전하다고 생각하기 쉽지만, 문서가 강조하는 노출 경로는 꽤 구체적이다. 프로세스 인자에 비밀번호를 넘기면 ps aux에서 보일 수 있고, 같은 호스트의 다른 프로세스가 proc의 environ을 읽으면 환경변수가 평문으로 드러날 수 있다. 크래시 덤프나 스택 트레이스에 환경 전체가 포함되어 Node.js process.env 출력이나 Sentry, CloudWatch Logs에 남는 경우도 있다. Kubernetes에서는 Pod spec의 env가 encryption-at-rest 미설정 시 etcd에 평문으로 저장되어, 백업이나 node 침해가 클러스터 전체 시크릿 유출로 이어질 수 있다.
- 05
시크릿은 먼저 정적 시크릿과 동적 시크릿으로 나누어 이해하면 좋다. API Key와 DB 비밀번호 같은 정적 시크릿은 만료 없이 영구 유효하므로 유출되면 즉시 교체해야 한다. 반대로 STS 임시 토큰이나 Vault가 생성하는 DB 자격증명 같은 동적 시크릿은 TTL이 있어 시간이 지나면 자동 만료된다. 피해 반경도 종류마다 다르다. AWS Access Key는 계정 전체 탈취로 이어질 수 있고, DB 비밀번호는 해당 DB 데이터 노출, OAuth Token은 연동 서비스의 사용자 데이터 접근, TLS 인증서 Private Key는 MITM 가능성, SSH Key는 서버 접근으로 이어진다.
- 06
AWS Secrets Manager의 기본 구조는 시크릿을 금고에 넣고, 꺼낼 때마다 권한을 확인하는 흐름이다. 문서의 비유대로 시크릿은 은행 금고에 해당하는 KMS와, 접근을 허가하는 도어맨에 해당하는 IAM 사이에서 관리된다. 핵심은 키와 시크릿을 분리하는 것이다. 저장 방식은 Envelope Encryption, 즉 봉투 암호화다. 실제 저장값은 암호화된 데이터 키와 암호화된 시크릿 값의 조합이며, KMS 자체가 시크릿 값을 아는 것이 아니라 데이터 키만 암호화하고 복호화한다.
- 07
자동 교체는 Secrets Manager를 단순 저장소가 아니라 운영 도구로 만드는 지점이다. Rotation은 Lambda 기반 4단계 흐름인 createSecret, setSecret, testSecret, finishSecret으로 진행된다. 교체 로직은 RDS, Redshift, Elasticache처럼 대상 서비스마다 다르기 때문에 Lambda로 커스터마이징한다. 무중단을 위해 버전 스테이지도 중요하다. AWSCURRENT와 AWSPREVIOUS가 동시에 유효하면 교체가 끝나기 전 기존 DB 연결이 이전 값으로도 유지될 수 있다. 비용은 시크릿 저장 $0.40/개/월, API 호출 $0.05/10,000건, 교체용 Lambda 요금 별도라는 구조다.
- 08
Secrets Manager에도 공식 quota와 실패 지점이 있다. Secret value 크기는 65,536 bytes라서 TLS 인증서 chain, private key, intermediate를 한 시크릿에 묶으면 PutSecretValue가 ValidationException으로 거부될 수 있다. Versions per secret은 100개이고, PutSecretValue나 UpdateSecret은 10분당 1회 이하가 권고된다. 24시간 미만 버전은 정리되지 않으므로 회전이 너무 잦으면 다음 회전이 조용히 실패할 수 있다. GetSecretValue는 10,000 RPS지만 BatchGetSecretValue는 100 RPS라서 batch가 항상 빠르다는 가정도 깨진다.
- 09
따라서 쓰지 말아야 할 경우도 분명하다. 시크릿 하나를 초당 10,000회 이상 읽는 워크로드는 SDK로 매번 조회하면 throttle이 나므로 클라이언트 캐싱이나 노드 로컬 sidecar 캐시가 필요하다. 64KB를 넘는 binary blob은 S3와 SSE-KMS로 분리하고 Secrets Manager에는 S3 경로만 저장하는 편이 맞다. 분당 1회 이상 회전이 필요한 워크로드는 version quota 100개에 빠르게 도달할 수 있으므로 STS나 OAuth dynamic 같은 자체 토큰 발급 시스템을 검토해야 한다. 선택 기준은 기능 유무가 아니라 호출 빈도, 값의 크기, 회전 주기다.
- 10
Parameter Store는 Secrets Manager와 다른 선택지다. 문서의 비유로는 Secrets Manager가 자동 교체와 버저닝이 있는 강화 금고라면, Parameter Store는 열쇠 달린 서랍장에 가깝다. String은 공개 설정값, StringList는 쉼표 구분 목록, SecureString은 KMS로 암호화한 시크릿 값에 쓴다. Standard는 무료이고 최대 4KB, 스루풋 40 TPS이며, Advanced는 $0.05/파라미터/월, 최대 8KB, 파라미터 정책과 100 TPS를 제공한다. 자동 교체가 필요하면 Secrets Manager, 단순 저장과 낮은 비용이 우선이면 Parameter Store가 더 맞을 수 있다.
- 11
런타임 주입 패턴의 원칙은 시크릿을 코드나 이미지에 넣지 않는 것이다. ECS에서는 Task Definition의 secrets 필드로 Secrets Manager나 Parameter Store를 참조하고, ECS 에이전트가 태스크 시작 시 값을 가져와 컨테이너 환경변수로 주입한다. 애플리케이션 코드는 process.env.DB_PASSWORD처럼 읽지만, 실제 값은 IAM Role 권한으로 런타임에 조회된다. CI/CD에서도 GitHub Actions에 AWS 자격증명을 직접 저장하기보다 OIDC로 AssumeRole하고 배포용 시크릿을 동적으로 가져오는 흐름이 문서의 방향이다. 환경별로는 dev/*와 prod/*를 나누어 IAM 정책으로 접근을 제한한다.
- 12
Vault는 AWS만 쓰는 환경에서는 꼭 필요하지 않을 수 있지만, 온프레미스와 멀티클라우드에서는 다른 역할을 한다. 가장 큰 차별점은 동적 시크릿으로, DB에 접근할 때마다 고유한 임시 계정을 만들고 사용 후 자동 삭제할 수 있다는 점이다. AWS, GCP, Azure, HashiCorp Vault로 플랫폼이 바뀌어도 핵심 원리는 이어진다. Envelope Encryption은 KMS CMK, Cloud KMS Key Ring, Key Vault Key, Transit Secret Engine처럼 구현체만 달라진다. Dynamic Secret은 AWS STS AssumeRole, GCP Workload Identity Federation, Azure Managed Identity, K8s Projected Volume Token, Vault Database Secrets Engine에서 유사한 원리가 보인다.
- 13
운영에서 자주 만나는 실패는 증상과 원인을 분리해 봐야 한다. AccessDeniedException이 나면 ECS Task Role, Lambda 실행 역할, IAM 사용자에 secretsmanager:GetSecretValue 권한이 있는지와 리소스 ARN이 맞는지 확인해야 하며, CMK를 쓰면 kms:Decrypt도 필요하다. ECS 태스크가 unable to pull secrets로 시작하지 못하면 Private 서브넷에서 Secrets Manager 엔드포인트에 닿지 못하는 경우가 많아 NAT Gateway나 VPC Endpoint가 필요하다. AWSPENDING에 고착되면 Rotation Lambda의 setSecret이나 testSecret 단계에서 타임아웃, DB 접속 실패, rotation token 누락을 의심해야 한다.
- 14
프론트엔드 경험과도 직접 이어진다. React에서 REACT_APP_API_KEY 값을 .env에 넣고 빌드하면 그 값은 번들된 JavaScript 파일에 그대로 포함되고, 브라우저 개발자 도구의 Sources 탭에서 누구나 볼 수 있다. 민감한 정보는 클라이언트로 내려가면 안 되며, API Key가 필요하다면 NestJS 백엔드가 프록시 역할을 하도록 구조를 바꿔야 한다. 정리하면 시크릿은 코드와 분리하고, 접근은 IAM Role로 통제하며, 회전은 Lambda 흐름과 버전 스테이지를 이해한 상태에서 설계해야 한다. CloudTrail로 접근을 추적하고, Private 서브넷에서는 VPC Endpoint까지 함께 봐야 한다.
같은 레이어