콘텐츠로 이동

Secrets Management

분류: Layer 3 - AWS 인프라 & 보안

Secrets Management는 API 키, DB 비밀번호, 토큰, private key처럼 알면 권한을 얻는 값을 코드와 분리해 저장하고, 필요한 워크로드에만 주입하고, 유출되면 빠르게 교체할 수 있게 만드는 체계다.

여기서 secret(시크릿)은 “민감한 문자열”보다 좁고 실용적인 개념이다. secret은 시스템이 다른 시스템에 자신을 증명하거나, 암호화된 데이터를 풀거나, 보호된 API를 호출하는 데 쓰는 값이다. 그래서 secret이 유출되면 단순 정보 노출이 아니라 권한 위임 사고가 된다.

처음 읽을 때 붙잡을 문장은 이것이다.

secret은 저장할 값이 아니라 접근 권한이다.
Secrets Management는 그 권한의 저장 위치, 접근 주체, 교체 방법, 감사 기록을 설계하는 일이다.

핵심 용어를 먼저 정의한다.

용어첫 정의처음 잡을 경계
Secret알면 인증·인가·복호화가 가능한 값이다. DB password, API key, OAuth client secret, private key가 여기에 속한다.공개 설정값이나 feature flag와 다르다. 노출되면 권한이 생기는 값이어야 한다.
Static Secret만료 시간이 없거나 길게 유지되는 secret이다.유출되면 사람이 revoke 또는 rotate하기 전까지 계속 살아 있다.
Dynamic Secret요청 시점에 짧은 TTL(Time To Live)로 발급되고 만료되는 secret이다.저장보다 발급·만료가 핵심이다. Vault DB credential, AWS STS 세션이 대표 예다.
Blast Radiussecret 하나가 유출됐을 때 영향을 받는 리소스·환경·시간 범위다.강한 secret일수록 권한 범위와 수명이 짧아야 한다.
KMSKey Management Service. 암호화 키를 만들고 보호하며 복호화 권한을 IAM으로 통제하는 서비스다.secret 저장소가 아니라 key 계층이다.
Envelope Encryption데이터를 직접 master key로 암호화하지 않고, 데이터 키로 암호화한 뒤 그 데이터 키를 KMS로 보호하는 방식이다.KMS가 모든 원문 데이터를 저장하는 구조가 아니다.
Rotationsecret 값을 새 값으로 바꾸고, 소비자가 새 값을 쓰도록 전환하는 절차다.”새 값을 만들기”가 아니라 구버전과 신버전의 전환 안정성까지 포함한다.
Version StageSecrets Manager에서 버전에 붙는 라벨이다. AWSCURRENT, AWSPREVIOUS, AWSPENDING이 대표다.버전 ID와 stage는 다르다. 앱은 보통 stage를 통해 현재 버전을 읽는다.
Parameter StoreAWS Systems Manager의 설정 저장소다. SecureString으로 단순 secret 저장도 가능하다.자동 rotation과 rich secret lifecycle은 Secrets Manager 쪽이 강하다.
VaultHashiCorp의 멀티클라우드·온프레미스 secret 관리 시스템이다.AWS 서비스가 아니라 별도 운영 대상이다. 동적 secret에 강하다.

secret 사고는 “나중에 지우면 되는 실수”가 아니다. git에 한 번 올라간 secret은 commit history, fork, mirror, CI log, local clone에 남을 수 있다. 파일에서 지우는 git rm은 현재 파일만 지울 뿐 이미 push된 history와 외부 clone을 되돌리지 못한다.

GitGuardian State of Secrets Sprawl 2025는 2024년 공개 GitHub에서 노출된 secret을 23.8M 건, 전년 대비 25% 증가로 보고했고, 2022년에 노출된 secret 중 상당수가 수년 뒤에도 유효했다고 설명한다. 이 수치는 “정확한 최신 카운트”로 외우기보다 위험의 방향을 잡는 데 쓴다. 별도의 honeypot 실험들에서도 공개 저장소에 올라간 cloud key에 자동 스캐너가 수 분 내 접근을 시도하는 패턴이 반복적으로 관찰됐다. 발견이 늦어질수록 secret은 공격자에게 재사용 가능한 자산이 된다.

환경변수는 코드와 설정을 분리하는 데 유용하지만, secret 관리의 최종 해법은 아니다. 환경변수는 프로세스 시작 시점에 주입되는 평문 값이고, 접근 감사나 자동 rotation을 자체로 제공하지 않는다.

대표 노출 경로는 다음과 같다.

노출 경로왜 문제가 되는가먼저 잡을 개념
.env 파일 커밋local 편의 파일이 git history에 남는다.코드와 secret 분리, secret scanner
CI/CD 로그배포 스크립트가 값을 echo하거나 실패 로그에 환경 전체가 찍힌다.로그 마스킹, OIDC, 런타임 조회
프로세스 환경디버그 셸, crash dump, 런타임 dump가 환경변수를 평문으로 노출할 수 있다.런타임 주입, 최소 권한, 관측 범위 제한
컨테이너 오케스트레이터 spec잘못 저장하면 Pod spec, task definition, 백업에 값이 남는다.ARN 참조, Secret reference
장기 key 재사용한 서비스의 key가 여러 서비스·환경에서 같이 쓰이면 유출 시 blast radius가 커진다.환경별 분리, static vs dynamic secret
rotation 없는 운영유출된 secret을 언제 바꿔야 하는지 몰라 사실상 영구 권한이 된다.rotation, version stage, 감사 로그

환경변수 자체를 금지하자는 뜻은 아니다. ECS secrets 필드처럼 저장소에는 ARN만 두고, 컨테이너 시작 시점에 플랫폼이 값을 가져와 환경변수로 넣는 패턴은 흔히 쓴다. 중요한 차이는 Task Definition이나 git에는 secret 값이 아니라 참조만 남고, 값을 가져오는 주체와 시간이 IAM·CloudTrail로 관리된다는 점이다.

secret은 언제 주입되는지도 중요하다.

빌드타임 주입:
source + secret -> build artifact
결과: 이미지, 번들, sourcemap, layer cache에 값이 남을 수 있다.
런타임 주입:
artifact + IAM role -> secret store 조회 -> process memory
결과: 코드와 이미지에는 값이 없고 실행 주체만 접근한다.

민감 정보는 기본적으로 런타임에 주입한다. 빌드 산출물은 registry, CDN, 브라우저, 캐시, artifact store로 퍼지기 때문에 한 번 들어간 값을 회수하기 어렵다. 공개 클라이언트 번들에 들어가는 값은 secret이 될 수 없다. 브라우저에서 볼 수 있는 값은 “숨겨진 값”이 아니라 “공개 식별자”로 설계해야 한다.

2.5 선행 기술의 한계 - 왜 중앙화 Secrets Management가 등장했나

섹션 제목: “2.5 선행 기술의 한계 - 왜 중앙화 Secrets Management가 등장했나”

secret 관리는 세 단계를 거쳐 진화했다. 각 단계의 한계를 보면 Secrets Manager, Parameter Store, Vault가 단순 “안전한 메모장”이 아니라는 점이 보인다.

1단계 - 코드 안에 secret을 직접 둔다

섹션 제목: “1단계 - 코드 안에 secret을 직접 둔다”

초기에는 DB password나 API key를 config 파일이나 source code에 직접 적는 일이 많았다. 작은 팀, 단일 서버, 수동 배포에서는 빠르게 동작한다. 그러나 git과 CI/CD가 들어오면 이 방식은 바로 깨진다.

app.ts
const dbPassword = "prod-password";
git commit
-> local clone
-> remote repository
-> CI log
-> fork / mirror / artifact

문제는 삭제가 어렵다는 점이다. 현재 파일에서 문자열을 지워도 이전 commit에는 남는다. secret이 이미 외부로 나갔다면 history rewrite만으로는 충분하지 않고, 원 secret 자체를 revoke 또는 rotate해야 한다.

2단계 - .env와 환경변수로 코드와 설정을 분리한다

섹션 제목: “2단계 - .env와 환경변수로 코드와 설정을 분리한다”

12-factor App의 config 분리 원칙은 큰 개선이었다. 코드는 repo에 두고, 환경별 값은 .env, deployment setting, container environment로 주입한다.

하지만 .env와 환경변수는 다음 문제를 남긴다.

  • 값이 여전히 평문이고, 잘못 커밋되기 쉽다.
  • 누가 언제 값을 읽었는지 추적하기 어렵다.
  • 값을 바꾸려면 배포·재시작이 필요하다.
  • 같은 값을 여러 서버에 복사하면 실제 사용처 목록이 흐려진다.
  • 장기 key는 TTL이 없어서 유출 피해 창이 길다.

3단계 - 중앙화 secret store와 IAM/KMS로 관리한다

섹션 제목: “3단계 - 중앙화 secret store와 IAM/KMS로 관리한다”

Secrets Manager, Parameter Store, Vault 같은 중앙화 secret store는 다음 메커니즘을 추가한다.

이전 한계중앙화 관리가 추가하는 메커니즘
git history에 평문 저장코드에는 secret 값 대신 ARN, path, logical name만 남긴다.
읽기 감사 부재GetSecretValue 같은 조회 호출을 CloudTrail 또는 audit log에 남긴다.
무제한 접근IAM policy, resource policy, KMS decrypt 권한으로 접근 주체를 좁힌다.
회전 시 중단 위험version stage와 rotation workflow로 구버전·신버전 전환을 관리한다.
정적 key의 긴 노출창STS, OIDC, Vault dynamic credential처럼 TTL이 있는 자격증명으로 피해 시간을 줄인다.
환경별 secret 혼용/prod/service/name, /dev/service/name 같은 경로와 role을 분리해 blast radius를 좁힌다.

이 lineage가 이 문서 전체의 중심이다. secret 관리는 보관 위치를 바꾸는 일이 아니라 secret 값, 접근 주체, 암호화 키, 네트워크 경로, rotation 상태를 분리해서 각각 좁히는 일이다.

Secret 조회는 아래 다섯 질문으로 분해하면 된다.

1. 어떤 secret인가?
static인가 dynamic인가, prod인가 dev인가, 어떤 서비스 소유인가
2. 누가 읽는가?
ECS Task Role, Lambda Role, GitHub Actions OIDC 세션, 사람 SSO 세션
3. 어떤 저장소에서 읽는가?
Secrets Manager, Parameter Store, Vault, S3+KMS, 외부 SaaS
4. 어떤 key로 보호되는가?
AWS managed key, customer managed KMS key, Vault Transit key
5. 어디서 실패할 수 있는가?
IAM deny, KMS decrypt deny, VPC Endpoint 없음, rotation pending, cache stale

정적 Secret과 동적 Secret

정적 Secret

API key, DB password, OAuth client secret처럼 사람이 만들고 오래 유지하는 값이다. 유출되면 교체 전까지 계속 유효하다.

외부 SaaS key, legacy DB 계정, 자동 TTL 발급을 지원하지 않는 연동

동적 Secret

요청 시점에 발급되고 TTL이 끝나면 만료되는 값이다. 유출되더라도 피해 시간이 TTL 안으로 제한된다.

AWS STS 세션, CI/CD OIDC, Vault DB credential, 단기 workload identity

정적 secret은 없앨 수 있으면 줄이는 것이 좋지만, 현실에서는 외부 API key, legacy DB password, vendor credential 때문에 계속 남는다. 따라서 정적 secret은 “절대 유출되지 않게”가 아니라 다음 네 조건으로 관리한다.

  1. 코드와 이미지에 들어가지 않는다.
  2. 환경과 서비스별로 분리한다.
  3. 접근 주체를 최소화한다.
  4. 유출을 전제로 rotation과 감사 경로를 준비한다.

동적 secret은 설계가 더 안전하지만 운영 복잡도가 있다. Vault Database Secrets Engine처럼 접속 때마다 DB 계정을 만들고 TTL 후 삭제하는 구조는 blast radius를 크게 줄인다. 대신 발급 시스템의 가용성, TTL 갱신, connection pool과의 상호작용을 같이 설계해야 한다.

같은 “DB password”라도 blast radius는 설계에 따라 달라진다.

설계유출 시 영향 범위개선 방향
root DB 계정 하나를 모든 서비스가 공유모든 DB, 모든 테이블, 모든 환경서비스별 DB user, 읽기/쓰기 권한 분리
prod와 staging이 같은 password 사용staging 유출이 prod 사고로 번짐환경별 secret path와 IAM role 분리
CI, ECS, 사람 노트북이 같은 AWS Access Key어떤 경로에서 유출됐는지 추적 불가, 계정 전체 위험OIDC/Role 세션, workload role, 사람 SSO 분리
Vault가 1시간 TTL DB credential 발급해당 계정의 권한과 TTL 시간 안으로 제한connection pool TTL과 revoke 실패 감시
Secrets Manager + alternating user rotation구버전 연결과 신버전 연결을 나눠 무중단 전환 가능AWSPREVIOUS 유효 기간과 앱 캐시 TTL을 맞춘다

좋은 secret 설계는 secret 값을 더 복잡하게 만드는 것이 아니라 하나의 secret이 해킹됐을 때 어디까지 갈 수 있는지 설명 가능하게 만드는 것이다.

KMS(Key Management Service)는 secret을 저장하는 서비스가 아니라 key를 보호하고 key 사용 권한을 평가하는 서비스다. Secrets Manager는 저장소이고, KMS는 그 저장소 안의 값을 암호화·복호화하는 key 계층이다.

Envelope Encryption은 다음 흐름으로 이해하면 된다.

Secrets Manager 조회와 Envelope Encryption
flowchart TD
App["워크로드가 Secret 조회 요청"] --> SM["Secrets Manager"]
SM --> IAM{"secretsmanager:GetSecretValue 허용?"}
IAM -->|No| Deny["AccessDenied"]
IAM -->|Yes| KMS["KMS Decrypt 요청"]
KMS --> KMSIAM{"kms:Decrypt 허용?"}
KMSIAM -->|No| KMSDeny["KMS AccessDenied"]
KMSIAM -->|Yes| DataKey["암호화된 데이터 키 복호화"]
DataKey --> Plain["데이터 키로 Secret 값 복호화"]
Plain --> Return["평문 Secret 반환"]

저장 형태를 단순화하면 아래와 같다.

Secrets Manager 안의 저장물
encrypted_secret_value
encrypted_data_key
metadata / version stage
KMS가 보호하는 것
master key
data key decrypt 권한

왜 굳이 이렇게 나누는가? KMS가 모든 데이터를 직접 암호화하면 호출 지연과 비용이 커지고, 대용량 데이터 처리에도 맞지 않는다. 그래서 실제 데이터는 데이터 키로 암호화하고, 데이터 키만 KMS master key로 감싼다. 이 구조가 “envelope”이라는 이름의 이유다.

처음에는 KMS 내부 구현보다 이 경계를 기억하면 된다.

  • Secret 저장소 접근 권한과 KMS decrypt 권한은 둘 다 필요할 수 있다.
  • KMS key policy나 IAM policy가 막으면 GetSecretValue 권한이 있어도 복호화가 실패한다.
  • KMS는 secret 값을 관리하는 UI가 아니라 key 사용 권한과 감사의 경계다.
  • 대용량 blob은 Secrets Manager가 아니라 S3 + SSE-KMS에 두고, Secrets Manager에는 위치와 짧은 credential만 두는 편이 낫다.

KMS key 선택은 처음에는 단순하게 본다. 기본 AWS managed key로도 작은 서비스의 secret 암호화는 시작할 수 있다. 고객 관리 KMS key(CMK)는 key policy를 직접 통제해야 하거나, key rotation·분리 감사·삭제 대기 기간 같은 컴플라이언스 요구가 있을 때 검토한다. CMK를 쓰면 보안 제어가 늘어나는 대신 key policy 오류로 secret 조회가 막히는 실패면도 함께 늘어난다.

IAM permission boundary로 secret 접근 읽기

섹션 제목: “IAM permission boundary로 secret 접근 읽기”

IAM prerequisite에서 배운 것처럼 AWS 요청은 단일 Allow 하나로 끝나지 않는다. secret 조회도 여러 계층을 통과한다.

ECS Task 또는 Lambda
-> IAM Role 세션
-> Role Permission Policy
-> Permission Boundary / SCP / Session Policy
-> Secret Resource Policy
-> KMS Key Policy + kms:Decrypt
-> VPC Endpoint 또는 NAT 경로

작은 policy 예시를 보자.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/myapp/*"
},
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/key-id"
}
]
}

이 예시는 넓은 secretsmanager:*가 아니라 읽기에 필요한 API만 허용한다. Resource도 전체 계정이 아니라 prod/myapp/*로 좁힌다. 단, 실제 운영에서는 secret 이름 규칙, CMK 사용 여부, resource policy, 조직 SCP까지 함께 본다.

반례가 중요하다.

착각실제 결과먼저 확인할 것
GetSecretValue만 있으면 충분하다customer managed KMS key를 쓰면 kms:Decrypt가 없어 실패할 수 있다.KMS key policy, role policy
Execution Role에 앱 권한을 붙이면 된다앱 코드가 SDK로 조회하는 경우 Task Role 권한이 필요하다.ECS Task Role vs Execution Role
Role policy simulator가 Allow면 끝이다SCP, permission boundary, resource policy Deny가 Allow를 덮을 수 있다.effective permission 계층
secret 이름이 prod/*면 격리된다이름은 권한 경계가 아니다. policy가 Resource: "*"면 경로 분리가 무력해진다.ARN resource scope, tag condition, account boundary

ECS에서는 두 Role을 구분해야 한다.

Rolesecret과의 관계대표 실패 신호
Execution RoleECS 플랫폼이 task 시작 시 secrets 필드의 값을 가져와 컨테이너에 주입할 때 쓴다.task가 시작 전 unable to retrieve secret으로 실패한다.
Task Role컨테이너 안의 애플리케이션 코드가 SDK로 GetSecretValue를 호출할 때 쓴다.앱 로그에서 AccessDeniedException 또는 KMS deny가 발생한다.

AWS Secrets Manager는 secret 값, 버전, rotation 상태, IAM 접근을 함께 관리하는 서비스다. 이 문서에서는 “콘솔에서 secret 만들기”보다 아래 네 가지를 이해하는 것이 더 중요하다.

  1. secret은 KMS로 암호화되어 저장된다.
  2. 조회는 IAM과 KMS 권한을 모두 통과할 수 있다.
  3. rotation은 stage label을 이동시키는 절차다.
  4. quota와 API 호출 패턴은 부팅·대규모 배포에서 실패 신호가 된다.

Version Stage - 현재값을 가리키는 라벨

섹션 제목: “Version Stage - 현재값을 가리키는 라벨”

Secrets Manager는 값을 버전으로 보관하고, 버전에는 stage label을 붙인다.

Version ID Stage
abc123 AWSCURRENT
def456 AWSPREVIOUS
ghi789 AWSPENDING

앱이 보통 읽는 것은 AWSCURRENT다. Rotation 중에는 새 값이 AWSPENDING으로 만들어지고, 테스트가 끝나면 AWSCURRENT로 승격된다. 직전 값은 AWSPREVIOUS로 남아 rollback이나 기존 connection 유예에 쓰인다.

중요한 경계는 “stage가 곧 값”이 아니라는 점이다. stage는 특정 version ID를 가리키는 라벨이다. rotation이 실패하면 값이 바뀌지 않았는데도 새 pending version이 남을 수 있고, 앱은 계속 기존 AWSCURRENT를 읽어 정상처럼 보일 수 있다.

Secrets Manager Rotation 4단계

  1. createSecret

    새 값을 만들고 AWSPENDING 버전에 저장한다.

  2. setSecret

    대상 DB나 외부 시스템에 새 값을 실제로 반영한다.

  3. testSecret

    새 값으로 접속 또는 인증이 되는지 확인한다.

  4. finishSecret

    AWSPENDING을 AWSCURRENT로 승격하고 기존 값은 AWSPREVIOUS로 남긴다.

왜 Lambda가 등장하는가? rotation은 secret 저장소만 바꾸는 일이 아니다. DB password라면 실제 DB 계정 password도 바꿔야 하고, 외부 SaaS key라면 해당 SaaS API에서 새 key를 발급해야 한다. 이 대상 시스템별 작업을 Lambda가 수행한다.

DB password rotation에서 가장 헷갈리는 부분은 “값을 바꾸면 기존 연결은 어떻게 되나”다.

전략동작 방식장점경계
Single User기존 DB user의 password를 새 값으로 바꾼다.단순하다.기존 connection pool, 캐시된 password가 끊길 수 있다.
Alternating Useruser_auser_b를 번갈아 AWSCURRENT로 올린다.무중단 전환에 유리하다.DB 계정 2개와 권한 동기화가 필요하다.

Alternating User 흐름은 다음처럼 읽는다.

현재 상태:
user_a password = AWSCURRENT
user_b password = 대기
rotation:
1. user_b password를 새로 만든다.
2. user_b로 접속 테스트한다.
3. user_b를 AWSCURRENT로 승격한다.
4. user_a는 AWSPREVIOUS로 남긴다.
다음 rotation:
user_a를 새 값으로 바꾸고 다시 AWSCURRENT로 승격한다.

이 패턴은 기존 connection이 잠깐 구 user로 남아 있어도 서비스가 바로 끊기지 않게 만든다. 대신 앱의 secret cache TTL과 DB connection pool 갱신 정책이 rotation 주기와 맞아야 한다.

AWSPENDING이 오래 남아 있으면 rotation이 “진행 중”처럼 보이지만 사실상 멈춘 상태일 수 있다.

정상:
abc123 -> AWSCURRENT
def456 -> AWSPREVIOUS
고착:
abc123 -> AWSCURRENT
def456 -> AWSPREVIOUS
ghi789 -> AWSPENDING

대표 원인은 다음이다.

  • setSecret 단계에서 DB 접속 실패
  • testSecret 단계에서 새 password 인증 실패
  • Rotation Lambda가 Private Subnet에 있지만 Secrets Manager 또는 DB로 나가는 경로가 없음
  • Lambda 실행 role에 PutSecretValue, UpdateSecretVersionStage, kms:Decrypt 중 필요한 권한이 없음
  • 여러 rotation 시도가 겹쳐 pending label이 정리되지 않음

학습 포인트는 “pending label을 지우는 명령”보다 rotation은 저장소, 대상 시스템, 네트워크, IAM이 모두 맞아야 완료된다는 점이다. AWSCURRENT가 그대로라서 앱이 정상 동작하더라도 rotation이 멈췄다면 보안 상태는 점점 오래된 secret으로 회귀한다.

5. API, quota, 비용으로 보는 현실 감각

섹션 제목: “5. API, quota, 비용으로 보는 현실 감각”

Secrets Manager는 managed service이지만 무한 저장소가 아니다. quota와 비용은 설계 판단을 바꾼다. 아래 수치는 AWS 공식 quota와 pricing page를 기준으로 학습 감각을 잡기 위한 값이며, 실제 설계 전에는 공식 문서를 다시 확인해야 한다.

항목기준값학습 포인트
Secret value 크기65,536 bytesTLS chain, private key, 큰 JSON을 한 secret에 다 넣으면 실패한다. 큰 blob은 S3 + SSE-KMS로 분리한다.
Versions per secret100개너무 자주 PutSecretValue를 호출하면 24시간 미만 버전이 쌓여 quota에 닿을 수 있다.
GetSecretValue10,000 RPS대규모 부팅 storm에서 SDK 직접 호출이 throttle될 수 있다.
BatchGetSecretValue100 RPSbatch는 호출 횟수를 줄이지만 quota가 훨씬 낮다. 항상 더 빠른 선택이 아니다.
Secrets per region500,000큰 조직은 환경 x 서비스 x credential cross-product가 커질 수 있다.
저장 비용secret당 월 $0.40secret 수가 많고 rotation이 필요 없으면 Parameter Store와 비교한다.
API 호출 비용10,000 호출당 $0.05request path에서 매 요청 조회하면 비용과 latency가 늘어난다. client cache를 둔다.

여러 secret을 한 번에 읽고 싶으면 batch API가 매력적으로 보인다. 하지만 quota 때문에 사용처를 구분해야 한다.

상황더 나은 선택이유
단일 서비스가 부팅 시 5-20개 secret을 읽음BatchGetSecretValue 검토호출 횟수와 bootstrap latency를 줄일 수 있다.
수천 task가 동시에 올라오는 배포분산 bootstrap + cachebatch quota가 낮아 batch 자체가 throttle 지점이 될 수 있다.
request마다 같은 secret을 읽음앱 메모리 cache 또는 sidecarsecret은 request path가 아니라 refresh path에서 읽는 편이 안전하다.
secret 하나만 가끔 조회GetSecretValue단순하고 quota가 높다.

작은 수치 예시를 보자.

상황:
ECS task 2,000개가 동시에 시작
각 task가 secret 10개를 읽음
개별 호출:
20,000 GetSecretValue 요청
GetSecretValue quota 10,000 RPS라면 분산·retry 없이는 throttle 가능
batch 호출:
2,000 BatchGetSecretValue 요청
Batch quota 100 RPS라면 더 오래 throttle될 수 있음
결론:
"batch가 항상 빠르다"가 아니라, 부팅 분산과 cache가 먼저다.

자동 rotation이나 배포 스크립트가 PutSecretValue를 너무 자주 호출하면 version이 빠르게 쌓인다. Secrets Manager는 짧은 시간 안에 생긴 버전을 바로 정리하지 못하므로, 100개에 가까워지면 다음 rotation이 실패할 수 있다.

이 실패는 더 위험하다. 앱은 계속 AWSCURRENT를 읽기 때문에 장애가 없고, 보안팀은 rotation이 정상이라고 착각할 수 있다.

증상:
앱은 정상
RotationFailed 이벤트만 증가
secret version count가 100에 근접
의미:
새 version을 만들 수 없어 rotation이 멈춘 상태
먼저 볼 개념:
rotation 주기, PutSecretValue 호출 빈도, version stage 정리

언제 Secrets Manager를 피하거나 보완해야 하나

섹션 제목: “언제 Secrets Manager를 피하거나 보완해야 하나”

Secrets Manager가 좋은 기본값이어도 모든 값을 넣는 곳은 아니다.

조건위험대안 또는 보완
secret 하나를 초당 수천-수만 번 읽음API throttle, latency, 비용 증가client cache, sidecar cache, startup load 후 TTL refresh
64KB를 넘는 인증서 bundle 또는 binary blobvalue size 제한S3 + SSE-KMS에 blob 저장, Secrets Manager에는 S3 URI와 짧은 key만
분 단위 이하 rotation이 필요함version quota와 소비자 cache가 따라가기 어려움STS, OAuth token service, Vault dynamic credential
공개 설정값까지 모두 secret으로 저장비용과 권한 관리 복잡도 증가Parameter Store String, app config service
멀티클라우드·온프레미스 중심AWS 종속성과 운영 경계 불일치Vault 또는 cloud-neutral secret platform

Secrets Management는 AWS Secrets Manager 하나로 끝나지 않는다. 같은 문제를 비용, 기능, 운영 위치에 따라 다르게 푸는 도구들이 있다.

AWS Systems Manager Parameter Store는 계층형 path로 설정값을 저장하는 서비스다. 값 유형은 크게 세 가지다.

유형암호화대표 사용처
String없음region, domain, feature flag처럼 공개 가능한 설정
StringList없음쉼표로 구분된 짧은 목록
SecureStringKMSrotation이 단순하거나 드문 secret

Standard와 Advanced는 크기, 비용, 정책 기능에서 다르다.

항목StandardAdvanced
비용무료 티어 중심파라미터당 월 비용
최대 크기4KB8KB
파라미터 정책없음TTL, 알림 등 정책 가능
처리량 감각낮은 TPS 설계에 적합더 높은 TPS가 필요할 때

Parameter Store는 “설정 저장소”로 먼저 이해하는 편이 좋다. SecureString은 작은 secret을 저렴하게 저장하는 방법이지만, Secrets Manager처럼 rotation workflow와 secret version stage를 중심으로 설계된 서비스는 아니다.

계층형 path는 IAM scope와 함께 쓸 때 강해진다.

/prod/api/db-password
/prod/api/jwt-secret
/prod/cache/redis-url
/staging/api/db-password
/dev/api/db-password

이름만 나눈다고 보안 경계가 생기는 것은 아니다. IAM policy가 /prod/*/dev/*를 실제로 다르게 허용해야 한다.

Vault는 AWS 밖에서도 쓸 수 있는 secret management platform이다. AWS만 쓰는 작은 시스템에서는 Secrets Manager가 더 단순할 수 있지만, 다음 조건에서는 Vault가 의미를 갖는다.

  • 온프레미스와 여러 cloud를 함께 운영한다.
  • DB credential을 요청 시점에 동적으로 만들고 TTL 후 폐기하고 싶다.
  • 조직 표준 secret workflow를 cloud provider와 독립적으로 두고 싶다.
  • Vault Transit처럼 “암호화 as a service”를 중앙화하고 싶다.

Vault의 대표 학습 포인트는 dynamic secret이다.

앱이 DB 접근 요청
-> Vault가 DB에 임시 user를 생성
-> TTL 1시간 credential 반환
-> TTL 만료 또는 revoke 시 DB user 삭제

이 구조는 유출 피해 창을 줄인다. 하지만 Vault 자체가 중요한 인프라가 되므로 HA 구성, unseal, audit log, policy, lease renewal을 운영해야 한다. unseallease renewal은 첫 회독에서 구현까지 알 필요는 없고, “Vault를 살려 두고 발급한 자격증명의 수명을 관리하는 운영 심화 용어”로 표시해 두면 충분하다. 즉 Vault는 “더 안전한 저장소”가 아니라 “더 강한 control plane”이다.

Secret 저장소 선택 흐름
flowchart TD
Start["저장할 값이 생김"] --> Public{"노출되어도 권한이 생기지 않는 설정값인가?"}
Public -->|Yes| PSString["Parameter Store String 또는 app config"]
Public -->|No| Rotate{"자동 rotation 또는 version stage가 필요한가?"}
Rotate -->|Yes| SM["AWS Secrets Manager"]
Rotate -->|No| Multi{"멀티클라우드/온프레미스 통합이 필요한가?"}
Multi -->|Yes| Vault["Vault 검토"]
Multi -->|No| Dynamic{"TTL 기반 동적 발급이 필요한가?"}
Dynamic -->|Yes| VaultSTS["Vault dynamic secret 또는 STS/OIDC"]
Dynamic -->|No| Cost{"secret 수가 많고 비용이 민감한가?"}
Cost -->|Yes| PSSecure["Parameter Store SecureString"]
Cost -->|No| SM

실무 판단은 보통 이렇게 정리된다.

조건우선 후보이유
DB password rotation 필요Secrets ManagerLambda rotation, version stage, managed integration
단순 API key 저장, rotation 드묾Parameter Store SecureString 또는 Secrets Manager비용과 audit 요구를 비교한다.
공개 설정값Parameter Store Stringsecret으로 취급하면 비용과 권한 관리만 늘어난다.
GitHub Actions 배포 AWS credentialOIDC + AssumeRole저장 secret을 없애고 실행 시점 임시 세션을 쓴다.
멀티클라우드 DB 동적 credentialVaultprovider 독립 policy와 lease를 제공한다.
대용량 private key bundle 또는 binary dataS3 + SSE-KMS + secret referenceSecrets Manager value size 제한을 피한다.

7. 주입 패턴 - ECS와 애플리케이션이 secret을 받는 방식

섹션 제목: “7. 주입 패턴 - ECS와 애플리케이션이 secret을 받는 방식”

secret을 저장하는 것만큼 중요한 질문은 값이 애플리케이션에 어떻게 도착하는가다.

ECS에서는 Task Definition의 secrets 배열에 Secrets Manager ARN 또는 Parameter Store parameter ARN을 적을 수 있다. 값 자체가 아니라 참조를 적는다.

{
"containerDefinitions": [
{
"name": "api",
"image": "123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/api:latest",
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/myapp/db-password-AbCdEf"
},
{
"name": "JWT_SECRET",
"valueFrom": "arn:aws:ssm:ap-northeast-2:123456789012:parameter/prod/api/jwt-secret"
}
]
}
]
}

이때 동작 주체는 ECS 플랫폼이다.

Task 시작
-> ECS agent / Fargate control plane
-> Execution Role로 secret 조회
-> 컨테이너 환경변수에 값 주입
-> 앱은 process.env.DB_PASSWORD로 읽음

장점은 애플리케이션 코드가 Secrets Manager SDK를 몰라도 된다는 점이다. 단점은 secret이 컨테이너 시작 시점에 환경변수로 고정된다는 점이다. rotation이 일어나도 이미 떠 있는 프로세스의 process.env가 자동으로 바뀌지는 않는다.

이 경로에서 ECS는 secret 참조를 먼저 해석한 뒤 컨테이너를 시작한다. ARN, JSON key, version stage, Execution Role 권한, 네트워크 경로 때문에 값을 가져오지 못하면 일반적으로 Task가 시작 또는 리소스 초기화 단계에서 중지된다. 플랫폼이 실패를 빈 문자열로 바꿔 환경변수에 넣거나 애플리케이션 기본값을 골라 주는 동작은 아니다.

앱이 SDK로 직접 조회하는 패턴도 있다.

import {
GetSecretValueCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "ap-northeast-2" });
async function loadDbSecret() {
const response = await client.send(
new GetSecretValueCommand({
SecretId: "prod/myapp/db-password",
}),
);
if (!response.SecretString) {
throw new Error("SecretString is empty");
}
return JSON.parse(response.SecretString);
}

이 패턴은 rotation 후 TTL 기반 refresh를 구현할 수 있다. 대신 앱 코드가 AWS SDK, retry, cache, IAM Task Role 권한, bootstrap 실패를 책임진다. SDK 조회가 실패하면 호출은 예외로 끝나며, fallback은 애플리케이션이 catch에서 유효 기간이 남은 last-known-good cache를 쓰는 식으로 명시적으로 설계해야 한다. 필수 credential을 빈 문자열이나 dev 기본값으로 대체하면 장애가 다른 시스템으로 번지므로, 허용한 fallback 조건이 아니면 부팅을 실패시키는 편이 안전하다.

기준ECS secrets 주입앱 SDK 런타임 조회
코드 복잡도낮다. 앱은 환경변수만 읽는다.높다. SDK, retry, cache, parsing이 필요하다.
필요한 RoleExecution Role 중심Task Role 중심
rotation 반영task 재시작 또는 재배포 필요cache TTL에 따라 반영 가능
장애 위치앱 실행 전 Task 시작·리소스 초기화 단계에서 중지앱 bootstrap 또는 request path의 SDK 예외
fallback 주체ECS가 빈 값 fallback을 제공하지 않는다.앱이 last-known-good 사용 조건과 만료 시 실패를 명시한다.
적합한 경우단순 서버 앱, 배포 때 secret 갱신 허용장시간 실행 프로세스, rotation 후 빠른 반영 필요
조심할 점process env dump, task 재시작 전략, stopped task 확인매 요청 조회 금지, cache stale, throttle, IAM/KMS 실패, 조용한 기본값

secret cache는 필요하다. 하지만 rotation보다 오래 살아 있는 cache는 조용한 실패를 만든다.

rotation 주기: 30일
앱 cache TTL: 1시간
결과:
최대 1시간 안에 새 AWSCURRENT를 다시 읽는다.
rotation 주기: 30일
앱 cache TTL: 프로세스 생존 기간
결과:
task가 재시작될 때까지 기존 password를 계속 쓴다.
AWSPREVIOUS 유예가 끝나면 DB 인증 실패가 산발적으로 발생한다.

cache TTL은 “짧을수록 안전”만으로 정하지 않는다. TTL이 너무 짧으면 API 호출 비용과 throttle 위험이 커진다. TTL이 너무 길면 rotation 반영이 늦다. 보통은 rotation 주기보다 충분히 짧고, 장애 시 Secrets Manager 일시 실패를 견딜 정도의 cache를 둔다.

예제 1 - git leak이 왜 영구 사고가 되는가

섹션 제목: “예제 1 - git leak이 왜 영구 사고가 되는가”

아래처럼 AWS Access Key를 코드에 넣고 push했다고 가정하자.

T+0m commit push
T+1m public repo scanner가 key 문자열 발견 가능
T+3m 공격자가 STS GetCallerIdentity 또는 S3 ListBuckets 시도 가능
T+10m 개발자가 파일에서 key 삭제 후 새 commit
T+11m 이전 commit, fork, CI log에는 key가 계속 남음

여기서 중요한 판단은 “파일을 지웠는가”가 아니라 “key가 아직 유효한가”다.

잘못된 대응:
git rm .env
commit
올바른 사고:
1. key를 즉시 비활성화하거나 교체한다.
2. CloudTrail에서 그 key가 사용된 흔적을 본다.
3. 같은 key가 다른 서비스에 재사용됐는지 찾는다.
4. history rewrite와 secret scanner를 재발 방지로 처리한다.

history cleanup은 필요하지만 사고 차단의 첫 단계가 아니다. 유출된 static secret은 이미 복사됐다고 보고 revoke/rotate가 먼저다.

예제 2 - ECS secret injection이 실패하는 경로

섹션 제목: “예제 2 - ECS secret injection이 실패하는 경로”

Private Subnet의 Fargate Task가 Secrets Manager secret을 주입받아야 한다고 하자.

Task Definition:
secrets:
DB_PASSWORD -> arn:aws:secretsmanager:...
네트워크:
Private Subnet
NAT Gateway 없음
Secrets Manager Interface Endpoint 없음
결과:
task가 RUNNING 이전 단계에서 secret retrieval 실패

이 실패는 애플리케이션 코드가 실행되기 전이다. 따라서 앱 로그에 아무것도 없을 수 있고, 애플리케이션의 process.env.DB_PASSWORD || fallback도 실행되지 않는다. 먼저 볼 곳은 ECS service event, stopped reason, VPC route, Secrets Manager endpoint다.

증상:
ResourceInitializationError
unable to retrieve secret
no such host 또는 timeout
의미:
Execution Role 권한 또는 네트워크 경로가 task 시작 전 막힘
먼저 확인:
1. executionRoleArn에 secret 조회 권한이 있는가
2. CMK 사용 시 kms:Decrypt가 있는가
3. Private Subnet에서 NAT 또는 com.amazonaws.<region>.secretsmanager endpoint가 있는가
4. endpoint security group이 task ENI에서 443을 허용하는가

VPC prerequisite에서 배운 것처럼 Private Subnet은 이름만으로 AWS API에 접근할 수 없다. Secrets Manager는 AWS 서비스지만, Interface Endpoint나 NAT 경로가 있어야 한다.

예제 3 - rotation은 성공했는데 앱이 늦게 실패한다

섹션 제목: “예제 3 - rotation은 성공했는데 앱이 늦게 실패한다”

상황을 단순화해 보자.

T+0 앱이 부팅하면서 DB password를 process.env에 저장
T+30d Secrets Manager rotation 성공
T+30d+ AWSCURRENT가 새 password로 바뀜
T+30d+ 앱 프로세스는 여전히 이전 password를 메모리에 보관
T+30d+ 기존 DB connection은 잠시 유지
T+30d+ connection 재생성 시점부터 인증 실패 발생

이 문제는 rotation 실패가 아니라 소비자 갱신 실패다. Secrets Manager는 새 값을 만들었지만, 애플리케이션이 그 값을 다시 읽지 않았다.

대응 방향은 세 가지다.

방향효과경계
task 재배포새 task가 새 환경변수로 시작한다.배포 타이밍과 연결 drain을 관리해야 한다.
SDK runtime refreshTTL마다 새 AWSCURRENT를 읽는다.cache, retry, IAM, throttle를 구현한다.
Alternating User rotation기존 user와 새 user를 번갈아 써 연결 끊김을 줄인다.DB 계정 2개와 권한 동기화가 필요하다.

Secrets Manager value limit은 65,536 bytes다. DB password 하나에는 충분하지만, 아래처럼 큰 bundle을 넣으면 실패할 수 있다.

secret JSON:
private_key: 3KB
certificate: 2KB
intermediate_chain: 12KB
여러 vendor credential과 PEM bundle: 수십 KB
추가 metadata와 escape 문자
결과:
64KB를 넘으면 PutSecretValue가 거부된다.

이 경우 secret 저장소를 크게 쓰기보다 데이터를 분리한다.

S3 object:
s3://secure-bucket/certs/service-a.pem
SSE-KMS 적용
Secrets Manager:
cert_s3_uri
short-lived decrypt/use credential

학습 포인트는 “Secrets Manager는 작은 secret lifecycle 관리에 강하고, 대용량 암호화 객체 저장소가 아니다”라는 점이다.

9. 실패 신호로 이해하는 Secrets Management

섹션 제목: “9. 실패 신호로 이해하는 Secrets Management”

긴 runbook을 외우기보다 증상, 의미, 먼저 볼 개념을 연결한다.

증상의미먼저 볼 개념
AccessDeniedException on GetSecretValuerole이 secret 조회를 허용받지 못했다.IAM Role, Resource ARN, SCP/Boundary, resource policy
AccessDeniedException on kms:Decryptsecret 조회는 허용됐지만 KMS key 사용이 막혔다.KMS key policy, kms:Decrypt, CMK ownership
ECS unable to retrieve secrettask 시작 전에 secret 주입이 실패했다.Execution Role, VPC Endpoint/NAT, endpoint SG
앱은 정상 부팅했지만 DB 인증이 rotation 후 산발적으로 실패앱이 이전 secret을 cache하고 있다.cache TTL, AWSPREVIOUS 유예, connection pool
AWSPENDING이 오래 남음rotation workflow가 중간 단계에서 멈췄다.Rotation Lambda, set/test 단계, stage label
RotationFailed만 있고 앱 장애는 없음앱은 기존 AWSCURRENT를 쓰지만 rotation 보안 상태가 멈췄다.CloudTrail/EventBridge alarm, version count
ValidationException on PutSecretValuevalue size, JSON 형식, version quota 같은 저장 조건을 넘었다.64KB limit, version count, update frequency
secret scanner가 .env leak을 알림저장 방식보다 원 credential의 유효성이 문제다.revoke/rotate, CloudTrail, git history cleanup
BatchGetSecretValue에서 throttlebatch 호출이 부팅 storm의 병목이 됐다.API quota, startup fan-out, cache
prod 서비스가 staging credential을 사용path, environment, IAM scope가 서로 맞지 않는다.naming convention, env separation, task definition
CloudTrail 호출자가 예상 Role이 아님secret policy보다 credential 공급 경로가 틀렸다.Task Role vs Execution Role, profile, OIDC trust
SDK 응답 JSON에 예상 key가 없는데도 앱이 정상 부팅앱 parser나 명시적 fallback이 빈 값·기본 credential을 허용했다.response schema, config validation, fallback 만료 조건

이 표의 목표는 명령을 외우는 것이 아니다. secret 문제는 대개 네 계층 중 하나에서 막힌다.

ECS valueFrom에서 JSON secret의 특정 key만 꺼낼 때는 ARN 뒤에 key/stage/version을 붙이는 문법을 쓴다. 예를 들어 ...:secret:prod/db-AbCdEf:password::는 JSON secret에서 password key의 AWSCURRENT 값을 주입한다. 이 플랫폼 주입 경로에서 key 이름이나 참조를 틀리면 ECS가 값을 해석하지 못해 Task가 일반적으로 시작 단계에서 중지되며, 빈 값이 주입된 뒤 앱 fallback으로 넘어가는 것이 아니다. 반대로 앱이 SDK로 secret JSON 전체를 읽는 경로에서는 key 존재 여부와 fallback을 앱 코드가 판단하므로, schema validation과 fallback 만료 조건으로 조용한 기본값 사용을 막아야 한다.

Terraform으로 secret 값을 직접 관리할 때도 주의한다. aws_secretsmanager_secret 리소스 자체는 괜찮지만 aws_secretsmanager_secret_version.secret_string에 평문을 넣으면 Terraform state에 값이 남을 수 있다. IaC에는 secret 컨테이너와 권한을 두고, 실제 값은 안전한 주입 경로를 따로 설계하는 편이 낫다.

권한:
IAM, KMS, boundary, resource policy
네트워크:
NAT, Interface Endpoint, endpoint security group
버전:
AWSCURRENT, AWSPREVIOUS, AWSPENDING, rotation workflow
소비자:
env injection, SDK cache, connection pool, config validation

이름과 경로는 권한 모델의 일부다

섹션 제목: “이름과 경로는 권한 모델의 일부다”

좋은 secret 이름은 사람이 보기 좋은 label을 넘어서 IAM scope의 단위가 된다.

권장:
/prod/payments/db/password
/prod/payments/stripe/client-secret
/staging/payments/db/password
/dev/payments/db/password
피하기:
/shared/db
/prod/password
/test-secret

이름 설계의 기준은 세 가지다.

질문예시
환경dev, staging, prod가 분리되는가?/prod/..., /staging/...
서비스어떤 workload가 소유하고 읽는가?/prod/payments/...
secret 종류DB, JWT, vendor API, certificate처럼 용도가 드러나는가?/db/password, /stripe/key

이름만으로는 충분하지 않다. IAM policy가 이 경로를 실제로 사용해야 한다.

{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/payments/*"
}

최소 권한은 “누가 어떤 secret을 왜 읽는가”로 좁힌다

섹션 제목: “최소 권한은 “누가 어떤 secret을 왜 읽는가”로 좁힌다”

secret 접근 권한은 넓게 주기 쉽다. 하지만 prod/* 전체 접근은 한 서비스 침해가 prod 전체 침해로 커지는 경로다.

넓은 설계좁힌 설계실패 신호
모든 ECS Task Role에 prod/* 허용서비스별 role에 /prod/service-name/*만 허용한 task 침해로 다른 서비스 DB 접근 가능
모든 개발자에게 prod secret read 허용break-glass role, approval, CloudTrail 알림사람 노트북 침해가 prod credential 유출로 연결
GitHub repo secret에 AWS Access Key 저장OIDC trust policy로 특정 repo/ref만 assume 허용fork나 로그에 장기 key가 남음
KMS key 하나를 모든 secret에 공유민감도나 조직 경계에 따라 CMK 분리한 key policy 실수가 여러 도메인에 영향

rotation 주기는 값의 성격과 소비자 갱신 능력에 맞춘다

섹션 제목: “rotation 주기는 값의 성격과 소비자 갱신 능력에 맞춘다”

rotation은 자주 할수록 무조건 좋은 것이 아니다. 주기가 짧아질수록 소비자가 새 값을 따라가는 능력, version quota, 대상 시스템 rate limit이 중요해진다.

secret 종류rotation 감각주의할 점
DB password30-90일 같은 정기 rotation을 많이 검토connection pool, alternating user, maintenance window
외부 SaaS API keyprovider가 rotation API를 지원하는지 확인수동 콘솔 발급이면 자동화보다 사용처 문서화가 먼저일 수 있다.
AWS workload credential장기 key보다 STS/OIDC로 없애는 방향저장된 secret이 아니라 실행 시점 세션으로 전환한다.
JWT signing keykey id, multiple active keys, token TTL 필요단순 overwrite는 기존 token 검증 실패를 만들 수 있다.
TLS private key인증서 만료, 배포 위치, reload 방식 확인큰 chain은 Secrets Manager size limit보다 배포 구조가 중요하다.

rotation의 목표는 “날짜가 됐으니 바꾸기”가 아니라 유출 피해 창을 줄이는 것이다. 소비자가 새 값을 못 따라가면 rotation은 보안 개선이 아니라 장애 트리거가 된다.

secret scanner는 마지막 방어선이 아니라 조기 경보

섹션 제목: “secret scanner는 마지막 방어선이 아니라 조기 경보”

git-secrets, trufflehog, GitHub secret scanning 같은 도구는 커밋 전후에 secret 패턴을 찾는다. 하지만 scanner가 있다고 secret을 커밋해도 된다는 뜻은 아니다.

scanner의 역할은 세 가지다.

  1. 실수 커밋을 빠르게 막는다.
  2. 이미 들어간 history를 다시 훑어 누락을 찾는다.
  3. 새 종류의 key pattern을 팀 표준에 추가한다.

한계도 있다.

  • 모든 vendor secret 패턴을 완벽히 알 수 없다.
  • base64, JSON escape, custom token은 놓칠 수 있다.
  • private repo, artifact, log에 들어간 값은 별도 경로로 찾아야 한다.
  • 발견 후 자동으로 revoke되는 것은 아니다.

따라서 scanner는 prevention이면서 detection이다. 사고 차단은 revoke/rotate와 권한 축소가 담당한다.

11. 선택 부록 - 절차는 짧게 확인한다

섹션 제목: “11. 선택 부록 - 절차는 짧게 확인한다”

아래 절차는 첫 회독의 핵심이 아니다. 본문을 읽은 뒤 실제 작업에서 확인 순서를 잡을 때만 펼쳐본다.

하드코딩 secret을 중앙 관리로 옮기는 압축 절차

목표는 “문자열을 이동”이 아니라 “유효한 이전 credential을 폐기하고, 새 접근 경로를 IAM으로 통제”하는 것이다.

  1. 발견

    • 현재 코드와 git history에서 password, secret, AKIA, .env 패턴을 찾는다.
    • CI/CD log와 배포 artifact에도 값이 남았는지 확인한다.
  2. 분류

    • 이미 유출됐거나 외부로 나갔을 가능성이 있으면 새 저장소에 옮기기 전에 revoke/rotate 대상으로 표시한다.
    • prod, staging, dev를 같은 값으로 쓰고 있으면 먼저 분리한다.
  3. 등록

    • Secrets Manager 또는 Parameter Store에 새 이름 규칙으로 등록한다.
    • KMS CMK를 쓴다면 key policy와 kms:Decrypt 권한을 함께 설계한다.
  4. 주입 경로 전환

    • ECS는 Task Definition secrets 필드 또는 앱 SDK runtime 조회 중 하나를 고른다.
    • 앱 config validation으로 필수 secret 누락을 부팅 실패로 바꾼다.
  5. 폐기

    • 이전 credential을 revoke/rotate한다.
    • history rewrite는 노출 표면을 줄이는 후속 조치로 수행한다.
  6. 재발 방지

    • secret scanner를 pre-commit과 CI에 둔다.
    • CloudTrail/EventBridge로 비정상 GetSecretValue와 rotation failure를 알린다.
secret 유출 대응 타임라인의 학습 모델

secret 유출 대응에서 가장 큰 실수는 “조용히 값만 바꾸고 끝내기”다. 교체는 필요하지만, 사용 흔적과 blast radius를 같이 봐야 한다.

T+0 유출 감지
- scanner alert, CloudTrail anomaly, 직접 발견
T+1m 차단
- AWS Access Key 비활성화
- DB password rotation
- OAuth token revoke
T+5m 영향 범위 확인
- CloudTrail에서 해당 principal/key 사용 흔적 확인
- 접근된 S3, RDS, Secrets Manager, IAM API 식별
- 같은 secret 재사용처 확인
T+30m 재발 방지
- history와 artifact 재스캔
- scanner rule 추가
- IAM scope 축소
- rotation과 alarm 상태 확인

CloudTrail snapshot을 먼저 볼지, 즉시 revoke할지는 상황에 따라 trade-off가 있다. 공격이 진행 중이면 차단이 우선이고, 차단 전후로 가능한 한 빨리 사용 흔적을 보존한다. 중요한 것은 “교체했으니 안전”으로 끝내지 않는 것이다.

NIST SP 800-61 같은 incident response framework는 이 흐름을 조직 프로세스 관점에서 더 깊게 다룬다. 첫 회독의 hard prerequisite는 아니고, 보안 운영 절차를 만들 때의 심화 자료로 보면 된다.

짧은 확인 명령 모음

실제 운영에서는 콘솔, IaC, CloudTrail, SIEM을 함께 본다. 아래 명령은 개념을 확인하기 위한 최소 예시다.

Terminal window
# secret version stage 확인
aws secretsmanager describe-secret \
--secret-id prod/myapp/db-password \
--query 'VersionIdsToStages'
Terminal window
# secret version 수 감각 확인
aws secretsmanager list-secret-version-ids \
--secret-id prod/myapp/db-password \
--query 'length(Versions)'
Terminal window
# 특정 secret 조회 이벤트 확인
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue
Terminal window
# git history에서 .env 계열 파일 흔적 확인
git log --all --full-history -- "**/.env*"
Terminal window
# 실제 환경에서는 값 대신 필요한 변수의 존재 여부만 확인
grep -z '^DB_PASSWORD=' /proc/1/environ >/dev/null 2>&1
case $? in
0) printf 'DB_PASSWORD: present\n' ;;
1) printf 'DB_PASSWORD: missing\n' ;;
*) printf 'DB_PASSWORD: unreadable\n' ;;
esac
# 값 노출 성질은 격리된 로컬 셸에서 명백한 가짜 값으로만 확인
DEMO_SECRET='fake-local-only' sh -c 'printf "%s\n" "$DEMO_SECRET"'

운영 프로세스에서 env, printenv, /proc/<pid>/environ의 전체 내용을 출력하지 않는다. 확인 결과와 로그에는 변수명과 존재 여부만 남긴다.

이 명령들은 runbook의 전부가 아니다. 출력에서 봐야 하는 질문은 항상 같다.

  • 어떤 principal이 호출했는가?
  • 어떤 secret ARN 또는 parameter path였는가?
  • 어떤 stage를 읽었는가?
  • IAM deny인지 KMS deny인지 네트워크 실패인지 구분되는가?
  • rotation이 멈췄는데 앱만 정상인 상태는 아닌가?
비교 항목구분
Secret vs 설정값secret은 알면 권한이 생기는 값이고, 설정값은 공개되어도 권한이 생기지 않는 값이다.
Secrets Manager vs KMSSecrets Manager는 secret 저장소, KMS는 암호화 key와 decrypt 권한 계층이다.
Secret value vs ARNvalue는 실제 평문 secret이고, ARN은 secret을 가리키는 참조다.
IAM Allow vs 실제 허용IAM Allow가 있어도 KMS, SCP, boundary, resource policy가 막을 수 있다.
Task Role vs Execution RoleTask Role은 앱 코드의 AWS 호출, Execution Role은 ECS가 task 시작에 필요한 호출이다.
AWSCURRENT vs version IDAWSCURRENT는 현재 version을 가리키는 stage label이고, version ID는 실제 버전 식별자다.
Rotation vs 재배포rotation은 secret 값을 바꾸는 일이고, 재배포는 소비자가 새 값을 읽게 만드는 한 방법이다.
Parameter Store vs Secrets ManagerParameter Store는 설정 저장소 성격이 강하고, Secrets Manager는 rotation과 secret lifecycle에 강하다.
Static secret vs Dynamic secretstatic은 오래 사는 값이고, dynamic은 요청 시점에 짧게 발급되는 값이다.
Scanner vs 대응scanner는 발견을 돕지만, 유출된 credential을 무효화하지 않는다.
  • 팀 repo에서 .env, password, secret, AKIA 같은 문자열이 현재 파일과 history에 남아 있는지 확인한다.
  • ECS Task Definition 하나를 열어 environmentsecrets가 어떻게 구분되어 있는지 본다.
  • 해당 Task의 executionRoleArntaskRoleArn이 각각 무엇을 허용하는지 IAM policy를 비교한다.
  • prod secret 하나를 골라 AWSCURRENT, AWSPREVIOUS, AWSPENDING stage가 어떻게 붙어 있는지 확인한다.
  • Private Subnet의 task가 Secrets Manager에 접근할 때 NAT Gateway를 쓰는지, Interface Endpoint를 쓰는지 VPC route와 endpoint 목록을 확인한다.
  • CloudTrail에서 GetSecretValue 이벤트를 찾아 호출 principal이 예상한 Role인지 확인한다.
  • 앱 코드에서 secret을 매 요청마다 조회하는지, bootstrap에 한 번만 읽는지, TTL cache가 있는지 확인한다.

Secrets Management 학습 체크리스트

  • secret을 단순 민감 문자열이 아니라 권한을 주는 값으로 설명할 수 있다.
  • static secret과 dynamic secret의 차이를 노출창 관점에서 비교할 수 있다.
  • blast radius를 환경, 서비스, 권한, 시간 범위로 나눠 설명할 수 있다.
  • KMS와 Secrets Manager의 역할을 저장소와 key 계층으로 구분할 수 있다.
  • Envelope Encryption에서 데이터 키와 KMS key가 왜 분리되는지 설명할 수 있다.
  • secret 조회가 IAM, KMS, resource policy, boundary, 네트워크 경로를 함께 통과한다는 점을 말할 수 있다.
  • AWSCURRENT, AWSPREVIOUS, AWSPENDING stage가 rotation에서 어떤 역할을 하는지 설명할 수 있다.
  • GetSecretValue와 BatchGetSecretValue의 quota 차이가 부팅 storm에서 왜 중요해지는지 말할 수 있다.
  • Secrets Manager, Parameter Store, Vault 중 어느 도구를 고를지 rotation, 비용, 멀티클라우드, 동적 발급 기준으로 판단할 수 있다.
  • ECS Task Definition의 secrets 주입에서 Execution Role과 VPC Endpoint 실패 신호를 구분할 수 있다.
  • git-secrets / trufflehog / GitHub secret scanning: secret 패턴을 commit 전후에 탐지하는 도구. 탐지는 revoke/rotate를 대체하지 않는다.
  • AWS KMS Envelope Encryption: 데이터 키와 master key를 분리해 대량 데이터 암호화와 key 접근 제어를 함께 처리하는 패턴.
  • AWS STS: Role assume을 통해 임시 자격증명을 발급하는 서비스. static AWS Access Key를 줄이는 핵심 수단이다.
  • OIDC Federation: GitHub Actions 같은 외부 실행 주체가 저장된 cloud key 없이 Role을 assume하게 하는 방식.
  • SOPS: 설정 파일 안의 값 단위 암호화를 지원하는 도구. git에 암호문을 저장하는 GitOps 패턴에서 만난다.
  • Sealed Secrets: Kubernetes Secret을 cluster public key로 암호화해 git에 저장하는 패턴.
  • Vault Transit Engine: 애플리케이션이 직접 key를 들고 있지 않고 Vault를 통해 암복호화 작업을 수행하는 방식.
  • NIST SP 800-61: incident response lifecycle을 다루는 보안 운영 framework. secret 유출 대응 절차를 조직화할 때 참고한다.
  1. secret은 알면 권한이 생기는 값이며, 코드·이미지·빌드 산출물에 들어가면 회수가 어렵다.
  2. Secrets Management의 핵심은 저장소가 아니라 IAM 주체, KMS key, version stage, rotation, 감사 로그를 분리해 관리하는 것이다.
  3. static secret은 환경·서비스별로 blast radius를 줄이고, 가능하면 STS/OIDC/Vault 같은 dynamic secret으로 바꾼다.
  4. Secrets Manager는 rotation과 version stage에 강하고, Parameter Store는 단순 설정과 작은 secret, Vault는 멀티클라우드·동적 발급에 강하다.
  5. 장애를 만나면 권한(IAM/KMS), 네트워크(NAT/Endpoint), 버전(AWSPENDING), 소비자(cache/env 주입)를 순서대로 분해한다.