Dev&Ops/DevOps

[Infra] Springboot에 AWS Secrets Manager 적용해보기(2)

zeroneCoder 2022. 9. 30. 18:05

 

안녕하세요! zerone-code입니다.

 

지난 글에서 AWS의 Secrets Manager를 이용해서 Application의 보안 측면을 높이는 방법을 소개했습니다.

AWS Secrets Manager가 무엇이고, 왜 적용해야 하며, 어떻게 적용되는지에 대해 알아봤었습니다.

2022.09.18 - [Dev&Ops/DevOps] - [Infra] Springboot에 AWS Secrets Manager 적용해보기(1)

 

[Infra] Springboot에 AWS Secrets Manager 적용해보기(1)

안녕하세요! zerone-code입니다. 이번 글에서는 AWS의 Secrets Manager를 이용해서 Springboot의 db와 관련된 값이나 토큰 값과 같은 노출되서는 안될 값들에 적용해 어플리케이션의 보안 측면을 조금 더 높

zerone-code.tistory.com

 

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의 적용 순서를 기억하시고 가시면 좋을 것 같습니다.

  1. jar 패키지 안의 application.yml / properties
  2. jar 패키지 안의 application-{env}.yml / properties
  3. jar 패키지 밖의 application.yml / properties
  4. 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
}

 

정리를 해보자면

  1. EC2에 attach하여 사용할 IAM instance profile
  2. Encrypt/Decrypt에 사용될 KMS
  3. 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

https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-aws-secrets-manager-config/2.2.6.RELEASE

 

Spring Application yml priority

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

KMS terraform

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key

 

Terraform Registry

 

registry.terraform.io

Secrets Manager Rotation

https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-reference.html