SpringBoot 2.x 프레임워크가 적용된 애플리케이션에 적용하는 가이드
1. AWS Secrets Manager 개요
AWS Secrets Manager를 사용하면 수명 주기 동안 데이터베이스 보안 인증, 애플리케이션 보안 인증, OAuth 토큰, API 키 및 기타 암호를 관리, 검색, 교체할 수 있다.
- 소스 코드에 하드코딩된 보안 인증 정보가 불필요해져 보안 태세를 개선할 수 있음
- 런타임 호출로 대체하여 필요할 때 동적으로 보안 인증 정보를 검색
2. 필요한 IAM 권한
참고: https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/retrieving-secrets_cache-java.html
secretsmanager:DescribeSecretsecretsmanager:GetSecretValue
3. AWS Secrets Manager JDBC를 사용하는 방법 (Reference)
build.gradle에 aws-secretsmanager-jdbc 라이브러리 의존성을 추가한다.
// build.gradle
dependencies {
// ...
implementation 'com.amazonaws.secretsmanager:aws-secretsmanager-jdbc:1.0.12'
// ...
}
application.yml의 DataSource 설정을 아래와 같이 변경한다. (AS-IS → TO-BE)
# AS-IS
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:postgresql://db-url.ap-northeast-2.rds.amazonaws.com:5432
driver-class-name: org.postgresql.Driver
username: # DB 접속 계정 ID
password: # DB 접속 계정 비밀번호
# TO-BE
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc-secretsmanager:postgresql://db-url.ap-northeast-2.rds.amazonaws.com:5432
driver-class-name: com.amazonaws.secretsmanager.sql.AWSSecretsManagerPostgreSQLDriver
username: appUser # AWS SecretManager에 설정된 Secret Name
# password 항목 제거
| 항목 | AS-IS | TO-BE |
|---|---|---|
| DB URL prefix | jdbc:postgresql:// |
jdbc-secretsmanager:postgresql:// |
| Driver Class Name | org.postgresql.Driver |
com.amazonaws.secretsmanager.sql.AWSSecretsManagerPostgreSQLDriver |
| DB Account | DB 접속 ID와 PW 기입 | username에 AWS Secret Name 기입, password 항목 제거 |
4. Reference 가이드 적용 시 문제점
- Driver Class가 AWS SDK에서 제공하는 Secrets Manager DB Driver로 고정되기 때문에, 로컬 개발 환경에서 디버깅 용으로 흔히 쓰는 DriverSpy 등의 다른 DB 드라이버 사용이 불가함.
- Auto Configure로 AWS Credential 설정이 Default만 적용되어 AWS SDK의 Credentials Provider Chain이 적용됨.
- 애플리케이션 성능 저하
- 구동 머신에 AWS CLI 등 AWS Access Role이 여러 개 설정되어 있으면 의도하지 않은 Credential 정보로 인증이 시도되어 오동작할 우려가 있음
5. Spring Cloud AWS를 사용하여 DataSource를 Java Bean으로 설정하는 방법
build.gradle에 Spring Cloud AWS 라이브러리 의존성을 추가한다.
SpringBoot, Spring Cloud와의 버전 호환성 참고: https://github.com/awspring/spring-cloud-aws
// build.gradle
ext {
// ...
set('springCloudAwsVersion', "2.4.4")
}
dependencies {
// ...
implementation "io.awspring.cloud:spring-cloud-starter-aws-secrets-manager-config:${springCloudAwsVersion}"
// ...
}
application.yml에서 password 항목을 제거하고, username에 Secret Name을 기입한다.
# application.yml
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:postgresql://db-url.ap-northeast-2.rds.amazonaws.com:5432
driver-class-name: org.postgresql.Driver
username: appUser # AWS SecretManager에 설정된 Secret Name
#password: # 제거
DataSource를 Java Bean으로 설정한다.
AWS Credential Provider는 서버 환경(dev, stag, prod)은 EC2 IAM Role을, 로컬 개발환경(local, h2)은 JVM 옵션의 프로퍼티로 인증하도록 분리 설정한다.
// DataSourceConfigure.java
@Configuration
@Import(DataSourcePoolMetadataProvidersConfiguration.class)
public class DataSourceConfigure {
@Value("${spring.datasource.url}")
private String dataSourceUrl;
@Value("${spring.datasource.username}")
private String secretId;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Bean
@Profile({"local", "h2"})
public AWSCredentialsProvider awsCredentialsProviderLocal() {
return new SystemPropertiesCredentialsProvider();
}
@Bean
@Profile({"dev", "stag", "prod"})
public AWSCredentialsProvider awsCredentialsProvider() {
return InstanceProfileCredentialsProvider.getInstance();
}
@Bean
public AWSSecretsManager awsSecretsManager(AWSCredentialsProvider awsCredentialsProvider) {
return AWSSecretsManagerClient.builder()
.withCredentials(awsCredentialsProvider)
.withRegion(AP_NORTHEAST_2)
.build();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.hikari")
public DataSource dataSource(AWSSecretsManager awsSecretsManager) throws JsonProcessingException {
final GetSecretValueRequest secretValueRequest = new GetSecretValueRequest().withSecretId(secretId);
final GetSecretValueResult result = awsSecretsManager.getSecretValue(secretValueRequest);
if (result == null) {
throw new RuntimeException("Failed to get amazon secret value");
}
final String secretStr = result.getSecretString();
final DataSourceSecrets secrets = new ObjectMapper().readValue(secretStr, DataSourceSecrets.class);
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.driverClassName(driverClassName)
.url(dataSourceUrl)
.password(secrets.getPassword())
.username(secrets.getUsername())
.build();
}
}
getSecretString() 결과값을 매핑할 Model 클래스를 생성한다.
// DataSourceSecrets.java
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DataSourceSecrets {
private String username;
private String password;
private String engine;
private String host;
private String port;
private String dbInstanceIdentifier;
}
로컬 개발 환경에서는 JVM 옵션으로 AWS Access Key ID와 Secret Key를 설정한다. (EC2에서는 불필요)
-Daws.accessKeyId={AWS Access Key ID} -Daws.secretKey={AWS Secret Key}
6. 추가 설정
DB Connection URL을 proxy가 적용된 버전으로 변경한다.