IK.AM

@making's tech note


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

🗃 {Programming/Java/org/springframework/vault}
🏷 Spring Vault 🏷 Spring Cloud 🏷 Spring Boot 🏷 Java 🏷 Vault 
🗓 Updated at 2017-06-26T13:56:45Z  🗓 Created at 2017-06-26T13:32:43Z   🌎 English Page

目次

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


✒️️ Edit  ⏰ History  🗑 Delete