예제 코드는 Github repository 에서 확인 가능합니다.
앞서 Sentry, DataDog을 연동할 때 secret(DSN, API key, Application Key)을 그대로 application.yml에 넣어서 사용하였다. 하지만 secret이 VCS에 포함될 경우 보안 측면에서 bad practice이며 외부 유출 가능성이 있다.
그렇기 때문에 이러한 평문으로 된 secret 값을 안전하게 숨겨주는 tool이 여러가지 있는데, 이 예제에서는 Vault
를 사용하여 secret 값을 저장하고 꺼내쓰는 방식에 대해 알아보도록 하겠다.
Vault 설치 먼저 로컬에 Vault를 설치하여 세팅을 해보자. mac에서는 homebrew를 사용하여 설치하면된다.
설치가 되었으면 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 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 10 s 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과 연동하는 방법에 대해 알아보았다.