AWS 고급 #6 Secrets Manager / Parameter Store
DB 비밀번호, 외부 API 키, OAuth client secret. 어디에 둘까. 코드 / git은 절대 안 되고, 환경 변수에 평문도 위험합니다. AWS에는 두 가지 선택지가 있습니다. Secrets Manager와 SSM Parameter Store입니다.
이 글은 둘의 차이, 자동 회전, 코드에서 가져오는 패턴, ECS / Lambda와의 통합, IaC 연결, 비용 비교까지, 시크릿 / 설정 관리의 기준을 한 번에 정리하겠습니다.
시크릿이 들어가면 안 되는 곳 #
기초 #6 보안 기본의 연장. 절대 하면 안 되는 것:
- 코드 안 평문 (
PASSWORD = "abc123") - git에 .env (지운 뒤에도 history에 영원)
- README, 슬랙, 위키, 이메일에 붙여넣기
- Dockerfile 안 환경변수 / 빌드 인자 (이미지 layer에 박힘)
- ECS Task Definition의 평문 environment variables (콘솔 / IaC / 로그 노출)
대신 쓰는 곳: Secrets Manager 또는 SSM Parameter Store.
둘의 차이: 한 줄 비교 #
| Secrets Manager | SSM Parameter Store | |
|---|---|---|
| 정체 | 시크릿 전용 매니지드 | 설정 + 시크릿 겸용 |
| 자동 회전 | 있음 (Lambda 기반) | 없음 (수동) |
| Crypto | 항상 KMS 암호화 | 표준 (평문) / SecureString (KMS 암호화) |
| 버전 관리 | 자동 (스테이지: AWSCURRENT, AWSPREVIOUS) | 자동 (정수 버전) |
| 크기 한도 | 64KB | Standard 4KB / Advanced 8KB |
| 비용 | secret당 $0.40 / 월 + API call | Standard 무료 / Advanced 유료 |
| 통합 | RDS / Redshift의 자동 회전 templates | 광범위 (CloudFormation, ECS, Lambda) |
한 줄 결정 가이드 #
DB 비밀번호 / 외부 API 키 + 자동 회전 필요 → Secrets Manager
│
일반 설정 값 (DB 호스트, region, feature flag) → Parameter Store
│
회전 필요 없는 시크릿 (예: 외부 API 키) → Parameter Store SecureString (저렴)Secrets Manager #
만들기 #
aws secretsmanager create-secret \
--name myapp/prod/db \
--description "Postgres for myapp prod" \
--secret-string '{
"username": "myapp",
"password": "very-strong-secret",
"host": "myapp-prod.abc123.ap-northeast-2.rds.amazonaws.com",
"port": 5432,
"dbname": "myapp"
}'ARN 반환:
arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:myapp/prod/db-AbCdEfARN 끝의 6자 랜덤 (AbCdEf)은 시크릿 무단 enumeration 방지용. IAM 정책에서 arn:.../myapp/prod/db-* 같이 와일드카드 사용.
읽기: boto3 #
import boto3, json
client = boto3.client("secretsmanager")
resp = client.get_secret_value(SecretId="myapp/prod/db")
creds = json.loads(resp["SecretString"])
print(creds["username"], creds["password"])캐싱: 매 호출마다 가져오면 비용 #
API call당 과금이라 매 요청마다 get_secret_value를 호출하면 비용도 늘고 속도도 느려집니다.
전역에서 한 번 + 캐싱 패턴:
import boto3, json
# Lambda 컨테이너의 INIT 단계에서 한 번
_secrets = None
def get_db_creds():
global _secrets
if _secrets is None:
resp = boto3.client("secretsmanager").get_secret_value(
SecretId="myapp/prod/db")
_secrets = json.loads(resp["SecretString"])
return _secrets
def handler(event, context):
creds = get_db_creds()
...Powertools의 Parameters가 캐싱 / TTL / 다중 시크릿을 한 줄로:
from aws_lambda_powertools.utilities import parameters
# TTL 5분 캐싱
creds = parameters.get_secret("myapp/prod/db", transform="json",
max_age=300)자동 회전 #
Secrets Manager의 가장 큰 장점입니다. 일정 주기마다 새 비밀번호를 생성하고 DB의 사용자 비밀번호를 자동 업데이트합니다.
aws secretsmanager rotate-secret \
--secret-id myapp/prod/db \
--rotation-lambda-arn arn:aws:lambda:ap-northeast-2:...:SecretsManagerRDSPostgreSQLRotationSingleUser \
--rotation-rules AutomaticallyAfterDays=30AWS가 매니지드 회전 Lambda를 templates으로 제공합니다 (RDS / Redshift / DocumentDB / Aurora). 30일마다 새 비밀번호로 자동 회전됩니다.
회전의 4 단계 (이해 차원) #
1. createSecret → 새 비밀번호 생성, AWSPENDING으로 저장
2. setSecret → DB에 새 비밀번호 적용
3. testSecret → 새 비밀번호로 연결 테스트
4. finishSecret → AWSPENDING → AWSCURRENT, 옛 것 → AWSPREVIOUS코드는 항상 AWSCURRENT (기본)을 가져옴. 회전 중간 잠깐 AWSPREVIOUS도 유효.
Single user vs Multi-user 회전 #
- Single user: 한 사용자의 비밀번호를 갈아끼움. 회전 순간 짧은 connection 끊김 가능
- Multi-user: 두 사용자를 번갈아 사용. 무중단 회전 가능. 운영 권장
Parameter Store (SSM) #
Standard vs Advanced #
| Standard | Advanced | |
|---|---|---|
| 크기 | 4KB | 8KB |
| 정책 (만료, 알림) | 없음 | 있음 |
| Throughput | 40 / 초 | 1,000 / 초 (옵션) |
| 비용 | 무료 | 파라미터 $0.05 / 월 |
처음에는 Standard로 시작하면 충분합니다.
만들기 #
aws ssm put-parameter \
--name /myapp/prod/aws-region \
--value ap-northeast-2 \
--type String
# 계층 (자유 : '/' 로 구조화)
aws ssm put-parameter \
--name /myapp/prod/feature/new-checkout \
--value enabled \
--type Stringaws ssm put-parameter \
--name /myapp/prod/external-api-key \
--value sk_live_abc123 \
--type SecureString \
--key-id alias/aws/ssmSecureString은 KMS로 암호화. AWS 관리 키 (alias/aws/ssm)가 무료.
읽기 #
import boto3
ssm = boto3.client("ssm")
# 단일
resp = ssm.get_parameter(
Name="/myapp/prod/external-api-key",
WithDecryption=True
)
api_key = resp["Parameter"]["Value"]
# 다중 (계층)
resp = ssm.get_parameters_by_path(
Path="/myapp/prod/",
Recursive=True,
WithDecryption=True
)
for p in resp["Parameters"]:
print(p["Name"], p["Value"])from aws_lambda_powertools.utilities import parameters
# 단일
api_key = parameters.get_parameter(
"/myapp/prod/external-api-key", decrypt=True, max_age=300)
# 계층
all_params = parameters.get_parameters(
"/myapp/prod/", decrypt=True, max_age=300)버전 관리 #
같은 이름에 새 값을 put 할 때마다 버전이 1, 2, 3 … 순으로 누적됩니다. 옛 값으로 롤백할 수 있습니다.
# 버전 5의 값 보기
aws ssm get-parameter-history --name /myapp/prod/db-host
# 버전 3으로 되돌리기 (복사해서 새 버전)
aws ssm put-parameter --name /myapp/prod/db-host \
--value $(aws ssm get-parameter --name /myapp/prod/db-host:3 \
--query 'Parameter.Value' --output text) --overwriteECS / Lambda와의 통합 #
가장 자주 만나는 방식입니다. Task Definition / 함수 환경변수에 시크릿을 직접 주입합니다.
ECS Task Definition #
{
"containerDefinitions": [
{
"name": "web",
"image": "...",
"environment": [
{"name": "ENV", "value": "production"}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:myapp/prod/db-AbCdEf"
},
{
"name": "STRIPE_KEY",
"valueFrom": "arn:aws:ssm:ap-northeast-2:123456789012:parameter/myapp/prod/stripe-key"
}
]
}
]
}ECS가 Task 시작 직전 시크릿을 가져와 컨테이너의 환경 변수로 주입합니다. 코드 안에서는 os.environ["DATABASE_URL"]만 읽으면 됩니다.
필요한 권한 (#1 Execution Role):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:myapp/prod/*"
},
{
"Effect": "Allow",
"Action": ["ssm:GetParameters"],
"Resource": "arn:aws:ssm:ap-northeast-2:123456789012:parameter/myapp/prod/*"
},
{
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/*"
}
]
}JSON 시크릿의 특정 필드만 #
Secrets Manager의 시크릿이 JSON 일 때, ECS가 특정 필드만 환경변수로 줄 수 있음:
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:...:myapp/prod/db-AbCdEf:password::"
}ARN 끝의 :password::가 JSON의 password 필드만 추출. 첫 :와 마지막 : 사이가 JSON key, 둘째 : 다음은 version.
Lambda의 시크릿 #
Lambda도 같은 패턴입니다. 함수 environment variables에 직접 시크릿을 박지 말고 코드에서 boto3로 가져오세요. 또는 AWS Parameters and Secrets Lambda Extension으로 캐싱 sidecar를 사용할 수도 있습니다. 이 경우 함수 안에서 localhost:2773으로 호출합니다.
import urllib.request, json, os
def get_secret(name):
url = f"http://localhost:2773/secretsmanager/get?secretId={name}"
req = urllib.request.Request(url,
headers={"X-Aws-Parameters-Secrets-Token": os.environ["AWS_SESSION_TOKEN"]})
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
return json.loads(data["SecretString"])Extension이 자동 캐싱 (TTL 설정 가능). boto3 캐싱 코드 안 짜도 됨.
IaC와의 연결 #
Terraform #
resource "aws_secretsmanager_secret" "db" {
name = "myapp/prod/db"
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
username = "myapp"
password = random_password.db.result
host = aws_db_instance.main.address
})
}
resource "random_password" "db" {
length = 32
special = false
}비밀번호 자체를 IaC에 직접 박지 말 것.
random_password로 생성하거나, AWS가 회전으로 관리.
CloudFormation의 dynamic reference #
Resources:
MyDB:
Type: AWS::RDS::DBInstance
Properties:
MasterUsername: '{{resolve:secretsmanager:myapp/prod/db:SecretString:username}}'
MasterUserPassword: '{{resolve:secretsmanager:myapp/prod/db:SecretString:password}}'{{resolve:...}} 패턴이 stack 안에서 시크릿 / 파라미터를 직접 가져옴. 평문이 stack 템플릿 / 로그에 안 남음.
비용 #
Secrets Manager #
- 시크릿 당 $0.40 / 월
- API call 10,000당 $0.05
10개 시크릿 + 매일 100 호출 = $4 + 거의 0 = $4 / 월
Parameter Store (Standard) #
- 무료 (Throughput 40 / 초까지)
- KMS 사용 시 KMS API call 비용 (10,000당 $0.03)
10개 SecureString + 매일 100 호출 = 거의 0
가격 차이의 포인트 #
회전 / 매니지드 통합이 필요 없으면 Parameter Store SecureString이 압도적으로 저렴. 100개 시크릿을 관리한다면 Secrets Manager는 월 $40이지만 Parameter Store는 거의 $0입니다.
비밀과 설정의 분리 #
운영 권장 패턴:
Secrets Manager
├── /myapp/prod/db (자동 회전)
└── /myapp/prod/jwt-signing (자동 회전)
Parameter Store
├── /myapp/prod/db-host
├── /myapp/prod/aws-region
├── /myapp/prod/feature/new-checkout (feature flag)
├── /myapp/prod/log-level
└── /myapp/prod/external-api-key (SecureString : 회전 안 하는 시크릿)회전이 필요한 핵심 비밀만 Secrets Manager. 나머지는 Parameter Store로 비용 절감.
Cross-Account / Cross-Region #
Resource Policy #
다른 계정의 사용자에 시크릿 접근 허용:
aws secretsmanager put-resource-policy \
--secret-id myapp/prod/db \
--resource-policy '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::222222222222:root"},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}]
}'Replication #
다른 리전에 자동 복제 (Multi-region 운영):
aws secretsmanager replicate-secret-to-regions \
--secret-id myapp/prod/db \
--add-replica-regions Region=us-east-1primary가 회전되면 replica도 자동 동기화.
자주 만나는 함정 #
1) 시크릿이 ECS Task에 안 주입됨 #
Task 시작 시 “ResourceInitializationError … not authorized to perform secretsmanager:GetSecretValue"가 뜨면 Execution Role 권한 누락입니다. 또는 SecureString의 KMS key에 kms:Decrypt가 빠져 있을 수 있습니다.
2) 매 요청마다 boto3 호출 #
코드의 핸들러 안에서 매번 get_secret_value를 호출하면 API call이 폭주해 비용과 지연이 늘어납니다. 모듈 전역에서 한 번 가져온 뒤 캐싱하세요.
3) JSON 필드 추출 ARN의 형태 오타 #
arn:...:db-AbCdEf:password::. 콜론 갯수 / 위치 한 자만 틀려도 silently 빈 문자열로 들어옵니다. 콘솔에서 ARN을 그대로 복사해 쓰세요.
4) IaC의 평문 비밀 #
Terraform secret_string = "abc123". state 파일에 평문. 항상 random_password 또는 외부 입력, state는 암호화 (S3 + KMS).
5) 회전 후 옛 비밀번호로 호출 실패 #
회전 중간 짧은 시간 동안 옛 비밀번호로 만든 connection이 끊길 수 있음. multi-user 회전 + connection retry.
6) 시크릿 이름 의도치 않은 공개 #
ARN의 6자 랜덤 (-AbCdEf)이 CloudTrail / 로그에 남으면 추측이 어렵지만, 시크릿 이름 자체는 (myapp/prod/db) 콘솔 권한 있는 사람이면 다 봄. 권한을 줄이고 (시크릿마다 별도 IAM) 노출 면을 좁힘.
7) Parameter Store throughput 한도 #
Standard의 40 req/s 한도에 부딪히면 throttling. 핫 패스에서 자주 호출하면 Advanced 또는 캐싱.
정리 #
이번 글에서 잡은 것:
- 시크릿이 들어가면 안 되는 곳. 코드, git, README, Dockerfile, 평문 환경변수
- Secrets Manager. 자동 회전 (RDS templates), 항상 KMS, secret당 $0.40
- Parameter Store. 설정 + 시크릿. Standard 무료, SecureString도 무료
- 선택 가이드. 자동 회전 필요 → Secrets Manager. 그 외 → Parameter Store SecureString
- boto3 + 캐싱. 모듈 전역에서 한 번 + Powertools의
parameters - 자동 회전 4 단계. createSecret → setSecret → testSecret → finishSecret. multi-user가 무중단
- ECS 통합. Task Definition의
secrets로 환경변수 자동 주입. Execution Role 권한 필수 - JSON 시크릿의 특정 필드. ARN 끝의
:fieldName:: - Lambda Extension. Parameters and Secrets Lambda Extension으로 캐싱 sidecar
- IaC.
random_password생성, CloudFormation의{{resolve:...}} - 비밀 / 설정 분리. 회전 필요한 핵심만 Secrets Manager, 나머지는 Parameter Store
- Cross-Account / Cross-Region. Resource Policy / Replication
- 함정. Execution Role 권한, 호출 폭주, ARN 오타, IaC 평문, 회전 중 끊김, throughput 한도
다음: Step Functions #
이 시리즈의 마지막입니다. 여러 Lambda / ECS / 외부 API 호출을 묶어 하나의 워크플로우로 만드는 방식입니다.
#7 Step Functions 입문에서는 State machine / Task / Choice / Parallel, Standard vs Express, Lambda 오케스트레이션, 에러 처리와 retry까지, AWS 워크플로우를 한 번에 정리하겠습니다.