---
title: SpringのVaultサポートその1 (Spring Vault)
tags: ["Spring Vault", "Spring Cloud", "Spring Boot", "Java", "Vault"]
categories: ["Programming", "Java", "org", "springframework", "vault"]
date: 2017-06-26T13:32:43Z
updated: 2017-06-26T13:56:45Z
---

**目次**
<!-- toc -->


### SpringのVaultサポート

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

| プロジェクト名 | 説明 |
| --- | --- |
| [Spring Vault](http://projects.spring.io/spring-vault/) | VaultサポートのCore機能。Vault APIを`RestTemplate`でラップしたものと、`ProperteySource`にマッピングするもの |
| [Spring Cloud Vault](http://cloud.spring.io/spring-cloud-vault/) | Spring Cloud ConfigのConfig Serverの代わりにVaultを使うもの。データベースのCredentialsの生成もできる。内部でSpring Vaultを使用している。 |
| [Spring Cloud Config](http://cloud.spring.io/spring-cloud-config/) | Spring Cloud ConfigのConfig Serverの[バックエンドにVault](http://cloud.spring.io/spring-cloud-static/spring-cloud-config/1.3.1.RELEASE/#_vault_backend)を使えるもの。 GitとVaultを組み合わせたConfig Serverを作成できる。Spring Vaultを使用していない。 |


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

### Vaultのセットアップ

ローカルで立ててもいいが、ここでは[こちら](https://blog.ik.am/entries/423)で立てたCloud Foundry上のVault Serverを使う。

### プロジェクトの作成

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

``` xml
<?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`を使う。

``` java
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
```

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

``` java
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バックエンドを紹介する。
