Spring Boot 3rd-party (Sentry + DataDog + Vault) integration 3. Vault 연동

예제 코드는 Github repository에서 확인 가능합니다.


앞서 Sentry, DataDog을 연동할 때 secret(DSN, API key, Application Key)을 그대로 application.yml에 넣어서 사용하였다.
하지만 secret이 VCS에 포함될 경우 보안 측면에서 bad practice이며 외부 유출 가능성이 있다.

그렇기 때문에 이러한 평문으로 된 secret 값을 안전하게 숨겨주는 tool이 여러가지 있는데,
이 예제에서는 Vault를 사용하여 secret 값을 저장하고 꺼내쓰는 방식에 대해 알아보도록 하겠다.

Vault 설치

먼저 로컬에 Vault를 설치하여 세팅을 해보자.
mac에서는 homebrew를 사용하여 설치하면된다.

1
$ brew install vault

설치가 되었으면 Vault 서버를 dev로 실행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ vault server --dev --dev-root-token-id="00000000-0000-0000-0000-000000000000"

==> Vault server configuration:

Api Address: http://127.0.0.1:8200
Cgo: disabled
Cluster Address: https://127.0.0.1:8201
Go Version: go1.15.2
Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
Log Level: info
Mlock: supported: false, enabled: false
Recovery Mode: false
Storage: inmem
Version: Vault v1.5.4
Version Sha: 1a730771ec70149293efe91e1d283b10d255c6d1+CHANGES

http://localhost:8200 로 접속하면 방금 실행한 Vault 서버로 접속할 수 있다.

앞서 Vault 서버를 실행할 때 지정했던 개발용 Token으로 로그인한다.

로그인 뒤에 secret에 들어가서 새로운 secret을 만들기 위해 Create secrete을 선택한다.

새로운 secret에 Vault 연동 테스트 용으로 다음과 같이 JSON 형태의 값을 추가하자.

1
2
3
{
"message": "Hello World"
}

그리고 Save를 눌러 secret값을 저장하도록 한다.

일단 연동이 잘되는지 테스트를 하기 위한 Vault 세팅을 하였고 이제 Spring Boot Application에서 Vault에 연동하기 위한 설정을 하도록 하자.

Application 설정

초반 프로젝트 생성을 했을 때 build.gradle에 주석 처리했던 Vault config dependency를 풀도록한다.

1
implementation 'org.springframework.cloud:spring-cloud-starter-vault-config'

그리고 Application을 실행하면 다음과 같이 오류가 난다.

1
2
3
4
5
6
7
8
9
10
Caused by: java.lang.IllegalArgumentException: Token (spring.cloud.vault.token) must not be empty
at org.springframework.util.Assert.hasText(Assert.java:287)
at org.springframework.cloud.vault.config.ClientAuthenticationFactory.createClientAuthentication(ClientAuthenticationFactory.java:143)
at org.springframework.cloud.vault.config.VaultBootstrapConfiguration.clientAuthentication(VaultBootstrapConfiguration.java:248)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 81 common frames omitted

Vault 접속 설정이 안되있기 때문에 위 에러 로그는 Vault Token을 설정하라는 메세지이다.

Spring Cloud Vault 설정은 secret을 위한 설정값이기 때문에 properties 보다 일찍 load 될 필요가 있다.
그렇기 때문에 application.yml이 아닌 bootstrap.yml에 설정해야한다.

bootstrap-local.yml에 아래와 Spring Cloud Vault 설정을 같이 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
vault:
enabled: true
token: 00000000-0000-0000-0000-000000000000
scheme: http
host: localhost
port: 8200
kv:
enabled: true
backend: secret
default-context: local

그리고 Vault에 테스트용으로 추가했던 message secret 값을 잘 가져오는지 확인하기 위해서
아래와 같이 application main class에 ApplicationRunner를 상속받아 message값을 출력해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootApplication
public class ThirdpartyApplication implements ApplicationRunner {
@Value("${message}")
private String message;

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

@Override
public void run(final ApplicationArguments args) throws Exception {
System.out.println("-----------------");
System.out.println(message);
System.out.println("-----------------");
}
}

local profile로 실행하였을 때 정상적으로 Vault와 연동이 됐다면 아래와 같이 Hello World 메세지가 찍히는 걸 확인할 수 있다.

연동 확인은 끝났으니 message를 출력하는 코드는 지우고 local에서 Vault 설정또한 끄도록하자

1
2
3
4
5
6
7
8
@SpringBootApplication
public class ThirdpartyApplication {

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

}
1
2
3
4
5
6
7
8
9
10
11
12
13
# bootstrap-local.yml
spring:
cloud:
vault:
enabled: false # 설정 끄기
token: 00000000-0000-0000-0000-000000000000
scheme: http
host: localhost
port: 8200
kv:
enabled: true
backend: secret
default-context: local

Sentry, DataDog 설정 옮기기

이제 Vault 연동은 확인하였기 때문에 application.yml에 있는 설정을 옮기도록 하자.

실질적으로 Sentry, DataDog는 local이 아닌 환경에서 사용하기 때문에
Vault에 dev로 path를 따로 만들어서 application.yml에 있는 설정을 옮기도록한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"management.metrics.export.datadog": {
"enabled": true,
"api-key": "your api key",
"application-key": "your application key",
"host-tag": "hostTag",
"step": "PT10S"
},
"sentry": {
"enabled": true,
"dsn": "your dsn",
"stacktrace.app-packages": "io.github.ivvve.thirdparty"
}
}

Vault 설정 환경 변수로 설정하기

Spring Cloud Vault 설정을 하기 전에 Vault Token 또한 secret인데 이 값을 어디에 두어야하냐도 문제이다.
bootstrap.yml에 그대로 두면 이 역시 VCS에 저장되기 때문에 보안 측면에서 문제가 되는데 특히 Vault에 모든 secret값이 들어가기 때문에 더욱 문제가 될 수 있다.

주로 secret 값은 production 환경에서 환경 변수에 저장을 하여 쓰는 경우가 많고 instance 자체가 털리지 않는 한 외부로 노출되기 쉽지 않기 때문에 환경변수에 spring cloud vault 설정 값을 넣고 실행하는 식으로 Vault의 설정값을 지정하도록 하겠다.

실행 시 적용되는 환경 변수에 bootstrap-local.yml에 있던 값을 그대로 사용하면된다.
(대신 profile이 dev기 때문에 그에 맞춰 spring.cloud.vault.kv.default-context값을 dev로 바꿔준다.)

1
2
3
4
5
6
7
8
spring.cloud.vault.enabled: true
spring.cloud.vault.token: 00000000-0000-0000-0000-000000000000
spring.cloud.vault.scheme: http
spring.cloud.vault.host: localhost
spring.cloud.vault.port: 8200
spring.cloud.vault.kv.enabled: true
spring.cloud.vault.kv.backend: secret
spring.cloud.vault.kv.default-context: dev

나는 IntelliJ를 통해서 dev 환경을 아래와 같이 설정하였다. (Active Profike을 dev로 설정도 하였다)

위와 같이 설정하였다면 dev profile로 application을 실행시켜보자.

문제가 없다면 아래와 같이 정상적으로 로그가 찍히면서 application이 돌아감을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2020-10-31 13:15:48.630  INFO 58715 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.4.RELEASE)

2020-10-31 13:15:48.876 INFO 58715 --- [ main] o.s.v.c.e.LeaseAwareVaultPropertySource : Vault location [secret/application/dev] not resolvable: Not found
2020-10-31 13:15:48.879 INFO 58715 --- [ main] o.s.v.c.e.LeaseAwareVaultPropertySource : Vault location [secret/application] not resolvable: Not found
2020-10-31 13:15:48.882 INFO 58715 --- [ main] o.s.v.c.e.LeaseAwareVaultPropertySource : Vault location [secret/dev/dev] not resolvable: Not found
2020-10-31 13:15:48.889 INFO 58715 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-secret/application/dev'}, BootstrapPropertySource {name='bootstrapProperties-secret/application'}, BootstrapPropertySource {name='bootstrapProperties-secret/dev/dev'}, BootstrapPropertySource {name='bootstrapProperties-secret/dev'}]
CONSOLE: 2020-10-31 13:15:48.894 INFO 58715 --- [ main] i.g.i.thirdparty.ThirdpartyApplication : The following profiles are active: dev
2020-10-31 13:15:49.543 INFO 58715 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=29a7249a-20e1-3d45-b129-e4d7ee447e4c
2020-10-31 13:15:49.738 INFO 58715 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-10-31 13:15:49.750 INFO 58715 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-10-31 13:15:49.750 INFO 58715 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.38]
2020-10-31 13:15:49.833 INFO 58715 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-10-31 13:15:49.833 INFO 58715 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 927 ms
2020-10-31 13:15:49.873 INFO 58715 --- [ main] i.m.c.instrument.push.PushMeterRegistry : publishing metrics for DatadogMeterRegistry every 10s
2020-10-31 13:15:50.123 INFO 58715 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-10-31 13:15:50.500 INFO 58715 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2020-10-31 13:15:50.583 INFO 58715 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
CONSOLE: 2020-10-31 13:15:50.602 INFO 58715 --- [ main] i.g.i.thirdparty.ThirdpartyApplication : Started ThirdpartyApplication in 2.952 seconds (JVM running for 3.671)
2020-10-31 13:15:51.102 INFO 58715 --- [(3)-10.79.1.237] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-10-31 13:15:51.103 INFO 58715 --- [(3)-10.79.1.237] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-10-31 13:15:51.113 INFO 58715 --- [(3)-10.79.1.237] o.s.web.servlet.DispatcherServlet : Completed initialization in 9 ms

지금까지 Application을 운영할 때 필요한 요소인 APM, Error tracking, Secret Managing Tool과 연동하는 방법에 대해 알아보았다.

Share