Support Consul credential generation

Fixes gh-5
This commit is contained in:
Mark Paluch
2016-06-18 21:34:22 +02:00
parent 7a27b605f7
commit 69a4bd2092
10 changed files with 285 additions and 13 deletions

4
.gitignore vendored
View File

@@ -15,7 +15,7 @@ _site/
*.iws
.factorypath
download/
vault/download
vault/vault
/vault/
/consul/
work
build/

View File

@@ -15,7 +15,9 @@ install:
- apache-cassandra-2.2.6/bin/cassandra
- src/test/bash/create_certificates.sh
- src/test/bash/install_vault.sh
- src/test/bash/install_consul.sh
- src/test/bash/local_run_vault.sh &
- src/test/bash/local_run_consul.sh &
before_script:
- mysql -e "CREATE USER 'spring' IDENTIFIED by 'vault';"

View File

@@ -113,15 +113,15 @@ spring.cloud.vault:
----
[source,yaml]
MyUserIdMechanism.java
.MyUserIdMechanism.java
----
public class MyUserIdMechanism implements AppIdUserIdMechanism {
@Override
public String createUserId() {
String userId = ...
return userId;
}
@Override
public String createUserId() {
String userId = ...
return userId;
}
}
----
@@ -151,7 +151,7 @@ JDBC secrets need to be configured separately.
Spring Cloud Vault allows to obtain credentials for Apache Cassandra.
The integration can be enabled by setting `spring.cloud.vault.cassandra.enabled=true`
(default "false"). Username and password are stored in `spring.data.cassandra.username`
(default `false`). Username and password are stored in `spring.data.cassandra.username`
and `spring.data.cassandra.password` so using Spring Boot will pick up the generated
credentials without further configuration. You can configure the property names
by setting `spring.cloud.vault.cassandra.username-property` and
@@ -173,7 +173,7 @@ See also: https://www.vaultproject.io/docs/secrets/cassandra/index.html[Vault Do
Spring Cloud Vault allows to obtain credentials for MySQL.
The integration can be enabled by setting `spring.cloud.vault.mysql.enabled=true`
(default "false"). Username and password are stored in `spring.datasource.username`
(default `false`). Username and password are stored in `spring.datasource.username`
and `spring.datasource.password` so using Spring Boot will pick up the generated
credentials without further configuration. You can configure the property names
by setting `spring.cloud.vault.mysql.username-property` and
@@ -195,7 +195,7 @@ See also: https://www.vaultproject.io/docs/secrets/mysql/index.html[Vault Docume
Spring Cloud Vault allows to obtain credentials for PostgreSQL.
The integration can be enabled by setting `spring.cloud.vault.postgresql.enabled=true`
(default "false"). Username and password are stored in `spring.datasource.username`
(default `false`). Username and password are stored in `spring.datasource.username`
and `spring.datasource.password` so using Spring Boot will pick up the generated
credentials without further configuration. You can configure the property names
by setting `spring.cloud.vault.postgresql.username-property` and
@@ -212,6 +212,28 @@ spring.cloud.vault:
See also: https://www.vaultproject.io/docs/secrets/postgresql/index.html[Vault Documentation: Setting up PostgreSQL with Vault]
[[vault-client-consul]]
== Consul
Spring Cloud Vault allows to obtain credentials for Hashicorp Consil.
The integration can be enabled by setting `spring.cloud.vault.consul.enabled=true`
(default `false`). The obtained token is stored in `spring.cloud.consul.token`
so using Spring Cloud Consul can pick up the generated
credentials without further configuration. You can configure the property name
by setting `spring.cloud.vault.consul.token-property`.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
consul:
enabled: true
----
See also: https://www.vaultproject.io/docs/secrets/consul/index.html[Vault Documentation: Setting up Consul with Vault]
[[vault-client-fail-fast]]
== Vault Client Fail Fast

View File

@@ -110,4 +110,40 @@ public class SecureBackendAccessors {
}
};
}
/**
* Creates a {@link SecureBackendAccessor} for a secure backend using
* {@link org.springframework.cloud.vault.VaultProperties.Consul}.
* This accessor transforms Vault's token property names to names provided
* with {@link VaultProperties.Consul#getTokenProperty()}.
*
* @param properties must not be {@literal null}.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor consul(
final VaultProperties.Consul properties) {
Assert.notNull(properties, "Consul properties must not be null");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
Map<String, String> result = new HashMap();
result.put(properties.getTokenProperty(), input.get("token"));
return result;
}
};
}
}

View File

@@ -100,6 +100,8 @@ public class VaultProperties {
private Cassandra cassandra = new Cassandra();
private Consul consul = new Consul();
/**
* Application name for AppId authentication.
*/
@@ -251,6 +253,32 @@ public class VaultProperties {
private String passwordProperty = "spring.data.cassandra.password";
}
@Data
public static class Consul {
/**
* Enable consul backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* Consul backend path.
*/
@NotEmpty
private String backend = "consul";
/**
* Target property for the obtained token.
*/
@NotEmpty
private String tokenProperty = "spring.cloud.consul.token";
}
/**
* Configuration properties interface for database secrets.
*/

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
/**
* Integration tests for {@link VaultClient} using the consul secret backend. This test
* requires a running Consul instance, see {@link #CONNECTION_URL}.
*
* @author Mark Paluch
*/
public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
private final static String CONSUL_HOST = "localhost";
private final static int CONSUL_PORT = 8500;
private final static String CONNECTION_URL = String.format("%s:%d", CONSUL_HOST,
CONSUL_PORT);
private final static String POLICY = "key \"\" { policy = \"read\" }";
private final static String CONSUL_ACL_MASTER_TOKEN = "consul-master-token";
private final static ParameterizedTypeReference<Map<String, String>> STRING_MAP = new ParameterizedTypeReference<Map<String, String>>() {
};
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.Consul consul = vaultProperties.getConsul();
private TestRestTemplate restTemplate = new TestRestTemplate();
/**
* Initialize the postgresql secret backend.
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
assumeTrue(CanConnect.to(new InetSocketAddress(CONSUL_HOST, CONSUL_PORT)));
consul.setEnabled(true);
consul.setRole("readonly");
if (!prepare().hasSecret(consul.getBackend())) {
prepare().mountSecret(consul.getBackend());
}
HttpHeaders headers = new HttpHeaders();
headers.add("X-Consul-Token", CONSUL_ACL_MASTER_TOKEN);
HttpEntity<String> requestEntity = new HttpEntity<>(
"{\"Name\": \"sample\", \"Type\": \"management\"}", headers);
ResponseEntity<Map<String, String>> tokenResponse = restTemplate.exchange(
"http://{address}/v1/acl/create", HttpMethod.PUT, requestEntity,
STRING_MAP, CONNECTION_URL);
Map<String, String> consulAccess = new HashMap<>();
consulAccess.put("address", CONNECTION_URL);
consulAccess.put("token", tokenResponse.getBody().get("ID"));
prepare().write(String.format("%s/config/access", consul.getBackend()),
consulAccess);
prepare().write(
String.format("%s/roles/%s", consul.getBackend(), consul.getRole()),
Collections.singletonMap("policy",
Base64.encodeBase64String(POLICY.getBytes())));
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
}
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(consul(consul),
Settings.token());
assertThat(secretProperties).containsKeys("spring.cloud.consul.token");
}
}

View File

@@ -0,0 +1,8 @@
{
"log_level": "INFO",
"server": true,
"datacenter": "dc1",
"advertise_addr": "127.0.0.1",
"acl_datacenter": "dc1",
"acl_master_token": "consul-master-token"
}

45
src/test/bash/install_consul.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
###########################################################################
# Download and Install Consul #
# This script is prepared for caching of the download directory #
###########################################################################
CONSUL_VER="0.6.3"
UNAME=$(uname -s | tr '[:upper:]' '[:lower:]')
CONSUL_ZIP="consul_${CONSUL_VER}_${UNAME}_amd64.zip"
IGNORE_CERTS="${IGNORE_CERTS:-no}"
# cleanup
mkdir -p consul
if [[ ! -f "download/${CONSUL_ZIP}" ]] ; then
cd download
# install Vault
if [[ "${IGNORE_CERTS}" == "no" ]] ; then
echo "Downloading Consul with certs verification"
wget "https://releases.hashicorp.com/consul/${CONSUL_VER}/${CONSUL_ZIP}"
else
echo "WARNING... Downloading Consul WITHOUT certs verification"
wget "https://releases.hashicorp.com/consul/${CONSUL_VER}/${CONSUL_ZIP}" --no-check-certificate
fi
if [[ $? != 0 ]] ; then
echo "Cannot download Consul"
exit 1
fi
cd ..
fi
cd consul
if [[ -f consul ]] ; then
rm consul
fi
unzip ../download/${CONSUL_ZIP}
chmod a+x consul
# check
./consul --version

View File

@@ -2,7 +2,7 @@
###########################################################################
# Download and Install Vault #
# This script is prepared for caching of the vault/download directory #
# This script is prepared for caching of the download directory #
###########################################################################
@@ -26,7 +26,7 @@ if [[ ! -f "download/${VAULT_ZIP}" ]] ; then
fi
if [[ $? != 0 ]] ; then
echo "Cannot download vault"
echo "Cannot download Vault"
exit 1
fi
cd ..

View File

@@ -0,0 +1,18 @@
#!/bin/bash
###########################################################################
# Start Consul on localhost:8500 #
###########################################################################
BASEDIR=`dirname $0`/../../..
#!/bin/bash
mkdir -p ${BASEDIR}/consul/config
mkdir -p ${BASEDIR}/consul/data
./consul/consul agent -server \
-bootstrap-expect 1 \
-data-dir ${BASEDIR}/consul/data \
-config-file=${BASEDIR}/spring-cloud-vault-config/src/test/resources/consul.json
exit $?