ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Infra] Springboot에 AWS Secrets Manager 적용해보기(2)
    Dev&Ops/DevOps 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

Designed by Tistory.