Managing Secrets with Terraform
Secret Management Basics
배경
: 배포 과정에서 민감 정보 (DB암호, API 키, TLS인증서, SSH키, GPG 키 등)를 안전하게 관리가 필요함
resource "aws_db_instance" "example" {
identifier_prefix = "terraform-up-and-running"
engine = "mysql"
allocated_storage = 10
instance_class = "db.t2.micro"
skip_final_snapshot = true
db_name = var.db_name
# How to set these parameters securely?
username = "???"
password = "???"
}
민감 정보 관리 규칙
: 첫번째 규칙(민감 정보를 평문으로 저장 하지 말것), 두번째 규칙(DO NOT STORE SECRETS IN PLAIN TEXT)
resource "aws_db_instance" "example" {
identifier_prefix = "terraform-up-and-running"
engine = "mysql"
allocated_storage = 10
instance_class = "db.t2.micro"
skip_final_snapshot = true
db_name = var.db_name
# DO NOT DO THIS!!!
username = "admin"
password = "password"
# DO NOT DO THIS!!!
}
- 버전 관리 시스템에 위 코드가 있을 경우 누구나 해당 데이터베이스를 비밀번호로 접속 할 수 있다
- 또한 repo 를 복사해서 개인 PC의 로컬에 비밀번호 정보가 저장 될 수 있다
- 이 경우 해당 비밀번호 접근 및 사용에 대한 통제나 감사를 확인 할 수 없다
⇒ 따라서, 비밀번호 관리 도구가 필요하다
Secret Management tools
The types of secrets you store
: 3가지 유형, 유형에 따라 관리 방식의 차이가 있음
- Personal secrets : 개인 소유의 암호, 예) 방문하는 웹 사이트 사용자 이름과 암호, SSH 키
- Customer secrets : 고객 소유의 암호, 예) 사이트를 운영하는데 고객의 사용자 이름과 암호, 고객의 개인정보 등 → 해싱 알고리즘 사용
- Infrastructure secrets : 인프라 관련 암호, 예) DB암호, API 키 등 → 암복호화 알고리즘 사용
The way you store secrets
: 2가지 암호 저장 방식 - 파일 기반 암호 저장 vs 중앙 집중식 암호 저장
- File-based secret stores : 민감 정보를 암호화 후 저장 → 암호화 관련 키 관리가 중요 ⇒ 해결책 : AWS KMS, GCP KMS 혹은 PGP Key
- Centralized secret stores : 일반적으로 데이터베이스(MySQL, Psql, DynamoDB 등)에 비밀번호를 암호화하여 저장, 암호화 키는 서비스 자체 혹은 클라우드 KMS를 사용
The interface you use to access secrets
: 암호 관리 툴은 API, CLI, UI 통해서 사용할 수 있음
A comparison of secret management tools
: 툴 비교
Types of secrets | Secret Storage | Secret Interface | |
---|---|---|---|
HashiCorp Vault | Infrastructure | Centralized service | UI, API, CLI |
AWS Secrets Manager | Infrastructure | Centralized service | UI, API, CLI |
GCP Secrets Manager | Infrastructure | Centralized service | UI, API, CLI |
Azure Key Vault | Infrastructure | Centralized service | UI, API, CLI |
Confidant | Infrastructure | Centralized service | UI, API, CLI |
Keywhiz | Infrastructure | Centralized service | API, CLI |
sops | Infrastructure | Files | CLI |
git-secret | Infrastructure | Files | CLI |
1Password | Personal | Centralized service | UI, API, CLI |
LastPass | Personal | Centralized service | UI, API, CLI |
Bitwarden | Personal | Centralized service | UI, API, CLI |
KeePass | Personal | Files | UI, CLI |
Keychain (macOS) | Personal | Files | UI, CLI |
Credential Manager (Windows) | Personal | Files | UI, CLI |
pass | Personal | Files | CLI |
Active Directory | Customer | Centralized service | UI, API, CLI |
Auth0 | Customer | Centralized service | UI, API, CLI |
Okta | Customer | Centralized service | UI, API, CLI |
OneLogin | Customer | Centralized service | UI, API, CLI |
Ping | Customer | Centralized service | UI, API, CLI |
AWS Cognito | Customer | Centralized service | UI, API, CLI |
Secret Management tools with Terraform
- Providers
실습
: 인증관련 정보 노출- IAM 자격 증명 정보를 코드의 Provider 에 직접 입력 → 안전하지 않다!
-
하나의 자격 증명만 사용하도록 하드 코딩되어 있어 실용적이지 않음
provider "aws" { region = "us-east-2" # DO NOT DO THIS!!! access_key = "(ACCESS_KEY)" secret_key = "(SECRET_KEY)" # DO NOT DO THIS!!! }
-
사용자 Human users : 환경 변수 사용 The most common option is to use environment variables.
$ export AWS_ACCESS_KEY_ID=(YOUR_ACCESS_KEY_ID) $ export AWS_SECRET_ACCESS_KEY=(YOUR_SECRET_ACCESS_KEY)
- 환경 변수 사용 시 코드에 암호가 들어가지 않고, 자격 증명이 디스크가 아닌 메모리에만 저장됨
- 다만 키정보를 어디에 저장할까? → 여전히 컴퓨터 파일에 평문으로 저장 시 문제 될 수 있음 ⇒ 1Password/LassPass 저장 후 사용 할 수 있음
-
예) 1Password : 로그인 후 키정보 획득 후 환경 변수에 저장
$ eval $(op signin my) $ export AWS_ACCESS_KEY_ID=$(op get item 'aws-dev' --fields 'id') $ export AWS_SECRET_ACCESS_KEY=$(op get item 'aws-dev' --fields 'secret')
-
실습
예) 오픈소스 aws-vault : - Github 44bits# mac 설치 brew install --cask aws-vault # 윈도우 설치 choco install aws-vault # 윈도우 Chocolatey scoop install aws-vault # 윈도우 Scoop # 버전 확인 aws-vault --version v6.6.0 # 설정 #aws-vault add <PROFILE_NAME> aws-vault add t101 Enter Access Key Id: (YOUR_ACCESS_KEY_ID) Enter Secret Key: (YOUR_SECRET_ACCESS_KEY) # 확인 aws-vault ls Profile Credentials Sessions ======= =========== ======== default - - t101 t101 - # 사용 #aws-vault exec <PROFILE> -- <COMMAND> aws-vault exec t101 -- aws s3 ls aws-vault exec --debug t101 -- aws s3 ls aws-vault exec t101 -- terraform plan aws-vault exec t101 -- terraform apply
- aws-vault 설정 후 ~/.aws/credentials 파일은 삭제를 권장
- 좀 더 보안 강화 : MFA 사용, IAM 사용자 기반 → IAM 역할 기반
- 머신(시스템) 사용자 Machine users : 시스템에 따라 인증 방식이 조금 다름, 예) CI서버
- 3가지 예 ⇒ 프로덕션 수준의 CI/CD workflows 관련 내용은 9장에서 다룸
- CircleCI as a CI server, with stored secrets
- EC2 Instance running Jenkins as a CI server, with IAM roles
- GitHub Actions as a CI server, with OIDC
- 3가지 예 ⇒ 프로덕션 수준의 CI/CD workflows 관련 내용은 9장에서 다룸
- [Providers] CircleCI as a CI server, with stored secrets : CI/CD 플랫폼인 CircleCI 를 통해 테라폼 코드를 실행한다고 가정
-
.circleci/config.yml 파일로 빌드 단계 구성 및 terrfaform apply 로 job 실행 할 수 있다
version: '2.1' orbs: # Install Terraform using a CircleCi Orb terraform: circleci/terraform@1.1.0 jobs: # Define a job to run 'terraform apply' terraform_apply: executor: terraform/default steps: - checkout # git clone the code - terraform/init # Run 'terraform init' - terraform/apply # Run 'terraform apply' workflows: # Create a workflow to run the 'terraform apply' job defined above deploy: jobs: - terraform_apply # Only run this workflow on commits to the main branch filters: branches: only: - main
- A CircleCI Context with AWS credentials : 자격 증명을 CircleCI Context 에 저장 후 빌드 실행 시 워크플로우 환경변수(액세스키, 시크릿키)에 Context 정보를 노출
-
Finally, you update the workflows in your .circleci/config.yml file to use your CircleCI Context via the context parameter:
workflows: # Create a workflow to run the 'terraform apply' job defined above deploy: jobs: **- terraform_apply** # Only run this workflow on commits to the main branch filters: branches: only: - main # Expose secrets in the CircleCI context as environment variables context: - example-context
- 단점 : 자격 증명을 수동 관리, 영구 자격 증명 사용 필요
-
- [Providers] EC2 Instance running Jenkins as a CI server, with IAM roles
실습
: EC2에 Jenkins 설치 후 CI서버로 테라폼 코드를 실행한다고 가정 시 IAM roles 활용 - 링크- IAM role 는 특정 사용자에 연결되지 않으며, 영구적인 자격증명이 아닙니다.
graph LR; A[dev-aws] -.->|STS assume role| B(STS); B -.->|AmazoneEc2 FullAccess| C(IAM Role); C -.->|단기 임시 보안| A; A -.->|EC2 Full 사용가능| EC2;- Role(EC2FullAccess) Switch 를 통해, EC2를 사용할 수 있습니다
-
sg.tf 코드 파일 생성
cat <<EOT > sg.tf resource "aws_security_group" "stg_mysg" { name = "T101 SG" description = "T101 Study SG" } resource "aws_security_group_rule" "stg_mysginbound" { type = "ingress" from_port = 0 to_port = **22** protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.stg_mysg.id } resource "aws_security_group_rule" "stg_mysgoutbound" { type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.stg_mysg.id } EOT
-
iamec2.tf 코드 파일 생성
# SSH 키페어 이름 지정 SSHKP=<각자 자신의 SSH 키페어 이름> SSHKP=kp-gasida
cat <<EOT > iamec2.tf provider "aws" { region = "ap-northeast-2" } resource "aws_instance" "example" { ami = "ami-0eddbd81024d3fbdd" instance_type = "t2.micro" key_name = "$SSHKP" vpc_security_group_ids = ["\${aws_security_group.stg_mysg.id}"] # Attach the instance profile iam_instance_profile = aws_iam_instance_profile.instance.name } # Create an IAM role resource "aws_iam_role" "instance" { name_prefix = var.name assume_role_policy = data.aws_iam_policy_document.assume_role.json } # Allow the IAM role to be assumed by EC2 instances data "aws_iam_policy_document" "assume_role" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } } } # Attach the EC2 admin permissions to the IAM role resource "aws_iam_role_policy" "example" { role = aws_iam_role.instance.id policy = data.aws_iam_policy_document.ec2_admin_permissions.json } # Create an IAM policy that grants EC2 admin permissions data "aws_iam_policy_document" "ec2_admin_permissions" { statement { effect = "Allow" actions = ["ec2:*"] resources = ["*"] } } # Create an instance profile with the IAM role attached resource "aws_iam_instance_profile" "instance" { role = aws_iam_role.instance.name } EOT
-
outputs.tf 코드 파일 생성
cat <<EOT > outputs.tf output "instance_id" { value = aws_instance.example.id description = "The ID of the EC2 instance" } output "instance_ip" { value = aws_instance.example.public_ip description = "The public IP of the EC2 instance" } output "iam_role_arn" { value = aws_iam_role.instance.arn description = "The ARN of the IAM role" } EOT
-
variables.tf 코드 파일 생성
cat <<EOT > variables.tf variable "name" { description = "The name used to namespace all the resources created by this module" type = string default = "ec2-iam-role-example" } EOT
-
init & plan & apply
terraform init && terraform plan && terraform apply -auto-approve Outputs: iam_role_arn = "arn:aws:iam::911283464785:role/ec2-iam-role-example20221120061438356500000001" instance_id = "i-03df84ae71e7b9aa2" instance_ip = "43.201.114.28"
-
각자 자신의 SSH 키페어로 EC2 접속 후 IAM role 동작 확인
#ssh -i <자신의 SSH 키파일 지정> ec2-user@$(terraform output -raw instance_ip) ssh -i ~/.ssh/kp-gasida.pem ec2-user@$(terraform output -raw instance_ip) --------------------------- curl -s http://169.254.169.254/latest/meta-data/ curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials ; echo IAMROLE=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials) curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAMROLE # EC2 관련 명령 실행 확인 export AWS_DEFAULT_REGION=ap-northeast-2 **aws ec2 describe-vpcs** --output table **aws ec2 describe-instances** --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ---------------------------
-
실습 자원 삭제
terraform destroy -auto-approve # 다음 실습을 위해 테라폼 코드 등 파일들 삭제
- [Providers] GitHub Actions as a CI server, with OIDC : Github Actions 은 직접 자격 증명과 OIDC Open ID Connect 지원
-
생활코딩
OAuth 2.0 역할
- 링크- User : 사용자 ⇒ Resource Owner
- mine : 내가 만들 서비스 ⇒ Client
- Their : 사용자가 회원 가입해있는곳(구글 페이스북 등) ⇒ Resource Server & Authorization Server (통상 인증 서버 역할은 R.S 에 통합)
Configure AWS Credentials For GitHub Actions with OIDC
-
You define GitHub Actions workflows in YAML files in a .github/workflows folder, such as the terraform.yml file shown here:
name: Terraform Apply # Only run this workflow on commits to the main branch on: push: branches: - 'main' jobs: TerraformApply: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # Run Terraform using HashiCorp's setup-terraform Action - uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.1.0 terraform_wrapper: false run: | terraform init terraform apply -auto-approve
-
OIDC 사용을 위해서 IAM OIDC identity provider 리소스를 사용하여 OIDC 정보 지정
# Create an IAM OIDC identity provider that trusts GitHub resource "aws_iam_openid_connect_provider" "github_actions" { url = "https://token.actions.githubusercontent.com" client_id_list = ["sts.amazonaws.com"] thumbprint_list = [ data.tls_certificate.github.certificates[0].sha1_fingerprint ] } # Fetch GitHub's OIDC thumbprint data "tls_certificate" "github" { url = "https://token.actions.githubusercontent.com" }
-
IAM 역할 생성
data "aws_iam_policy_document" "assume_role_policy" { statement { actions = ["sts:AssumeRoleWithWebIdentity"] effect = "Allow" principals { identifiers = [aws_iam_openid_connect_provider.github_actions.arn] type = "Federated" } condition { test = "StringEquals" variable = "token.actions.githubusercontent.com:sub" # The repos and branches defined in var.allowed_repos_branches # will be able to assume this IAM role values = [ for a in var.allowed_repos_branches : "repo:${a["org"]}/${a["repo"]}:ref:refs/heads/${a["branch"]}" ] } } }
-
입력 변수로 특정 Github 저장소나 브랜치만 IAM 역할 사용 가능하게 설정 가능
variable "allowed_repos_branches" { description = "GitHub repos/branches allowed to assume the IAM role." type = list(object({ org = string repo = string branch = string })) # Example: # allowed_repos_branches = [ # { # org = "brikis98" # repo = "terraform-up-and-running-code" # branch = "main" # } # ] }
-
먼저 워크플로우 맨 상단 빌드에 id-token: write 권한 부여
permissions: id-token: write
-
Terraform 실행 전, 아래 처럼 configure-aws-credentials 작업을 사용하여 AWS에 인증하는 빌드 단계를 추가
# Authenticate to AWS using OIDC - uses: aws-actions/configure-aws-credentials@v1 with: # Specify the IAM role to assume here role-to-assume: arn:aws:iam::123456789012:role/example-role aws-region: us-east-2 # Run Terraform using HashiCorp's setup-terraform Action - uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.1.0 terraform_wrapper: false run: | terraform init terraform apply -auto-approve
-
- [Resources and data sources] Environment variables : 리소스와 데이터 소스에 민감 정보 노출 - 예) 데이터베이스 암호 등
- 보완 방법 : Environment variables, Encrypted files, Secret stores
환경 변수 Environment variables
-
아래 처럼 DB 계정 정보를 변수로 사용 설정
variable "db_username" { description = "The username for the database" type = string sensitive = true } variable "db_password" { description = "The password for the database" type = string sensitive = true }
- 위 정보를 리소스에 전달
resource "aws_db_instance" "example" { identifier_prefix = "terraform-up-and-running" engine = "mysql" allocated_storage = 10 instance_class = "db.t2.micro" skip_final_snapshot = true db_name = var.db_name # Pass the secrets to the resource username = var.db_username password = var.db_password }
- 환경 변수로 전달 : TF_VAR_<변수이름>변수이름>
$ export TF_VAR_db_username=(DB_USERNAME) $ export TF_VAR_db_password=(DB_PASSWORD)
- 장점 : 모든 언어에서 환경 변수를 설정 및 쉽게 쓸 수 있고, 추가 비용이 들지 않음
- Keep plain-text secrets out of your code and version control system.
- Storing secrets is easy, as you can use just about any other secret management solution. That is, if your company already has a way to manage secrets, you can typically find a way to make it work with environment variables.
- Retrieving secrets is easy, as reading environment variables is straightforward in every language.
- Integrating with automated tests is easy, as you can easily set the environment variables to mock values.
- Using environment variables doesn’t cost any money, unlike some of the other secret management solutions discussed later.
- 단점 : 팀원 모두 환경 변수 수동 사용 관리가 필요, (테라폼 외부) 비밀 번호 관리 표준화가 어려움, 각 환경에서 버저닝/패키징/테스트 시 암호 환경 변수 지정 시 설정 오류 발생 가능
- Not everything is defined in the Terraform code itself. This makes understanding and maintaining the code harder. Everyone using your code has to know to take extra steps to either manually set these environment variables or run a wrapper script.
- Standardizing secret management practices is harder. Since all the management of secrets happens outside of Terraform, the code doesn’t enforce any security properties, and it’s possible someone is still managing the secrets in an insecure way (e.g., storing them in plain text).
- Since the secrets are not versioned, packaged, and tested with your code, configuration errors are more likely, such as adding a new secret in one environment (e.g., staging) but forgetting to add it in another environment (e.g., production).
- [Resources and data sources] Encrypted files
실습
: 암호를 파일에 저장 후 버전 관리 → 암호화 키를 클라우드 공급자 KMS를 통해 안전하게 저장 혹은 PGP 키 사용- AWS KMS 소개
- 암호화는 키를 사용해 평문을 암호문으로 변환하는 프로세스다
- 동일한 키를 사용해 암호문을 평문으로 변환할 수 있는데, 이를 복호화라고 한다
- AWS 키 관리 서비스 KMS는 공유 하드웨어 보안 모듈HSM 을 사용하면서 암호화키를 생성하고 관리할 수 있게 도와준다
- CloudHSM은 AWS 내에서 암호화키를 관리할 수 있지만 보안 강화를 위해 전용 HSM을 사용할 수 있는 서비스다
- 용어 변경 참고 : 기존 Customer Master Key (CMK) → AWS KMS key 혹은 KMS key 로 변경 - 링크
-
AWS KMS 간단 실습
: KMS 웹 생성 화면 보여주고 아래 진행# 키 생성(기본값) # aws kms create-key --description "my text encrypt descript demo" CREATE_KEY_JSON=$(aws kms create-key --description "my text encrypt descript demo") echo $CREATE_KEY_JSON | jq # 키 ID확인 KEY_ID=$(echo $CREATE_KEY_JSON | jq -r ".KeyMetadata.KeyId") echo $KEY_ID # 키 alias 생성 export ALIAS_SUFFIX=<각자 닉네임> export ALIAS_SUFFIX=gasida aws kms create-alias --alias-name alias/$ALIAS_SUFFIX --target-key-id $KEY_ID # 생성한 별칭 확인 aws kms list-aliases # CMK로 평문을 암호화해보기 echo "Hello 123123" > secrect.txt aws kms encrypt --key-id alias/$ALIAS_SUFFIX --cli-binary-format raw-in-base64-out --plaintext file://secrect.txt --output text --query CiphertextBlob | base64 --decode > secrect.txt.encrypted # 암호문 확인 cat secrect.txt.encrypted # 복호화해보기 aws kms decrypt --ciphertext-blob fileb://secrect.txt.encrypted --output text --query Plaintext | base64 --decode Hello 123123
- AWS CMK(KMS Key) 생성을 위해 Key policy 생성 : 현재 사용자가 CMK 권한을 가질 수 있게 코드 설정
- 현재 사용자 정보 확인
# aws cli 로 현재 사용자 정보 확인 aws sts get-caller-identity | jq { "UserId": "AIDA5ILF2FJIR2CFPVMG7", "Account": "911283464785", "Arn": "arn:aws:iam::911283464785:user/admin" }
# 테라폼에서 aws_caller_identity 데이터소스로 사용자 정보 확인 provider "aws" { region = "us-east-2" } # Look up the details of the current user data "aws_caller_identity" "self" {}
- 아래 처럼 현재 사용자에 KMS 권한 부여 정책 생성 할 수 있음
data "aws_iam_policy_document" "cmk_admin_policy" { statement { effect = "Allow" resources = ["*"] actions = ["kms:*"] principals { type = "AWS" identifiers = [data.aws_caller_identity.self.arn] } } }
- Next, you can create the CMK using the aws_kms_key resource:
resource "aws_kms_key" "cmk" { policy = data.aws_iam_policy_document.cmk_admin_policy.json }
- AWS KMS CMK ID는 긴 숫자로 이루어져 있어서, AWS KMS Alias 사용을 권고
resource "aws_kms_alias" "cmk" { name = "alias/kms-cmk-example" target_key_id = aws_kms_key.cmk.id }
- CMK 생성 후에는 CMK로 암호/복호화 할 수 있음.
-
먼저, DB계정 정보가 포함된 db-creds.yml 파일을 생성
실습
cat <<EOT > db-creds.yml username: admin password: password EOT
- AWS KMS CMK로 파일 암호화 하기 : 암호화파일 생성 후 db-creds.yml 삭제할 것
# CMK로 평문을 암호화해보기 # aws kms encrypt --key-id alias/$ALIAS_SUFFIX --cli-binary-format raw-in-base64-out --plaintext file://db-creds.yml --output text --query CiphertextBlob | base64 --decode | tee db-creds.yml.encrypted aws kms encrypt --key-id alias/$ALIAS_SUFFIX --cli-binary-format raw-in-base64-out --plaintext file://db-creds.yml --output text --query CiphertextBlob | tee db-creds.yml.encrypted2 # 암호문 확인 cat db-creds.yml.encrypted # 복호화해보기 aws kms decrypt --ciphertext-blob fileb://db-creds.yml.encrypted --output text --query Plaintext | base64 --decode
- 테라폼 코드에서 암호화(파일) 활용 할 수 있게 설정
- 먼저 aws_kms_secrets data source 설정
data "aws_kms_secrets" "creds" { secret { name = "db" payload = file("${path.module}/db-creds.yml.encrypted") } }
- 아래 처럼 복호화된 값을 로컬 변수로 지정
locals { db_creds = yamldecode(data.aws_kms_secrets.creds.plaintext["db"]) }
- DB 계정과 암호를 aws_kms_secrets 의 복호화된 로컬 변수를 전달
resource "aws_db_instance" "example" { identifier_prefix = "terraform-up-and-running" engine = "mysql" allocated_storage = 10 instance_class = "db.t2.micro" skip_final_snapshot = true db_name = var.db_name # Pass the secrets to the resource username = local.db_creds.username password = local.db_creds.password }
- 이제 안전하게 암호를 저장할 수 있고, 암호화된 파일은 버전 관리를 할 수 있습니다.
- 또한 테라폼 코드에서 암호를 다시 읽을 수 있습니다.
- rds.tf 코드 파일 생성
payload
- (Required) Base64 encoded payload, as returned from a KMS encrypt operation ⇒ payload 타입이 base64를 강요함! - 링크
cat <<EOT > rds.tf provider "aws" { region = "ap-northeast-2" } data "aws_kms_secrets" "creds" { secret { name = "db" payload = file("\${path.module}/**db-creds.yml.encrypted2**") } } locals { db_creds = yamldecode(data.aws_kms_secrets.creds.plaintext["db"]) } resource "aws_db_instance" "example" { identifier_prefix = "terraform-up-and-running" engine = "mysql" allocated_storage = 10 instance_class = "db.t2.micro" skip_final_snapshot = true db_name = var.db_name # Pass the secrets to the resource username = local.db_creds.username password = local.db_creds.password } EOT
-
variables.tf 코드 파일 생성
cat <<EOT > variables.tf variable "db_name" { description = "The name to use for the database" type = string default = "example" } EOT
-
outputs.tf 코드 파일 생성
cat <<EOT > outputs.tf output "address" { value = aws_db_instance.example.address description = "Connect to the database at this endpoint" } output "port" { value = aws_db_instance.example.port description = "The port the database is listening on" } EOT
-
init & plan & apply 후 삭제
실습
# terraform init & terraform plan & terraform apply -auto-approve # 리소스 생성 및 AWS RDS 정보 확인(username 등) aws rds describe-db-instances --query "*[].[DBInstanceIdentifier,MasterUsername,Endpoint.Address,Endpoint.Port]" --output text
- 실습 완료 후 삭제
terraform destroy -auto-approve
- 키 비활성화 및 삭제 예약
# 생성한 키 ID 변수 지정 KEY_ID=$(echo $CREATE_KEY_JSON | jq -r ".KeyMetadata.KeyId") # 키 비활성화 aws kms disable-key --key-id $KEY_ID # 키 삭제 예약 : 대기 기간(7일) aws kms schedule-key-deletion --key-id $KEY_ID --pending-window-in-days 7
-
위 작업은 aws kms 작업 단계가 필요함 → sops 편리한 오픈소스 활용 가능
- YAML, JSON, ENV, INI, BINARY 형식의 파일 암호화를 지원하고 AWS KMS, GCP KMS, Azure Key Vault, age, PGP 등을 이용하여 암호화하는 파일 편집기.
-
테라폼은 아직 sops 를 지원하지 않음 → carlpett/terraform-provider-sops 혹은 Terragrunt 는 내장 sops_decrypt_file function 사용 가능
https://github.com/carlpett/terraform-provider-sops
- 장점 : 암호를 평문 사용 절대 금지, 클라우드 KMS 활용
- Keep plain-text secrets out of your code and version control system.
- Your secrets are stored in an encrypted format in version control, so they are versioned, packaged, and tested with the rest of your code. This helps reduce configuration errors, such as adding a new secret in one environment (e.g., staging) but forgetting to add it in another environment (e.g., production).
- Retrieving secrets is easy, assuming the encryption format you’re using is natively supported by Terraform or a third-party plugin.
- It works with a variety of different encryption options: AWS KMS, GCP KMS, PGP, etc.
- Everything is defined in the code. There are no extra manual steps or wrapper scripts required (although sops integration does require a third-party plugin).
- 단점 : 도구 학습 필요, 자동화 환경에 적용 시 어려움, 여전히 암호 변경 어려움, 감사 로그 취약, 비밀 번호 관리 표준화 어려움
- Storing secrets is harder. You either have to run lots of commands (e.g., aws kms encrypt) or use an external tool such as sops. There’s a learning curve to using these tools correctly and securely.
- Integrating with automated tests is harder, as you will need to do extra work to make encryption keys and encrypted test data available for your test environments.
- The secrets are now encrypted, but as they are still stored in version control, rotating and revoking secrets is hard. If anyone ever compromises the encryption key, they can go back and decrypt all the secrets that were ever encrypted with it.
- The ability to audit who accessed secrets is minimal. If you’re using a cloud key management service (e.g., AWS KMS), it will likely maintain an audit log of who used an encryption key, but you won’t be able to tell what the key was actually used for (i.e., what secrets were accessed).
- Most managed key services cost a small amount of money. For example, each key you store in AWS KMS costs $1/month, plus $0.03 per 10,000 API calls, where each decryption and encryption operation requires one API call. A typical usage pattern, where you have a small number of keys in KMS and your apps use those keys to decrypt secrets during boot, usually costs $1–$10/month. For larger deployments, where you have dozens of apps and hundreds of secrets, the price is typically in the $10–$50/month range.
- Standardizing secret management practices is harder. Different developers or teams may use different ways to store encryption keys or manage encrypted files, and mistakes are relatively common, such as not using encryption correctly or accidentally checking in a plain-text file into version control.
- AWS KMS 소개
-
[Resources and data sources] Secret stores
실습
: 중앙 집중식 비밀 저장소 서비스 사용 - AWS Secrets Manager, Google Secret Manager 등AWS Secrets Manager에 데이터베이스 자격 증명을 저장 : 이름(db-creds)
-
테라폼 코드 예시
data "aws_secretsmanager_secret_version" "creds" { secret_id = "db-creds" } locals { db_creds = jsondecode( data.aws_secretsmanager_secret_version.creds.secret_string ) } resource "aws_db_instance" "example" { identifier_prefix = "terraform-up-and-running" engine = "mysql" allocated_storage = 10 instance_class = "db.t2.micro" skip_final_snapshot = true db_name = var.db_name # Pass the secrets to the resource username = local.db_creds.username password = local.db_creds.password } variable "db_name" { description = "The name to use for the database" type = string default = "example" } output "address" { value = aws_db_instance.example.address description = "Connect to the database at this endpoint" } output "port" { value = aws_db_instance.example.port description = "The port the database is listening on" }
- 장점 : 보통 웹 UI 제공, 암호 변경 용이(회수, 갱신 스케줄링), 감사 로그 지원, 비밀번호 표준화 용이
- Keep plain-text secrets out of your code and version control system.
- Everything is defined in the code itself. There are no extra manual steps or wrapper scripts required.
- Storing secrets is easy, as you typically can use a web UI.
- Secret stores typically support rotating and revoking secrets, which is useful in case a secret gets compromised. You can even enable rotation on a scheduled basis (e.g., every 30 days) as a preventative measure.
- Secret stores typically support detailed audit logs that show you exactly who accessed what data.
- Secret stores make it easier to standardize all your secret practices, as they enforce specific types of encryption, storage, access patterns, etc.
- 단점 : 서비스 비용 발생, 그 외에는 Encrypted files 과 동일한 단점 보유
- Since the secrets are not versioned, packaged, and tested with your code, configuration errors are more likely, such as adding a new secret in one environment (e.g., staging) but forgetting to add it in another environment (e.g., production).
- Most managed secret stores cost money. For example, AWS Secrets Manager charges $0.40 per month for each secret you store, plus $0.05 for every 10,000 API calls you make to store or retrieve data. A typical usage pattern, where you have several dozen secrets stored across several environments and a handful of apps that read those secrets during boot, usually costs around $10–$25/month. With larger deployments, where you have dozens of apps reading hundreds of secrets, the price can go up to hundreds of dollars per month.
- If you’re using a self-managed secret store such as HashiCorp Vault, then you’re both spending money to run the store (e.g., paying AWS for 3–5 EC2 Instances to run Vault in a highly available mode) and spending time and money to have your developers deploy, configure, manage, update, and monitor the store. Developer time is very expensive, so depending on how much time they have to spend on setting up and managing the secret store, this could cost you thousands of dollars per month.
- Retrieving secrets is harder, especially in automated environments (e.g., an app booting up and trying to read a database password), as you have to solve how to do secure authentication between multiple machines.
- Integrating with automated tests is harder, as much of the code you’re testing now depends on a running, external system that either needs to be mocked out or have test data stored in it.
-
-
[State files and plan files]
실습
: 상태 파일과 플랜 파일에 민감 정보 노출- State files : 테라폼 리소스와 데이터 소스에 전달되는 모든 민감정보는 테라폼 상태 파일에 평문으로 저장됨
- 백엔드 저장소에 저장 시 암호화 Store Terraform state in a backend that supports encryption
- 백엔드 액세스에 대한 접근 통제 Strictly control who can access your Terraform backend
-
plan files
실습
: 역시 암호화 및 파일 통제# plan 출력 내용을 파일로 저장 terraform plan -**out**=t101.plan #(참고) plan 출력 파일로 apply ## terraform apply t101.plan # plan 출력 내용을 파일 확인 cat t101.plan # 평문 terraform **show** t101.plan > t101.ansi less t101.ansi
- Encrypt your Terraform plan files
- Strictly control who can access your plan files
- State files : 테라폼 리소스와 데이터 소스에 전달되는 모든 민감정보는 테라폼 상태 파일에 평문으로 저장됨
A comparison of methods for machine users (e.g., a CI server) to pass secrets to Terraform providers
Stored credentials | IAM roles | OIDC | |
---|---|---|---|
Example | CircleCI | Jenkins on an EC2 Instance | GitHub Actions |
Avoid manually managing credentials | X | O | O |
Avoid using permanent credentials | X | O | O |
Works inside of cloud provider | X | O | X |
Works outside of cloud provider | O | X | O |
Widely supported as of 2022 | O | O | X |
A comparison of methods for passing secrets to Terraform resources and data sources
Environment variables | Encrypted files | Centralized secret stores | |
---|---|---|---|
Keeps plain-text secrets out of code | O | O | O |
All secrets management defined as code | X | O | O |
Audit log for access to encryption keys | X | O | O |
Audit log for access to individual secrets | X | X | O |
Rotating or revoking secrets is easy | X | X | O |
Standardizing secrets management is easy | X | X | O |
Secrets are versioned with the code | X | O | X |
Storing secrets is easy | O | X | O |
Retrieving secrets is easy | O | O | X |
Integrating with automated testing is easy | O | X | X |
Cost | 0 | $ | $$$ |