테라폼 - Secrets Manager와 SSM Parameter Store 구현하기
민감정보와 운영 설정값을 코드에서 분리해서 관리하기
이전 글에서는 Terraform으로 RDS MySQL을 구현하는 방법을 정리했다.
이번 글에서는 애플리케이션 운영에서 자주 필요한 Secrets Manager와 SSM Parameter Store를 Terraform으로 구현해보려 한다.
서비스를 만들다 보면 다음과 같은 값들이 필요하다.
DB password
API key
JWT secret
OAuth client secret
외부 서비스 token
DB endpoint
환경별 설정값
feature flag
log level
이런 값을 모두 코드에 직접 작성하면 위험하다.
application.yml
.env
terraform.tfvars
Dockerfile
GitHub repository
위와 같은 곳에 비밀번호나 토큰을 직접 넣으면 Git에 올라가거나, Terraform state에 남거나, 로그에 노출될 수 있다.
민감정보와 설정값은 애플리케이션 코드에서 분리해서 관리해야 한다.
목차
- 1. 왜 Secret과 설정값을 분리해야 할까?
- 2. Secrets Manager란 무엇인가
- 3. SSM Parameter Store란 무엇인가
- 4. Secrets Manager와 Parameter Store 차이
- 5. Terraform으로 Secret 메타데이터 만들기
- 6. Terraform으로 Secret 값을 넣을 때의 주의점
- 7. SSM Parameter Store 구현하기
- 8. IAM 권한 부여하기
- 9. ECS에서 Secret과 Parameter 사용하기
- 10. Lambda에서 Secret과 Parameter 사용하기
- 11. EC2에서 Secret과 Parameter 사용하기
- 12. 실전 예제: DB 접속 정보 관리
- 13. 의존성 흐름
- 14. 자주 하는 실수
- 15. 마무리
1. 왜 Secret과 설정값을 분리해야 할까?
애플리케이션에는 코드와 함께 관리하면 안 되는 값들이 있다.
대표적인 것이 비밀번호와 API Key다.
DB_PASSWORD=...
JWT_SECRET=...
OAUTH_CLIENT_SECRET=...
PAYMENT_API_KEY=...
이런 값들은 외부에 노출되면 바로 보안 사고로 이어질 수 있다.
반대로 민감정보는 아니지만 환경마다 달라지는 설정값도 있다.
APP_ENV=prod
LOG_LEVEL=info
DB_HOST=demo-mysql.xxxxxx.ap-northeast-2.rds.amazonaws.com
CACHE_TTL=300
FEATURE_NEW_UI=true
이 값들은 비밀번호처럼 민감하지는 않더라도 코드에 하드코딩하면 환경별 관리가 어려워진다.
그래서 값을 다음처럼 분리해서 생각하는 것이 좋다.
| 구분 | 예시 | 권장 저장 위치 |
|---|---|---|
| 민감정보 | DB password, API key, token | Secrets Manager |
| 일반 설정값 | log level, endpoint, feature flag | SSM Parameter Store |
| 민감 설정값 | 간단한 password, internal token | SSM SecureString 또는 Secrets Manager |
정리하면 다음과 같다.
코드
→ 로직
Secrets Manager / Parameter Store
→ 값
2. Secrets Manager란 무엇인가
Secrets Manager는 AWS에서 제공하는 secret 관리 서비스다.
다음과 같은 값을 저장할 때 사용한다.
DB credential
API key
OAuth secret
JWT secret
외부 서비스 token
Secrets Manager의 핵심은 단순 저장이 아니라 secret의 생명주기 관리다.
저장
조회
권한 제어
버전 관리
자동 rotation
감사 로그
특히 DB password처럼 주기적으로 변경해야 하는 값은 Secrets Manager가 더 적합하다.
예를 들어 RDS의 master password를 Secrets Manager로 관리하면 Terraform 코드에 DB password를 직접 넣지 않아도 된다.
RDS
→ manage_master_user_password = true
→ Secrets Manager에 master password 저장
Secrets Manager는 강력하지만 비용이 발생한다.
따라서 모든 설정값을 Secrets Manager에 넣기보다는 정말 secret으로 관리해야 하는 값을 넣는 것이 좋다.
3. SSM Parameter Store란 무엇인가
SSM Parameter Store는 AWS Systems Manager의 기능 중 하나다.
애플리케이션 설정값을 이름 기반으로 저장하고 조회할 수 있다.
Parameter Store는 보통 다음과 같은 값을 관리할 때 사용한다.
환경별 endpoint
log level
feature flag
외부 API URL
AMI ID
간단한 설정값
일부 SecureString 값
Parameter Store는 이름을 계층형으로 만들 수 있다.
/demo/dev/db/host
/demo/dev/app/log-level
/demo/prod/db/host
/demo/prod/app/log-level
이 구조를 사용하면 환경별 설정을 깔끔하게 나눌 수 있다.
/project/env/category/name
Parameter Store의 타입은 대표적으로 다음 세 가지가 있다.
String
StringList
SecureString
| 타입 | 의미 | 예시 |
|---|---|---|
| String | 일반 문자열 | log level, endpoint |
| StringList | 쉼표로 구분되는 문자열 목록 | allowed origins, subnet id 목록 |
| SecureString | KMS로 암호화되는 문자열 | 간단한 password, token |
초보 단계에서는 이렇게 나누면 이해하기 쉽다.
자주 회전해야 하는 민감정보
→ Secrets Manager
환경 설정값
→ Parameter Store
간단한 민감 설정값
→ Parameter Store SecureString 또는 Secrets Manager
4. Secrets Manager와 Parameter Store 차이
Secrets Manager와 Parameter Store는 비슷해 보이지만 목적이 조금 다르다.
| 구분 | Secrets Manager | SSM Parameter Store |
|---|---|---|
| 주 목적 | 민감정보 관리 | 설정값 관리 |
| 대표 값 | DB password, API key, token | endpoint, log level, feature flag |
| Rotation | 지원 | 직접 구현 필요 |
| 계층형 이름 | 가능 | 계층형 설정 관리에 적합 |
| 사용 예 | RDS credential, OAuth secret | /project/prod/app/log-level |
절대적인 정답은 없다.
다만 일반적인 기준은 다음과 같다.
비밀번호, 토큰, API Key
→ Secrets Manager
환경별 일반 설정값
→ Parameter Store
암호화는 필요하지만 rotation까지 필요 없는 값
→ Parameter Store SecureString 고려
Secrets Manager는 secret의 생명주기 관리에 강하고, Parameter Store는 환경별 설정값 관리에 강하다.
5. Terraform으로 Secret 메타데이터 만들기
Secrets Manager는 보통 두 가지 리소스로 나누어 볼 수 있다.
aws_secretsmanager_secret
→ Secret 메타데이터
aws_secretsmanager_secret_version
→ Secret 실제 값
먼저 Secret 메타데이터만 만들 수 있다.
variable "project_name" {
description = "Project name"
type = string
}
variable "environment" {
description = "Environment name"
type = string
}
resource "aws_secretsmanager_secret" "db" {
name = "/${var.project_name}/${var.environment}/db/credential"
description = "Database credential for ${var.project_name} ${var.environment}"
recovery_window_in_days = 7
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-db-credential"
})
}
이 리소스는 Secret의 이름과 설명, 태그를 만든다.
하지만 이 코드만으로는 실제 secret 값이 들어가지는 않는다.
Secret 메타데이터 생성
→ 값은 아직 없음
실무에서는 Terraform으로 Secret 이름과 접근 권한 구조만 만들고, 실제 값은 AWS Console, AWS CLI, CI/CD, 또는 별도 secret 관리 절차로 주입하는 방식도 많이 사용한다.
6. Terraform으로 Secret 값을 넣을 때의 주의점
Terraform으로 Secret 값을 직접 넣을 수도 있다.
variable "db_username" {
description = "Database username"
type = string
}
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
username = var.db_username
password = var.db_password
host = var.db_host
port = 3306
dbname = var.db_name
})
}
하지만 이 방식은 주의해야 한다.
Terraform으로 secret 값을 만들면 그 값이 Terraform state에 남을 수 있다.
sensitive = true는 CLI 출력에서 값을 숨기는 데 도움이 되지만,
state에 값이 저장되는 문제를 완전히 없애지는 않는다.
따라서 운영 환경에서는 다음 원칙을 고려하는 것이 좋다.
Secret 메타데이터
→ Terraform으로 생성 가능
Secret 실제 값
→ Terraform 밖에서 주입하는 방식 고려
Terraform state
→ 민감정보로 취급하고 접근 권한 제한
최신 Terraform과 Provider에서는 일부 리소스에서 state에 저장하지 않는 write-only 인자를 지원하기도 한다. 다만 프로젝트에서 사용하는 Terraform 버전과 AWS Provider 버전에 따라 지원 여부가 다르므로, 실제 적용 전 문서를 확인해야 한다.
7. SSM Parameter Store 구현하기
이번에는 SSM Parameter Store를 Terraform으로 만들어보자.
일반 설정값은 String 타입으로 만들 수 있다.
7.1 String Parameter
resource "aws_ssm_parameter" "log_level" {
name = "/${var.project_name}/${var.environment}/app/log-level"
description = "Application log level"
type = "String"
value = "info"
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-log-level"
})
}
이 값은 애플리케이션에서 log level 설정으로 사용할 수 있다.
/demo/prod/app/log-level
→ info
7.2 StringList Parameter
여러 값을 목록처럼 저장하고 싶다면 StringList를 사용할 수 있다.
resource "aws_ssm_parameter" "allowed_origins" {
name = "/${var.project_name}/${var.environment}/app/allowed-origins"
description = "Allowed CORS origins"
type = "StringList"
value = "https://example.com,https://admin.example.com"
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-allowed-origins"
})
}
StringList는 쉼표로 구분된 문자열로 저장된다.
https://example.com,https://admin.example.com
7.3 SecureString Parameter
암호화가 필요한 값은 SecureString으로 만들 수 있다.
variable "external_api_token" {
description = "External API token"
type = string
sensitive = true
}
resource "aws_ssm_parameter" "api_token" {
name = "/${var.project_name}/${var.environment}/app/api-token"
description = "External API token"
type = "SecureString"
value = var.external_api_token
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-api-token"
})
}
하지만 SecureString도 Terraform으로 값을 직접 넣으면 state에 평문 값이 남을 수 있다.
따라서 운영 환경에서는 SecureString 값도 Terraform 밖에서 주입하는 방식을 고려하는 것이 좋다.
권장 방향:
Terraform → Parameter 이름과 권한 구조 관리
실제 값 → Console / CLI / CI/CD / 별도 secret 관리 절차로 주입
8. IAM 권한 부여하기
Secrets Manager나 Parameter Store에 값을 저장했다고 해서 애플리케이션이 자동으로 읽을 수 있는 것은 아니다.
애플리케이션이 실행되는 Role에 읽기 권한을 부여해야 한다.
EC2 Role
ECS Task Role
ECS Task Execution Role
Lambda Role
8.1 Secrets Manager 읽기 권한
특정 Secret을 읽는 권한은 다음처럼 줄 수 있다.
resource "aws_iam_policy" "read_db_secret" {
name = "${var.project_name}-${var.environment}-read-db-secret"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = aws_secretsmanager_secret.db.arn
}
]
})
}
8.2 SSM Parameter 읽기 권한
Parameter Store 값을 읽으려면 다음 권한이 필요하다.
ssm:GetParameter
ssm:GetParameters
ssm:GetParametersByPath
예를 들어 특정 경로 아래의 Parameter를 읽을 수 있도록 할 수 있다.
resource "aws_iam_policy" "read_app_parameters" {
name = "${var.project_name}-${var.environment}-read-app-parameters"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
]
Resource = "arn:aws:ssm:${var.aws_region}:${var.aws_account_id}:parameter/${var.project_name}/${var.environment}/*"
}
]
})
}
SecureString을 customer managed KMS key로 암호화했다면 kms:Decrypt 권한도 필요할 수 있다.
SSM SecureString 읽기
→ ssm:GetParameter
→ kms:Decrypt 필요 가능
9. ECS에서 Secret과 Parameter 사용하기
ECS는 Secrets Manager나 SSM Parameter Store 값을 컨테이너 환경 변수로 주입할 수 있다.
일반 환경 변수는 environment에 넣고, 민감정보는 secrets 블록을 사용하는 것이 좋다.
environment
→ 일반 설정값
secrets
→ 민감정보
9.1 ECS Task Execution Role 권한
ECS가 컨테이너 실행 시점에 Secret 값을 가져와 환경 변수로 주입하려면 Task Execution Role에 권한이 필요하다.
resource "aws_iam_role" "ecs_task_execution" {
name = "${var.project_name}-${var.environment}-ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution_default" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
Secrets Manager 값을 주입하려면 추가로 Secret 읽기 권한을 붙일 수 있다.
resource "aws_iam_policy" "ecs_execution_read_secrets" {
name = "${var.project_name}-${var.environment}-ecs-execution-read-secrets"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = aws_secretsmanager_secret.db.arn
},
{
Effect = "Allow"
Action = [
"ssm:GetParameters",
"ssm:GetParameter"
]
Resource = "arn:aws:ssm:${var.aws_region}:${var.aws_account_id}:parameter/${var.project_name}/${var.environment}/*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_execution_read_secrets" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = aws_iam_policy.ecs_execution_read_secrets.arn
}
정리하면, ECS에서 secrets 블록으로 값을 주입할 때는 ECS Agent가 값을 가져와야 하므로 Task Execution Role 권한이 중요하다.
9.2 ECS Task Role 권한
반면 애플리케이션 코드가 실행 중에 직접 Secrets Manager나 Parameter Store를 조회한다면 Task Role에 권한이 필요하다.
resource "aws_iam_role" "ecs_task" {
name = "${var.project_name}-${var.environment}-ecs-task-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_read_parameters" {
role = aws_iam_role.ecs_task.name
policy_arn = aws_iam_policy.read_app_parameters.arn
}
초보자가 헷갈리기 쉬운 부분은 이 차이다.
| Role | 사용 주체 | 사용 예 |
|---|---|---|
| Task Execution Role | ECS가 Task를 실행할 때 사용 | ECR pull, CloudWatch Logs, secrets 주입 |
| Task Role | 컨테이너 안의 애플리케이션이 사용 | S3 접근, SDK로 Secret 조회 |
9.3 ECS Task Definition 예시
resource "aws_ecs_task_definition" "app" {
family = "${var.project_name}-${var.environment}-app"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256
memory = 512
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "app"
image = var.image
environment = [
{
name = "APP_ENV"
value = var.environment
},
{
name = "LOG_LEVEL"
value = "info"
}
]
secrets = [
{
name = "DB_CREDENTIAL"
valueFrom = aws_secretsmanager_secret.db.arn
},
{
name = "API_TOKEN"
valueFrom = aws_ssm_parameter.api_token.arn
}
]
}
])
}
이렇게 하면 컨테이너 안에서는 다음 환경 변수로 값을 받을 수 있다.
APP_ENV
LOG_LEVEL
DB_CREDENTIAL
API_TOKEN
다만 secret을 환경 변수로 주입하면 애플리케이션에서 사용하기는 편하지만, 컨테이너 내부 환경 변수 노출이나 로그 출력에 주의해야 한다.
10. Lambda에서 Secret과 Parameter 사용하기
Lambda는 보통 실제 secret 값을 환경 변수에 직접 넣기보다, Secret 이름이나 ARN을 환경 변수에 넣고 코드에서 Secrets Manager를 조회하는 방식을 사용한다.
Lambda 환경 변수
→ Secret 이름만 저장
Lambda 코드
→ Secrets Manager / Parameter Store 조회
10.1 Lambda 실행 Role
Lambda가 Secrets Manager 값을 읽으려면 실행 Role에 권한이 필요하다.
resource "aws_iam_role" "lambda" {
name = "${var.project_name}-${var.environment}-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
10.2 Lambda Secret 읽기 권한
resource "aws_iam_policy" "lambda_read_db_secret" {
name = "${var.project_name}-${var.environment}-lambda-read-db-secret"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = aws_secretsmanager_secret.db.arn
},
{
Effect = "Allow"
Action = [
"ssm:GetParameter",
"ssm:GetParameters"
]
Resource = "arn:aws:ssm:${var.aws_region}:${var.aws_account_id}:parameter/${var.project_name}/${var.environment}/*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_read_db_secret" {
role = aws_iam_role.lambda.name
policy_arn = aws_iam_policy.lambda_read_db_secret.arn
}
10.3 Lambda 환경 변수 설정
환경 변수에는 실제 password가 아니라 Secret 이름이나 ARN을 넣는다.
resource "aws_lambda_function" "app" {
function_name = "${var.project_name}-${var.environment}-app"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs20.x"
filename = "lambda.zip"
environment {
variables = {
APP_ENV = var.environment
DB_SECRET_NAME = aws_secretsmanager_secret.db.name
LOG_LEVEL_PARAM = aws_ssm_parameter.log_level.name
}
}
}
이 구조의 장점은 Lambda 설정에 실제 secret 값이 직접 들어가지 않는다는 점이다.
Lambda 환경 변수
→ Secret 이름만 저장
Secrets Manager
→ 실제 username / password 저장
10.4 Lambda 코드 예시
Node.js Lambda라면 다음처럼 Secret을 조회할 수 있다.
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
const secretsClient = new SecretsManagerClient({});
const ssmClient = new SSMClient({});
let cachedDbSecret;
let cachedLogLevel;
async function getDbSecret() {
if (cachedDbSecret) {
return cachedDbSecret;
}
const response = await secretsClient.send(
new GetSecretValueCommand({
SecretId: process.env.DB_SECRET_NAME,
})
);
cachedDbSecret = JSON.parse(response.SecretString);
return cachedDbSecret;
}
async function getLogLevel() {
if (cachedLogLevel) {
return cachedLogLevel;
}
const response = await ssmClient.send(
new GetParameterCommand({
Name: process.env.LOG_LEVEL_PARAM,
})
);
cachedLogLevel = response.Parameter.Value;
return cachedLogLevel;
}
export const handler = async (event) => {
const dbSecret = await getDbSecret();
const logLevel = await getLogLevel();
return {
statusCode: 200,
body: JSON.stringify({
message: "loaded config",
dbUser: dbSecret.username,
logLevel: logLevel,
}),
};
};
위 코드에서 중요한 점은 Secret과 Parameter를 매 요청마다 무조건 다시 조회하지 않고 메모리에 캐싱한다는 점이다.
첫 번째 호출
→ Secrets Manager / Parameter Store 조회
→ 메모리에 캐싱
이후 같은 Lambda 실행 환경 재사용
→ 캐시된 값 사용
다만 Secret rotation을 사용한다면 캐시 만료 시간도 함께 고려해야 한다.
10.5 Lambda Extension 방식
Lambda에서는 AWS Parameters and Secrets Lambda Extension을 사용할 수도 있다.
이 방식은 코드에서 SDK를 직접 호출하는 대신, Lambda 실행 환경 내부의 로컬 HTTP 엔드포인트를 통해 Secret이나 Parameter를 조회한다.
Lambda 코드
→ localhost extension endpoint 호출
→ Secrets Manager / Parameter Store 값 조회
초보 단계에서는 먼저 SDK 방식으로 흐름을 이해하고, 이후 성능이나 캐싱을 더 신경 써야 할 때 Extension을 고려해도 충분하다.
11. EC2에서 Secret과 Parameter 사용하기
EC2는 IAM Instance Profile을 통해 Secrets Manager나 Parameter Store를 읽을 수 있다.
EC2
→ Instance Profile
→ IAM Role
→ Secrets Manager / Parameter Store 읽기
11.1 EC2 IAM Role
resource "aws_iam_role" "ec2" {
name = "${var.project_name}-${var.environment}-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
11.2 EC2 Role에 읽기 권한 부여
resource "aws_iam_policy" "ec2_read_config" {
name = "${var.project_name}-${var.environment}-ec2-read-config"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = aws_secretsmanager_secret.db.arn
},
{
Effect = "Allow"
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
]
Resource = "arn:aws:ssm:${var.aws_region}:${var.aws_account_id}:parameter/${var.project_name}/${var.environment}/*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ec2_read_config" {
role = aws_iam_role.ec2.name
policy_arn = aws_iam_policy.ec2_read_config.arn
}
11.3 Instance Profile 연결
resource "aws_iam_instance_profile" "ec2" {
name = "${var.project_name}-${var.environment}-ec2-profile"
role = aws_iam_role.ec2.name
}
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"
subnet_id = aws_subnet.private.id
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile = aws_iam_instance_profile.ec2.name
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-ec2"
})
}
11.4 EC2 내부에서 AWS CLI로 조회하기
EC2 내부에서는 AWS CLI로 Secret이나 Parameter를 조회할 수 있다.
aws secretsmanager get-secret-value \
--secret-id /demo/prod/db/credential
Parameter Store 값은 다음처럼 조회할 수 있다.
aws ssm get-parameter \
--name /demo/prod/app/log-level
SecureString 값을 복호화해서 읽으려면 --with-decryption 옵션을 사용한다.
aws ssm get-parameter \
--name /demo/prod/app/api-token \
--with-decryption
11.5 User Data에서 설정 파일 생성 예시
EC2 부팅 시 User Data에서 Parameter Store 값을 읽어 설정 파일을 만들 수도 있다.
user_data = <<-EOF
#!/bin/bash
APP_ENV=$(aws ssm get-parameter \
--name "/demo/prod/app/env" \
--query "Parameter.Value" \
--output text)
LOG_LEVEL=$(aws ssm get-parameter \
--name "/demo/prod/app/log-level" \
--query "Parameter.Value" \
--output text)
cat > /etc/app.env <<EOT
APP_ENV=$APP_ENV
LOG_LEVEL=$LOG_LEVEL
EOT
EOF
다만 User Data에 secret 값을 파일로 남기는 방식은 주의해야 한다.
비밀번호나 토큰은 파일 권한, 로그 출력, AMI 이미지화 여부까지 함께 고려해야 한다.
12. 실전 예제: DB 접속 정보 관리
이번에는 DB 접속 정보를 어떻게 나누어 관리할지 예시로 보자.
DB 접속에는 보통 다음 값들이 필요하다.
host
port
dbname
username
password
이 중에서 모든 값이 같은 수준의 secret은 아니다.
| 값 | 민감도 | 저장 위치 예시 |
|---|---|---|
| host | 낮음 | SSM Parameter Store |
| port | 낮음 | SSM Parameter Store |
| dbname | 낮음 | SSM Parameter Store |
| username | 중간 | Secrets Manager 또는 Parameter Store |
| password | 높음 | Secrets Manager |
운영에서는 DB credential을 하나의 JSON Secret으로 관리할 수 있다.
{
"username": "admin",
"password": "xxxx",
"host": "demo-mysql.xxxxxx.ap-northeast-2.rds.amazonaws.com",
"port": 3306,
"dbname": "appdb"
}
다만 RDS에서 manage_master_user_password = true를 사용한다면 master password는 RDS가 Secrets Manager에 생성한 Secret으로 관리된다.
이 경우 애플리케이션에는 다음 값들을 전달해야 한다.
RDS endpoint
DB name
Secret ARN 또는 Secret name
예를 들어 RDS endpoint는 Parameter Store에 저장할 수 있다.
resource "aws_ssm_parameter" "db_host" {
name = "/${var.project_name}/${var.environment}/db/host"
type = "String"
value = aws_db_instance.mysql.address
}
resource "aws_ssm_parameter" "db_name" {
name = "/${var.project_name}/${var.environment}/db/name"
type = "String"
value = aws_db_instance.mysql.db_name
}
RDS가 관리하는 master password secret ARN은 output으로 전달할 수 있다.
output "db_host_parameter_name" {
description = "SSM parameter name for DB host"
value = aws_ssm_parameter.db_host.name
}
output "db_secret_arn" {
description = "RDS master user secret ARN"
value = aws_db_instance.mysql.master_user_secret[0].secret_arn
}
이렇게 하면 애플리케이션은 Parameter Store에서 host를 읽고, Secrets Manager에서 password를 읽는 구조로 만들 수 있다.
App
→ SSM Parameter Store에서 DB host 조회
→ Secrets Manager에서 DB credential 조회
→ RDS 접속
13. 의존성 흐름
Secrets Manager와 Parameter Store를 Terraform으로 구현할 때의 의존성 흐름은 다음과 같다.
Secret / Parameter
→ IAM Policy
→ IAM Role
→ 실행 리소스
여기서 실행 리소스는 ECS Task, Lambda Function, EC2 Instance 같은 리소스를 의미한다.

이 구조에서 중요한 점은 두 가지다.
1. IAM Policy는 Secret / Parameter의 ARN을 참조한다.
2. 실행 리소스는 IAM Role과 Secret / Parameter 이름 또는 ARN을 참조한다.
14. 자주 하는 실수
14.1 Secret 값을 Terraform 코드에 직접 작성함
다음처럼 secret 값을 코드에 직접 쓰면 안 된다.
secret_string = "my-password"
코드 저장소와 Terraform state에 민감정보가 남을 수 있다.
14.2 sensitive = true면 state에도 안 남는다고 생각함
sensitive = true는 화면 출력이나 plan 출력에서 값을 숨기는 데 도움을 준다.
하지만 state 저장 문제를 완전히 없애지는 않는다.
sensitive = true
→ 출력 숨김
state
→ 값 저장 가능성 있음
따라서 state 자체를 민감한 데이터로 보고 접근 권한을 제한해야 한다.
14.3 SecureString이면 Terraform state도 안전하다고 생각함
SSM Parameter Store의 SecureString은 AWS 안에서 암호화되어 저장된다.
하지만 Terraform으로 SecureString 값을 직접 관리하면 Terraform state에는 평문 값이 남을 수 있다.
따라서 SecureString을 사용하더라도 Terraform state 보안을 함께 고려해야 한다.
14.4 Parameter Store와 Secrets Manager를 구분하지 않고 사용함
모든 값을 Secrets Manager에 넣거나, 모든 값을 Parameter Store에 넣는 방식은 비용과 관리 측면에서 비효율적일 수 있다.
값의 성격에 따라 나누는 것이 좋다.
회전이 필요한 credential
→ Secrets Manager
환경별 일반 설정값
→ Parameter Store
간단한 암호화 설정값
→ SecureString 고려
14.5 IAM 권한을 너무 넓게 줌
다음처럼 모든 secret과 parameter를 읽을 수 있게 하는 것은 편하지만 위험하다.
Resource = "*"
가능하면 특정 ARN이나 특정 경로로 제한한다.
arn:aws:ssm:ap-northeast-2:123456789012:parameter/demo/prod/*
arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:/demo/prod/*
14.6 ECS에서 secret을 environment에 평문으로 넣음
ECS Task Definition에서 민감정보를 일반 environment에 직접 넣는 것은 피하는 것이 좋다.
environment = [
{
name = "DB_PASSWORD"
value = "my-password"
}
]
대신 secrets 블록을 사용해서 Secrets Manager나 Parameter Store 값을 참조한다.
secrets = [
{
name = "DB_PASSWORD"
valueFrom = aws_secretsmanager_secret.db.arn
}
]
14.7 Lambda 환경 변수에 secret 값을 직접 넣음
Lambda 환경 변수에는 실제 password나 token보다 Secret 이름이나 ARN을 넣는 것이 좋다.
권장하지 않음:
DB_PASSWORD=my-password
권장:
DB_SECRET_NAME=/demo/prod/db/credential
이후 Lambda 코드에서 실행 Role 권한으로 Secrets Manager를 조회한다.
14.8 EC2 User Data에 secret 값을 남김
User Data는 EC2 초기 설정에 유용하지만, 민감정보를 직접 넣는 것은 위험할 수 있다.
User Data 내용이 로그나 메타데이터 조회 과정에서 노출될 수 있기 때문이다.
따라서 User Data에는 secret 값 자체보다 Secret 이름이나 Parameter 이름을 넣고, 실행 시점에 IAM Role로 조회하는 방식이 더 안전하다.
14.9 Secret rotation을 고려하지 않음
Secrets Manager를 사용하는 이유 중 하나는 rotation이다.
DB password나 외부 API token처럼 주기적으로 변경해야 하는 값은 rotation 전략을 함께 고려해야 한다.
Secret 저장
→ 접근 권한 부여
→ 애플리케이션 사용
→ rotation 전략 수립
다만 모든 secret에 자동 rotation이 필요한 것은 아니다.
rotation이 필요한 값과 그렇지 않은 값을 구분해서 설계하는 것이 좋다.
14.10 삭제된 Secret이 바로 없어졌다고 생각함
Secrets Manager Secret은 삭제 시 즉시 완전히 사라지는 것이 아니라 복구 가능한 기간을 둘 수 있다.
Terraform에서는 다음처럼 복구 기간을 설정할 수 있다.
resource "aws_secretsmanager_secret" "db" {
name = "/demo/prod/db/credential"
recovery_window_in_days = 7
}
학습용에서는 즉시 삭제가 편할 수 있지만, 운영 환경에서는 복구 기간을 두는 것이 안전할 수 있다.
15. 마무리
이번 글에서는 Terraform으로 Secrets Manager와 SSM Parameter Store를 구현하는 방법을 정리했다.
핵심은 다음과 같다.
Secrets Manager
→ 비밀번호, 토큰, API Key 같은 민감정보 관리
SSM Parameter Store
→ 환경별 설정값과 간단한 SecureString 관리
IAM
→ 애플리케이션이 secret과 parameter를 읽을 권한 제어
ECS
→ Task Definition의 secrets 블록으로 주입 가능
Lambda
→ 환경 변수에는 Secret 이름을 넣고 코드에서 조회
EC2
→ Instance Profile 권한으로 CLI 또는 애플리케이션에서 조회
Terraform state
→ 민감정보가 남을 수 있으므로 반드시 보호 필요
Secret 관리에서 가장 중요한 것은 단순히 어디에 저장하느냐가 아니다.
누가 읽을 수 있는지, 어떻게 회전할지, state에 값이 남지 않는지, 애플리케이션에 어떻게 전달할지를 함께 봐야 한다.
한 줄 정리
Secrets Manager와 Parameter Store는 값을 코드에서 분리하고, IAM으로 읽기 권한을 제어하며, 실행 환경에 맞는 방식으로 전달하기 위한 도구다.
다음 글에서는 ALB와 Target Group을 Terraform으로 구현해본다. ALB는 외부 요청을 받아 애플리케이션 서버로 전달하고, Target Group Health Check를 통해 정상 대상에게만 트래픽을 보내는 핵심 리소스다.
'테라폼' 카테고리의 다른 글
| 4-10. 테라폼 - ECR 구현하기 (0) | 2026.05.14 |
|---|---|
| 4-9. 테라폼 - ALB와 Target Group 구현하기 (0) | 2026.05.14 |
| 4-7. 테라폼 - RDS MySQL 구현하기 (0) | 2026.05.13 |
| 4-6. 테라폼 - S3 구현하기 (0) | 2026.05.13 |
| 4-5. 테라폼 - EC2 구현하기 (0) | 2026.05.12 |