AWS Secrets Manager with Spring Boot

5 분 소요


1. AWS Secrets Manager

데이터베이스 인증서, API 키, 토큰 등 IT 리소스(resource) 접근에 필요한 보안 암호를 보호, 관리하는 AWS 컴포넌트입니다.

  • 보안 암호를 대규모로 관리, 교체, 검색할 수 있다.
  • 보안 암호의 수명 주기를 관리한다.
  • 다른 AWS 서비스와 통합하여 모니터링을 자동화한다.
  • 내부 보안 또는 규정 요건을 충족하지 않는 암호를 빠르게 찾아낼 수 있다.
  • 보안 암호 복제 기능으로 여러 지역의 어플리케이션과 재해 복구 상황을 지원한다.
  • 어플리케이션에서 필요한 설정 값들을 프로파일(profile) 단위로 관리할 수 있습니다.
    • 서비스가 배포되는 환경 별로 다른 설정 값을 사용할 수 있습니다.

2. AWS Secrets Manager Config in Spring Cloud

스프링 클라우드(spring cloud)는 AWS Scretes Manager을 쉽게 사용할 수 있도록 의존성(dependency)를 제공합니다.

  • 아래 의존성들을 추가합니다.
    • spring-cloud-starter-bootstrap
    • spring-cloud-starter-aws-secrets-manager-config
  • 해당 의존성들이 추가되면 어플리케이션이 실행되는 시점에 Secrets Manager에서 필요한 설정들을 읽습니다.
    • bootstrap.yml 파일에 별도 설정이 추가되어야 합니다.
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
        <version>4.0.3</version>
    </dependency>
    <dependency>
        <groupId>io.awspring.cloud</groupId>
        <artifactId>spring-cloud-starter-aws-secrets-manager-config</artifactId>
        <version>2.4.4</version>
    </dependency>

3. Practice

Secrets Manager에 간단한 프로퍼티를 설정하고 어플리케이션에서 주입 받는 예제를 살펴보겠습니다. 먼저 AWS 콘솔의 Secrets Manager에서 다음과 같은 과정을 통해 설정들을 추가합니다.

3.1. Setup Secrets Manager

  • Store a new secret 버튼을 클릭합니다.

  • Other type of secret을 선택 후 필요한 설정을 키/값 형태로 입력합니다.

  • 설정 이름을 작성합니다.
    • bootstrap.yml 파일에서 별도로 접두어(prefix)를 설정하지 않으면 /secret/가 자동으로 추가됩니다.
    • 이름을 /secret/action-in-blog으로 지정하였습니다.

  • 해당 화면에서 추가 설정 없이 다음(next) 버튼을 선택합니다.

  • 해당 화면에서 추가 설정 없이 저장(store) 버튼을 선택합니다.

3.2. Configuration in Spring Boot

3.2.1. pom.xml

pom.xml 파일에 다음과 같이 의존성들을 추가합니다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>action.in.blog</groupId>
    <artifactId>action-in-blog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>action-in-blog</name>
    <description>action-in-blog</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>io.awspring.cloud</groupId>
            <artifactId>spring-cloud-starter-aws-secrets-manager-config</artifactId>
            <version>2.4.4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2.2. bootstrap.yml

  • Secrets Manager를 통해 필요한 설정을 주입 받기 위해 다음과 같은 설정을 추가합니다.
    • Secrets 이름
      • 설정을 읽을 때 /secret/ 접두어가 자동으로 추가됩니다.
    • Secrets 적용 지역(region)
aws:
  secretsmanager:
    name: action-in-blog
cloud:
  aws:
    region:
      static: us-east-1

3.2.3. application.yml

  • Secrets Manager에 설정한 값들을 주입받습니다.
    • DB_USER - 데이터베이스 사용자
    • DB_PASSWORD - 데이터베이스 비밀번호
  • Secrets Manager는 프로파일 기능을 지원하며 이를 테스트하기 위해 프로파일을 dev로 활성화시킵니다.
spring:
  profiles:
    active: dev
database:
  user: ${DB_USER}
  password: ${DB_PASSWORD}

3.2.3. ActionInBlogApplication Class

어플리케이션이 실행 후 출력되는 로그를 통해 주입된 값들을 확인합니다.

package action.in.blog;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Map;

@Slf4j
@SpringBootApplication
public class ActionInBlogApplication {

    @Value("${database.user}")
    private String databaseUser;

    @Value("${database.password}")
    private String databasePassword;

    public static void main(String[] args) {
        SpringApplication.run(ActionInBlogApplication.class, args);
    }

    @PostConstruct
    private void postConstructor() {
        log.info("{}", Map.of(
                        "databaseUser", databaseUser,
                        "databasePassword", databasePassword
                )
        );
    }
}

3.3. Run Application

  • 프로파일이 적용된 스프링 어플리케이션을 실행할 때 다음과 같은 설정들이 모두 필요합니다.
    • /secret/{name}_{profile}
    • /secret/{name}
    • /secret/application_{profile}
    • /secret/application
  • 프로파일 설정은 시크릿 뒤에 _ 구분자로 추가됩니다.
    • 구분자는 bootstrap.yml 설정을 통해 변경할 수 있습니다.
  • 각 설정 별로 다음과 같은 값들이 설정되어 있습니다.
    • /secret/action-in-blog_dev
      • DB_USER - junhyunny-dev
      • DB_PASSWORD - password-dev
    • /secret/action-in-blog
      • DB_USER - junhyunny
      • DB_PASSWORD - password
    • /secret/application_dev
      • DB_USER - root-dev
      • DB_PASSWORD - mysql123-dev
    • /secret/application
      • DB_USER - root
      • DB_PASSWORD - mysql123

3.3.1. Setup AWS configure and token

로컬 컴퓨터에서 어플리케이션을 실행하면 다음과 같은 에러 로그를 만날 수 있습니다. 해당 에러는 로컬 컴퓨터가 Secrets Manager에 접근할 수 있는 권한이 없기 때문에 발생합니다.

2023-07-09T23:32:41.702+09:00  INFO 20762 --- [           main] i.a.c.s.AwsSecretsManagerPropertySources : Loading secrets from AWS Secret Manager secret with name: /secret/action-in-blog, optional: false
2023-07-09T23:32:41.716+09:00 ERROR 20762 --- [           main] o.s.boot.SpringApplication               : Application run failed

io.awspring.cloud.secretsmanager.AwsSecretsManagerPropertySources$AwsSecretsManagerPropertySourceNotFoundException: com.amazonaws.services.secretsmanager.model.AWSSecretsManagerException: The security token included in the request is expired (Service: AWSSecretsManager; Status Code: 400; Error Code: ExpiredTokenException; Request ID: fc9389d1-fc38-45bf-97e0-e368ba679a31; Proxy: null)
    at io.awspring.cloud.secretsmanager.AwsSecretsManagerPropertySources.createPropertySource(AwsSecretsManagerPropertySources.java:93) ~[spring-cloud-aws-secrets-manager-config-2.4.4.jar:2.4.4]
    at io.awspring.cloud.secretsmanager.AwsSecretsManagerPropertySourceLocator.locate(AwsSecretsManagerPropertySourceLocator.java:94) ~[spring-cloud-aws-secrets-manager-config-2.4.4.jar:2.4.4]
    at org.springframework.cloud.bootstrap.config.PropertySourceLocator.locateCollection(PropertySourceLocator.java:50) ~[spring-cloud-context-4.0.3.jar:4.0.3]
    at org.springframework.cloud.bootstrap.config.PropertySourceLocator.locateCollection(PropertySourceLocator.java:46) ~[spring-cloud-context-4.0.3.jar:4.0.3]

AWS 콘솔에서 IAM(Identity and Access Management)를 통해 발급받은 ID, Secret을 로컬 컴퓨터에 설정합니다. 해당 작업은 AWS CLI(command line interface) 설치가 필요합니다.

$ aws configure
AWS Access Key ID [****************S5FG]: ${YOUR_AWS_CLIENT_ID}
AWS Secret Access Key [****************KtFb]: {YOUR_AWS_CLIENT_SECRET}
Default region name [us-east-1]: 
Default output format [None]: 

$ aws configure set aws_session_token ${YOUR_AWS_SESSION_TOKEN}

3.3.2. Application Logs

어플리케이션을 실행합니다. 주요하게 살펴볼 로그들은 다음과 같습니다.

  • Located property source: [BootstrapPropertySource {name=’bootstrapProperties-/secret/action-in-blog_dev’}, BootstrapPropertySource {name=’bootstrapProperties-/secret/action-in-blog’}, BootstrapPropertySource {name=’bootstrapProperties-/secret/application_dev’}, BootstrapPropertySource {name=’bootstrapProperties-/secret/application’}]
    • 4개의 설정을 Secrets Manager로부터 주입받습니다.
  • The following 1 profile is active: “dev”
    • 활성화 된 프로파일은 dev입니다.
  • {databasePassword=password-dev, databaseUser=junhyunny-dev}
    • /secret/{name}_{profile}에 추가된 설정이 적용되었습니다.
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)

2023-07-09T23:46:59.001+09:00  INFO 24765 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/secret/action-in-blog_dev'}, BootstrapPropertySource {name='bootstrapProperties-/secret/action-in-blog'}, BootstrapPropertySource {name='bootstrapProperties-/secret/application_dev'}, BootstrapPropertySource {name='bootstrapProperties-/secret/application'}]
2023-07-09T23:46:59.012+09:00  INFO 24765 --- [           main] action.in.blog.ActionInBlogApplication   : The following 1 profile is active: "dev"
2023-07-09T23:46:59.025+09:00  INFO 24765 --- [           main] i.a.c.s.AwsSecretsManagerPropertySources : Loading secrets from AWS Secret Manager secret with name: /secret/action-in-blog_dev, optional: false
2023-07-09T23:46:59.025+09:00  INFO 24765 --- [           main] i.a.c.s.AwsSecretsManagerPropertySources : Loading secrets from AWS Secret Manager secret with name: /secret/action-in-blog, optional: false
2023-07-09T23:46:59.025+09:00  INFO 24765 --- [           main] i.a.c.s.AwsSecretsManagerPropertySources : Loading secrets from AWS Secret Manager secret with name: /secret/application_dev, optional: false
2023-07-09T23:46:59.025+09:00  INFO 24765 --- [           main] i.a.c.s.AwsSecretsManagerPropertySources : Loading secrets from AWS Secret Manager secret with name: /secret/application, optional: false
2023-07-09T23:46:59.351+09:00  INFO 24765 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=04d99736-6843-3b30-ab44-091dbb357372
2023-07-09T23:46:59.549+09:00  INFO 24765 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-07-09T23:46:59.557+09:00  INFO 24765 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-07-09T23:46:59.558+09:00  INFO 24765 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.10]
2023-07-09T23:46:59.612+09:00  INFO 24765 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-07-09T23:46:59.613+09:00  INFO 24765 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 588 ms
2023-07-09T23:46:59.650+09:00  INFO 24765 --- [           main] action.in.blog.ActionInBlogApplication   : {databasePassword=password-dev, databaseUser=junhyunny-dev}
2023-07-09T23:46:59.886+09:00  INFO 24765 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-07-09T23:46:59.897+09:00  INFO 24765 --- [           main] action.in.blog.ActionInBlogApplication   : Started ActionInBlogApplication in 3.844 seconds (process running for 4.159)

TEST CODE REPOSITORY

REFERENCE

댓글남기기