SpringのVaultサポートその1 (Spring Vault)

目次

SpringのVaultサポート

SpringのVaultサポートは次の3つある

プロジェクト名 説明
Spring Vault VaultサポートのCore機能。Vault APIをRestTemplateでラップしたものと、ProperteySourceにマッピングするもの
Spring Cloud Vault Spring Cloud ConfigのConfig Serverの代わりにVaultを使うもの。データベースのCredentialsの生成もできる。内部でSpring Vaultを使用している。
Spring Cloud Config Spring Cloud ConfigのConfig ServerのバックエンドにVaultを使えるもの。 GitとVaultを組み合わせたConfig Serverを作成できる。Spring Vaultを使用していない。

この記事ではSpring Vaultについて簡単に紹介する。その他は別の記事にて。

Vaultのセットアップ

ローカルで立ててもいいが、ここではこちらで立てたCloud Foundry上のVault Serverを使う。

プロジェクトの作成

普通のSpring Bootプロジェクトに次のdependencyを追加。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>demo-spring-vault</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo-spring-vault</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Dalston.SR1</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.vault</groupId>
			<artifactId>spring-vault-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

VaultTemplate

まずはRestTemplateでVault API(vaultコマンド)にアクセスする処理をラップしたVaultTemplateを使う。

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.config.AbstractVaultConfiguration;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;

import com.fasterxml.jackson.annotation.JsonProperty;

@SpringBootApplication
public class DemoSpringVaultApplication {

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

	@Bean
	CommandLineRunner commandLineRunner(VaultTemplate vaultTemplate) {
		return x -> {
			// vault write secret/hello value=world
			vaultTemplate.write("secret/hello", new Hello("world"));

			// vault read secret/hello (=> POJO)
			VaultResponseSupport<Hello> hello = vaultTemplate.read("secret/hello",
					Hello.class);
			System.out.println(hello.getData().getValue());

			// vault read secret/hello (=> Map<String, Object>)
			VaultResponse r = vaultTemplate.read("secret/hello");
			System.out.println(r.getData());
		};
	}

	public static class Hello {
		final String value;

		public Hello(@JsonProperty("value") String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	@Configuration
	public static class VaultConfig extends AbstractVaultConfiguration {
		@Value("${spring.cloud.vault.token}")
		String token;
		@Value("${spring.cloud.vault.host:localhost}")
		String host;
		@Value("${spring.cloud.vault.port:8200}")
		int port;

		@Override
		public VaultEndpoint vaultEndpoint() {
			return VaultEndpoint.create(host, port);
		}

		@Override
		public ClientAuthentication clientAuthentication() {
			return new TokenAuthentication(token);
		}
	}
}

application.propertiesに次の設定を

spring.cloud.vault.host=cf-vault.cfapps.io
spring.cloud.vault.port=443
spring.cloud.vault.token=<Set your Vault token>

# following is not necessary
spring.main.web-environment=false
logging.level.org.springframework.web.client.RestTemplate=DEBUG

実行すると

(略)
2017-06-26 18:31:30.378 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Created POST request for "https://cf-vault.cfapps.io:443/v1/secret/hello"
2017-06-26 18:31:30.415 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Setting request Accept header to [application/json, application/*+json]
2017-06-26 18:31:30.421 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Writing [com.example.demo.DemoSpringVaultApplication$Hello@52851b44] using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@584f54e6]
2017-06-26 18:31:35.764 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : POST request for "https://cf-vault.cfapps.io:443/v1/secret/hello" resulted in 204 (No Content)
2017-06-26 18:31:35.767 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Created GET request for "https://cf-vault.cfapps.io:443/v1/secret/hello"
2017-06-26 18:31:35.770 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Setting request Accept header to [application/json, application/*+json]
2017-06-26 18:31:35.942 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : GET request for "https://cf-vault.cfapps.io:443/v1/secret/hello" resulted in 200 (OK)
2017-06-26 18:31:35.943 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Reading [org.springframework.vault.client.VaultResponses$1@269f4bad] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@584f54e6]
world
2017-06-26 18:31:35.950 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Created GET request for "https://cf-vault.cfapps.io:443/v1/secret/hello"
2017-06-26 18:31:35.951 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Setting request Accept header to [application/json, application/*+json]
2017-06-26 18:31:36.120 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : GET request for "https://cf-vault.cfapps.io:443/v1/secret/hello" resulted in 200 (OK)
2017-06-26 18:31:36.121 DEBUG 77397 --- [           main] o.s.web.client.RestTemplate              : Reading [class org.springframework.vault.support.VaultResponse] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@584f54e6]
{value=world}
(略)

簡単。認証方式ごとにClientAuthenticationの以下の実装クラスが用意されている。

  • org.springframework.vault.authentication.AppIdAuthentication
  • org.springframework.vault.authentication.AppRoleAuthentication
  • org.springframework.vault.authentication.AwsEc2Authentication
  • org.springframework.vault.authentication.ClientCertificateAuthentication
  • org.springframework.vault.authentication.CubbyholeAuthentication
  • org.springframework.vault.authentication.TokenAuthentication

@VaultPropertySource

@ProprtySourceのVault版でpropertiesファイルの代わりにVaultから設定を読み込み、Environmentオブジェクトや@Vauleで参照できる。

vaultコマンドで次の設定をしておく。

$ vault write secret/myapp foo=100 bar=aaa
Success! Data written to: secret/myapp
$ vault read secret/myapp
Key             	Value
---             	-----
refresh_interval	768h0m0s
bar             	aaa
foo             	100

このfoobarをアプリケーションに設定するようにすると、ソースコードは次のようになる。

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.vault.annotation.VaultPropertySource;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.config.AbstractVaultConfiguration;

@SpringBootApplication
@VaultPropertySource("secret/myapp")
public class DemoSpringVaultApplication {

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

	@Bean
	CommandLineRunner commandLineRunner(MyApp myApp) {
		return x -> {
			myApp.hello();
		};
	}

	@Configuration
	public static class VaultConfig extends AbstractVaultConfiguration {
		@Override
		public VaultEndpoint vaultEndpoint() {
			return VaultEndpoint.create("cf-vault.cfapps.io", 443);
		}

		@Override
		public ClientAuthentication clientAuthentication() {
			return new TokenAuthentication("<Set your Vault token>");
		}

	}

	@Component
	public static class MyApp {
		@Value("${foo}")
		int foo;
		@Value("${bar}")
		String bar;

		public void hello() {
			System.out.println(foo + " " + bar);
		}
	}
}

実行すると、

(略)
100 aaa
(略)

出ました。 Renewalもサポートされているけれど、今回は紹介しない。

注意点は、Valutの接続情報はPropertySourceができる前に設定されていないといけない(bootstrap.propertiesなど)。今回はVaultConfigクラスにハードコードした。

続く

次はSpring Cloud VaultかSpring Cloud ConfigのVaultバックエンドを紹介する。