[Infra] Springboot에 AWS Secrets Manager 적용해보기(2)
안녕하세요! zerone-code입니다.
지난 글에서 AWS의 Secrets Manager를 이용해서 Application의 보안 측면을 높이는 방법을 소개했습니다.
AWS Secrets Manager가 무엇이고, 왜 적용해야 하며, 어떻게 적용되는지에 대해 알아봤었습니다.
2022.09.18 - [Dev&Ops/DevOps] - [Infra] Springboot에 AWS Secrets Manager 적용해보기(1)
1부에 이어서 2부에서는 코드 수준에서 SpringBoot에 적용해야하는 부분과 인프라 수준에서 AWS에서 적용해야할 부분에 대해 알아보도록 하겠습니다.
궁금한 부분은 댓글로 남겨주시면 아는 만큼 성심성의껏 답변해드리도록 하겠습니다.
잘 읽으셨다면 많은 공감과 구독 부탁드립니다!
1. 코드 수준에서 적용해야 할 부분
요즘에는 다양한 서비스들이 쫓아가기 힘들 정도로 많은 언어들로 개발되고 있습니다.
많은 언어에서 사용되는 설정을 다 설명드릴 수는 없고, 대표적으로 사용되는 SpringBoot에서 AWS Secrets Manager를 적용할 때 추가되야할 부분에 대해 알아보겠습니다.
크게 두 부분으로 나눠서 볼 수 있습니다.
빌드 도구에서 적용해야 할 부분과 Application 설정에서 적용해야 할 부분으로 나눠서 볼 수 있습니다.
먼저 빌드 도구에서 적용해야 할 부분에 대해 알아보겠습니다.
빌드 도구
SpringBoot는 Gradle이나 Maven을 이용해서 소스 코드의 디펜던시를 관리하거나 실행 가능한 형태로 컴파일링, 링킹, 패키징을 합니다.
Application이 시작될 때 필요한 클래스를 Loading 하고 Application 설정에 설정된 Key값을 이용해서 Secrets Manager에서 가져와서 실제로 사용되는 값을 주입해줘야 합니다.
따라서 SpringBoot에서 Secrets manager를 이용하기 위해서는 아래와 같이 디펜던시를 추가해줘야합니다.
Maven
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-aws-secrets-manager-config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-aws-secrets-manager-config</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
Gradle
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bootstrap
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap:3.1.4'
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-aws-secrets-manager-config
implementation 'org.springframework.cloud:spring-cloud-aws-secrets-manager-config:2.2.6.RELEASE'
Application 설정
디펜던시 설정을 다해줬다면 Appilcation에 설정을 해보도록 하겠습니다.
Application의 설정을 setDefaultProperties 메소드나 @PropertySource어노테이션을 통해 설정해 줄 수도 있지만,
대부분의 환경에서는 appilcation.yml의 형태나 application.properties의 형태로 사용하고 있습니다.
따라서 application.yml을 기준으로 알아보겠습니다. application.properties에서도 설정 key에 해당하는 값은 같으므로 쉽게 적용하실 수 있을거라 생각됩니다.
application.yml에 작성에 앞서 구독자 분들께서 application.yml의 적용 순서를 기억하시고 가시면 좋을 것 같습니다.
- jar 패키지 안의 application.yml / properties
- jar 패키지 안의 application-{env}.yml / properties
- jar 패키지 밖의 application.yml / properties
- jar 패키지 밖의 application-{env}.yml / properties
이 부분을 설명드리는 이유는 springboot 2.4 이전에는 bootstrap.yml을 이용해서 application.yml이 호출되기 이전에 공통적인 부분에 해당하는 context를 초기화했었지만, 그 이후에는 bootstrap.yml을 적용하기 위해선 별도의 설정을 해줘야 했습니다.
현재 springboot 2.7.4 버전에서도 bootstrap.yml을 사용할 수는 있지만 application.yml 우선순위를 적용해서 설명드리고자 합니다.
aws:
secretsmanager:
name: example #AWS Secrets Manager 이름
cloud:
aws:
region:
static: ap-northeast-2 #region 이름
위와 같이 application.yml을 구성하게 되면 위 설정은 yml 중 우선순위가 가장 위이기 때문에 공통적으로 적용될 수 있습니다.
그리고 application-dev,application-stg,application-prod와 같은 환경으로 application설정을 나누게 된다면, JVM option에서 spring.profiles.active를 적용해서 기동하실 거라 생각됩니다.
만약 dev를 예로 들면 application-dev.yml을 부르기 위해서는 -Dspring.profiles.active=dev를 추가해서 application을 실행하게 될 것이고, 그럼 spring-cloud-aws-secrets-manager-config에서는 '/secret/example_dev'라는 secrets manager resource를 찾게 됩니다.
이 부분을 아래 2. 인프라 수준에서 적용해야 할 부분에서 생성을 해줘야만 application에서 secrets manager의 값을 사용할 수 있게 됩니다.
2. 인프라 수준에서 적용해야 할 부분
코드 수준에서 적용해야 할 부분이 적용이 다 된다면 Application에서 AWS Secrets Manager를 사용할 준비는 다 됐습니다.
이번에는 인프라 수준에서 적용해야 할 부분에 대해 알아보도록 하겠습니다.
이때 사용되는 AWS 서비스는 IAM(Identity and Access Management), KMS(Key Management Service), Secrets Manager입니다.
2-1. KMS
먼저 KMS(Key Management Service)를 알아보겠습니다.
KMS는 크게 KMS key와 key의 alias 리소스로 구성할 수 있습니다.
KMS Key는 사용할 수 있는 account의 정보와 사용할 수 있는 application의 role을 허용해줘야 합니다.
resource "aws_kms_key" "example" {
description = "KMS key for common secrets"
enable_key_rotation = true
policy = <<EOF
{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "Enable IAM Account Permissions",
"Effect": "Allow",
"Principal": {
"AWS": <사용하려는 account의 user>
},
"Action": "kms:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": <사용하려는 application의 role arn>
},
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt",
"kms:GetParametersForImport",
"kms:GetPublicKey",
"kms:GetKeyRotationStatus",
"kms:GetKeyPolicy",
"kms:DescribeKey",
"kms:ListResourceTags"
],
"Resource": "*"
}
]
}
EOF
}
resource "aws_kms_alias" "example_kms_alias" {
name = "alias/example"
target_key_id = aws_kms_key.example.key_id
}
위와 같이 구성을 하면 특정 account에서 뜨는 application에서 'example'이라는 key에 접근을 할 수 있게 됩니다.
그 다음은 이 키를 사용해서 encrypt/decrypt가 되는 SecretsManager 리소스를 보도록 하겠습니다.
2-2. Secrets Manager
먼저 secrets Manager를 모듈화 시켜서 아래의 형태처럼 만들 수 있습니다.
SpringBoot에서는 spring-cloud-aws-secrets-manager-config를 사용해서 Secrets Manager의 값을 불러오면 기본적으로 '/secret/{name}'의 형태로 읽어오게 되있습니다.
따라서 '/secret/{name}'의 형태로 만들어주고, 실제 모듈을 사용하는 곳에서 {name}부분을 지정할 수 있도록 모듈을 구성할 수 있습니다.
그리고 kms의 arn과 tag값도 변수화시켜서 각 secrets manager 리소스마다 다르게 설정 할 수 있도록 구성합니다.
resource "aws_secretsmanager_secret" "secrets_resource" {
name = "/secret/${var.key_name}"
kms_key_id = var.kms_arn
tags = var.tags
recovery_window_in_days = 30
}
그 다음은 위의 모듈을 사용하여 리소스를 생성하는 부분입니다.
여기에서는 example이라는 key를 이용해서 '/secret/example_dev'이라는 key name을 가지는 secrets manager 리소스를 생성하고자 합니다.
아래와 같이 앞서 만들어준 kms의 arn도 variable로 관리하고, 그아래의 'aws_secretsmanager_secret_version'에서는 생성하려는 secrets manager 리소스에 저장될 값을 json의 형태로 넣어줄 수 있습니다. 이 또한 하나의 variable 파일로 관리하고, 이 variable 파일은 git에서 ansible-vault를 이용해 암호화 처리를 해서 관리하게 됩니다.
module "example_dev" {
source = "../_module/secretsmanager"
key_name = "example_dev"
kms_arn = var.kms.example
tags = {
Name = "example-dev-secretsmanager"
}
}
resource "aws_secretsmanager_secret_version" "example_dev" {
secret_id = module.example.id
secret_string = jsonencode(var.example_dev_values)
}
아래처럼 key-value의 조합으로 map형태로 만들어서 위의 secret_string에 암호화 되어 저장되게 됩니다.
variable "example_dev_values" {
default = {
username = "example"
token = "deeThuuhiepah2ceequohrai5oovai"
}
type = map(string)
}
위의 저장될 변수를 git에서 관리하기 위해 ansible-vault로 암호화를 하게 되면 아래와 같이 난수들로 구성된 파일이 됩니다.
만약 수정이 필요하면 decrypt를 하여 수정을하고 git에 올릴 때는 다시 encrypt하여 올리면 보다 보안 측면에서 좀 더 좋게 관리 될 수 있습니다.
$ANSIBLE_VAULT;1.1;AES256
34663266353037343538326438633565323230353333343936613962353564626531646339653138
3431356136396630633530623036613334373530613861650a336435303465306564303932313163
61323431306136633038653036373533613633386232666365323861373136613134373436396432
3036306237323730610a366466353735353630626431613261653465336238383530353834376632
35363439396537373066373366336361366539643737643561666537376537616139323434343238
63326366336261633432666263346531656135393262663638666633323836613866646139613134
63333538333563383530636534336138373733373735646666613535663935623635663434356634
63346636663533623164306634663666343736346539626639636661306262383965363731303139
34346662373737396461346365663538333031376630623931396432373432363236303731653337
35363266333535396264323532346464646466326230383032346632346465663139663366623836
36613837333565353965626233303238366135353631613531643835636137633965383761363739
33383462316233343533
이제 마지막으로 Appliction에 할당된 iam role을 살펴보도록 하겠습니다.
2-3. IAM Role
Appliction에서 사용할 iam role은 아래와 같이 구성할 수 있습니다.
EC2를 기준으로 IAM을 사용한다고 하면 Role에는 먼저 service에서 Role을 사용할 수 있도록 'sts:AssumeRole' 을 부여하고, 이전에 만들 example의 KMS를 사용하여 decrypt를 할 수 있는 권한을 부여합니다.
그리고 EC2에서는 Role의 Profile을 이용하여 동작하기 때문에 하단의 instance profile을 생성해줍니다.
resource "aws_iam_role" "app_example" {
name = "app-example"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy" "app_example_kms" {
name = "app-example-kms-decryption"
role = aws_iam_role.app_example.id
policy = <<EOF
{
"Statement": [
{
"Sid": "AllowToDecryptKMSKey",
"Action": [
"kms:Decrypt"
],
"Resource": [
"${var.example_kms_arn}"
],
"Effect": "Allow"
},
]
}
EOF
}
resource "aws_iam_instance_profile" "app_example" {
name = "app-example-profile"
role = aws_iam_role.app_example.name
}
정리를 해보자면
- EC2에 attach하여 사용할 IAM instance profile
- Encrypt/Decrypt에 사용될 KMS
- Encrypt된 정보를 저장하는 Secrets Manager
세 가지 AWS 리소스를 구축해 EC2를 기준으로 동작하는 Application 혹은 Service에서 민감한 DB 관련 정보, Token 값과 같은 걸 보다 안전하게 관리 할 수 있도록 만들 수 있습니다.
지금까지 Springboot와 EC2를 기준으로 동작하는 application에서 민감한 설정 정보들을 보다 안전하게 관리하기 위해 AWS 서비스를 이용해 구축해봤습니다.
글을 읽어 보시는 것 뿐만 아니라 직접 해보시면 더 빠른 이해가 되실 것이라고 생각됩니다.
어플리케이션 뿐만 아니라 인프라를 구축할 때도 API Key와 같은 민감한 정보를 사용하는 경우가 있습니다.
이 부분을 다음 글에서 한번 다뤄보도록 하겠습니다.
궁금한 부분은 댓글로 남겨주시면 아는 만큼 성심성의껏 답변해드리도록 하겠습니다.
잘 읽으셨다면 많은 공감과 구독 부탁드립니다!
Reference
SpringBoot Dependency
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bootstrap/3.1.4
Spring Application yml priority
KMS terraform
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key
Secrets Manager Rotation
https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-reference.html