Provide a Vault client API for low-level and higer-order functions
Split functionality from VaultClient and refactor it into VaultConfigOperations/VaultConfigTemplate. The template now holds the authentication state and the property source just uses the template. Fixes gh-23
This commit is contained in:
@@ -19,7 +19,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultSecretBackend;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -15,23 +15,26 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config.aws;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.aws.VaultConfigAwsBootstrapConfiguration.AwsSecureBackendAccessorFactory.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultConfigOperations;
|
||||
import org.springframework.cloud.vault.config.VaultTemplate;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.aws.VaultConfigAwsBootstrapConfiguration.AwsSecureBackendAccessorFactory.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the aws secret backend. This test
|
||||
* requires AWS credentials and a region, see {@link #AWS_ACCESS_KEY} and
|
||||
@@ -48,7 +51,7 @@ public class AwsSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
private final static String ARN = "arn:aws:iam::aws:policy/ReadOnlyAccess";
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
private VaultConfigOperations configOperations;
|
||||
private VaultAwsProperties aws = new VaultAwsProperties();
|
||||
|
||||
/**
|
||||
@@ -79,14 +82,14 @@ public class AwsSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
prepare().write(String.format("%s/roles/%s", aws.getBackend(), aws.getRole()),
|
||||
Collections.singletonMap("arn", ARN));
|
||||
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCredentialsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read(forAws(aws),
|
||||
Settings.token());
|
||||
Map<String, String> secretProperties = configOperations.read(forAws(aws));
|
||||
|
||||
assertThat(secretProperties).containsKeys("cloud.aws.credentials.accessKey",
|
||||
"cloud.aws.credentials.secretKey");
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultSecretBackend;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -15,22 +15,18 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config.consul;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.consul.VaultConfigConsulBootstrapConfiguration.ConsulSecureBackendAccessorFactory.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultConfigOperations;
|
||||
import org.springframework.cloud.vault.config.VaultTemplate;
|
||||
import org.springframework.cloud.vault.util.CanConnect;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
@@ -40,6 +36,13 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.consul.VaultConfigConsulBootstrapConfiguration.ConsulSecureBackendAccessorFactory.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the consul secret backend. This test
|
||||
* requires a running Consul instance, see {@link #CONNECTION_URL}.
|
||||
@@ -61,7 +64,7 @@ public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
};
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
private VaultConfigOperations configOperations;
|
||||
private VaultConsulProperties consul = new VaultConsulProperties();
|
||||
|
||||
private TestRestTemplate restTemplate = new TestRestTemplate();
|
||||
@@ -103,14 +106,14 @@ public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
Collections.singletonMap("policy",
|
||||
Base64Utils.encodeToString(POLICY.getBytes())));
|
||||
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCredentialsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read(forConsul(consul),
|
||||
Settings.token());
|
||||
Map<String, String> secretProperties = configOperations.read(forConsul(consul));
|
||||
|
||||
assertThat(secretProperties).containsKeys("spring.cloud.consul.token");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultSecretBackend;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -15,24 +15,27 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config.databases;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultConfigOperations;
|
||||
import org.springframework.cloud.vault.config.VaultTemplate;
|
||||
import org.springframework.cloud.vault.util.CanConnect;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the cassandra secret backend. This test
|
||||
* requires a running Cassandra instance, see {@link #CASSANDRA_HOST} and other
|
||||
@@ -52,7 +55,7 @@ public class CassandraSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
+ "GRANT SELECT ON ALL KEYSPACES TO {{username}};";
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
private VaultConfigOperations configOperations;
|
||||
private VaultCassandraProperties cassandra = new VaultCassandraProperties();
|
||||
|
||||
/**
|
||||
@@ -80,18 +83,21 @@ public class CassandraSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
prepare().write(String.format("%s/config/connection", cassandra.getBackend()),
|
||||
connection);
|
||||
|
||||
prepare().write(
|
||||
String.format("%s/roles/%s", cassandra.getBackend(), cassandra.getRole()),
|
||||
Collections.singletonMap("creation_cql", CREATE_USER_AND_GRANT_CQL));
|
||||
prepare()
|
||||
.write(String.format("%s/roles/%s", cassandra.getBackend(),
|
||||
cassandra.getRole()),
|
||||
Collections.singletonMap("creation_cql",
|
||||
CREATE_USER_AND_GRANT_CQL));
|
||||
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCredentialsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read(forDatabase(cassandra),
|
||||
Settings.token());
|
||||
Map<String, String> secretProperties = configOperations
|
||||
.read(forDatabase(cassandra));
|
||||
|
||||
assertThat(secretProperties).containsKeys("spring.data.cassandra.username",
|
||||
"spring.data.cassandra.password");
|
||||
|
||||
@@ -15,23 +15,26 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config.databases;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.forDatabase;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultConfigOperations;
|
||||
import org.springframework.cloud.vault.config.VaultTemplate;
|
||||
import org.springframework.cloud.vault.util.CanConnect;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the mysql secret backend. This test
|
||||
* requires a running MySQL instance, see {@link #ROOT_CREDENTIALS}.
|
||||
@@ -42,13 +45,13 @@ public class MySqlSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
private final static int MYSQL_PORT = 3306;
|
||||
private final static String MYSQL_HOST = "localhost";
|
||||
private final static String ROOT_CREDENTIALS = String
|
||||
.format("spring:vault@tcp(%s:%d)/", MYSQL_HOST, MYSQL_PORT);
|
||||
private final static String ROOT_CREDENTIALS = String.format(
|
||||
"spring:vault@tcp(%s:%d)/", MYSQL_HOST, MYSQL_PORT);
|
||||
private final static String CREATE_USER_AND_GRANT_SQL = "CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';"
|
||||
+ "GRANT SELECT ON *.* TO '{{name}}'@'%';";
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
private VaultConfigOperations configOperations;
|
||||
private VaultMySqlProperties mySql = new VaultMySqlProperties();
|
||||
|
||||
/**
|
||||
@@ -71,20 +74,20 @@ public class MySqlSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
prepare().write(String.format("%s/config/connection", mySql.getBackend()),
|
||||
Collections.singletonMap("connection_url", ROOT_CREDENTIALS));
|
||||
|
||||
prepare().write(String.format("%s/roles/%s", mySql.getBackend(), mySql.getRole()),
|
||||
prepare().write(
|
||||
String.format("%s/roles/%s", mySql.getBackend(), mySql.getRole()),
|
||||
Collections.singletonMap("sql", CREATE_USER_AND_GRANT_SQL));
|
||||
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCredentialsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read(forDatabase(mySql),
|
||||
Settings.token());
|
||||
Map<String, String> secretProperties = configOperations.read(forDatabase(mySql));
|
||||
|
||||
assertThat(secretProperties).containsKeys("spring.datasource.username",
|
||||
"spring.datasource.password");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,23 +15,26 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config.databases;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultConfigOperations;
|
||||
import org.springframework.cloud.vault.config.VaultTemplate;
|
||||
import org.springframework.cloud.vault.util.CanConnect;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the postgresql secret backend. This
|
||||
* test requires a running PostgreSQL instance, see {@link #CONNECTION_URL}.
|
||||
@@ -52,7 +55,7 @@ public class PostgreSqlSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
+ "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";";
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
private VaultConfigOperations configOperations;
|
||||
private VaultPostgreSqlProperties postgreSql = new VaultPostgreSqlProperties();
|
||||
|
||||
/**
|
||||
@@ -80,14 +83,15 @@ public class PostgreSqlSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
postgreSql.getRole()),
|
||||
Collections.singletonMap("sql", CREATE_USER_AND_GRANT_SQL));
|
||||
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCredentialsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read(forDatabase(postgreSql),
|
||||
Settings.token());
|
||||
Map<String, String> secretProperties = configOperations
|
||||
.read(forDatabase(postgreSql));
|
||||
|
||||
assertThat(secretProperties).containsKeys("spring.datasource.username",
|
||||
"spring.datasource.password");
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultSecretBackend;
|
||||
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -15,24 +15,27 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config.rabbitmq;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration.RabbitMqSecureBackendAccessorFactory.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultConfigOperations;
|
||||
import org.springframework.cloud.vault.config.VaultTemplate;
|
||||
import org.springframework.cloud.vault.util.CanConnect;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration.RabbitMqSecureBackendAccessorFactory.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the rabbitmq secret backend. This test
|
||||
* requires a running RabbitMQ instance, see {@link #RABBITMQ_URI}.
|
||||
@@ -53,7 +56,7 @@ public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
private final static String VHOSTS_ROLE = "{\"/\":{\"write\": \".*\", \"read\": \".*\"}}";
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
private VaultConfigOperations configOperations;
|
||||
private VaultRabbitMqProperties rabbitmq = new VaultRabbitMqProperties();
|
||||
|
||||
/**
|
||||
@@ -64,8 +67,8 @@ public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
assumeTrue(CanConnect
|
||||
.to(new InetSocketAddress(RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT)));
|
||||
assumeTrue(CanConnect.to(new InetSocketAddress(RABBITMQ_HOST,
|
||||
RABBITMQ_HTTP_MANAGEMENT_PORT)));
|
||||
|
||||
rabbitmq.setEnabled(true);
|
||||
rabbitmq.setRole("readonly");
|
||||
@@ -86,14 +89,15 @@ public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
String.format("%s/roles/%s", rabbitmq.getBackend(), rabbitmq.getRole()),
|
||||
Collections.singletonMap("vhosts", VHOSTS_ROLE));
|
||||
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCredentialsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read(forRabbitMq(rabbitmq),
|
||||
Settings.token());
|
||||
Map<String, String> secretProperties = configOperations
|
||||
.read(forRabbitMq(rabbitmq));
|
||||
|
||||
assertThat(secretProperties).containsKeys("spring.rabbitmq.username",
|
||||
"spring.rabbitmq.password");
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.vault;
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultSecretBackend;
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.springframework.cloud.vault.config;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,8 +19,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
import org.springframework.cloud.vault.VaultSecretBackend;
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import static org.springframework.cloud.vault.VaultBootstrapConfiguration.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,11 +22,11 @@ import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.vault.AppIdUserIdMechanism;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultBootstrapConfiguration;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
@@ -39,6 +37,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.springframework.cloud.vault.VaultBootstrapConfiguration.*;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@@ -59,19 +59,26 @@ public class VaultConfigBootstrapConfiguration implements ApplicationContextAwar
|
||||
}
|
||||
|
||||
@Bean
|
||||
public VaultPropertySourceLocator vaultPropertySourceLocator(VaultClient vaultClient,
|
||||
VaultProperties vaultProperties,
|
||||
VaultGenericBackendProperties vaultGenericBackendProperties,
|
||||
ClientFactoryWrapper clientFactoryWrapper) {
|
||||
@ConditionalOnMissingBean
|
||||
public VaultTemplate vaultOperations(VaultProperties properties,
|
||||
ClientFactoryWrapper clientFactoryWrapper, VaultClient client) {
|
||||
|
||||
ClientAuthentication clientAuthentication = clientAuthentication(
|
||||
applicationContext, clientFactoryWrapper, properties);
|
||||
|
||||
return new VaultTemplate(properties, client, clientAuthentication);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public VaultPropertySourceLocator vaultPropertySourceLocator(
|
||||
VaultOperations operations, VaultProperties vaultProperties,
|
||||
VaultGenericBackendProperties vaultGenericBackendProperties) {
|
||||
|
||||
Collection<SecureBackendAccessor> backendAccessors = SecureBackendFactories
|
||||
.createBackendAcessors(vaultSecretBackends, factories);
|
||||
|
||||
ClientAuthentication clientAuthentication = clientAuthentication(
|
||||
applicationContext, clientFactoryWrapper, vaultProperties);
|
||||
|
||||
return new VaultPropertySourceLocator(vaultClient, clientAuthentication,
|
||||
vaultProperties, vaultGenericBackendProperties, backendAccessors);
|
||||
return new VaultPropertySourceLocator(operations.opsForConfig(), vaultProperties,
|
||||
vaultGenericBackendProperties, backendAccessors);
|
||||
}
|
||||
|
||||
private ClientAuthentication clientAuthentication(
|
||||
@@ -82,12 +89,10 @@ public class VaultConfigBootstrapConfiguration implements ApplicationContextAwar
|
||||
clientFactoryWrapper.getClientHttpRequestFactory());
|
||||
ClientAuthentication clientAuthentication;
|
||||
|
||||
if (vaultProperties
|
||||
.getAuthentication() == VaultProperties.AuthenticationMethod.TOKEN) {
|
||||
if (vaultProperties.getAuthentication() == VaultProperties.AuthenticationMethod.TOKEN) {
|
||||
clientAuthentication = ClientAuthentication.token(vaultProperties);
|
||||
}
|
||||
else if (vaultProperties
|
||||
.getAuthentication() == VaultProperties.AuthenticationMethod.APPID) {
|
||||
else if (vaultProperties.getAuthentication() == VaultProperties.AuthenticationMethod.APPID) {
|
||||
|
||||
Map<String, AppIdUserIdMechanism> appIdUserIdMechanisms = applicationContext
|
||||
.getBeansOfType(AppIdUserIdMechanism.class);
|
||||
@@ -117,10 +122,9 @@ public class VaultConfigBootstrapConfiguration implements ApplicationContextAwar
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
|
||||
this.vaultSecretBackends = applicationContext
|
||||
.getBeansOfType(VaultSecretBackend.class).values();
|
||||
this.factories = (Collection) applicationContext
|
||||
.getBeansOfType(SecureBackendAccessorFactory.class).values();
|
||||
|
||||
this.vaultSecretBackends = applicationContext.getBeansOfType(
|
||||
VaultSecretBackend.class).values();
|
||||
this.factories = (Collection) applicationContext.getBeansOfType(
|
||||
SecureBackendAccessorFactory.class).values();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.vault.VaultClientResponse;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
|
||||
/**
|
||||
* Interface that specified a basic set of Vault operations, implemented by
|
||||
* {@link VaultConfigTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface VaultConfigOperations {
|
||||
|
||||
/**
|
||||
* Read configuration from a secure backend encapsulated within a
|
||||
* {@link SecureBackendAccessor}. Reading data using this method is suitable for
|
||||
* secret backends that do not require a request body.
|
||||
*
|
||||
* @param secureBackendAccessor must not be {@literal null}.
|
||||
* @return the configuration data. May be empty but never {@literal null}.
|
||||
* @throws IllegalStateException if {@link VaultProperties#isFailFast()} is enabled.
|
||||
*/
|
||||
Map<String, String> read(SecureBackendAccessor secureBackendAccessor);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultClientResponse;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.config.VaultOperations.SessionCallback;
|
||||
import org.springframework.cloud.vault.config.VaultOperations.VaultSession;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
/**
|
||||
* Central class to retrieve configuration from Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see VaultClient
|
||||
* @see ClientAuthentication
|
||||
*/
|
||||
@CommonsLog
|
||||
public class VaultConfigTemplate implements VaultConfigOperations {
|
||||
|
||||
private final VaultOperations vaultOperations;
|
||||
private final VaultProperties properties;
|
||||
private final VaultConfigSessionCallback callback;
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultConfigTemplate}.
|
||||
*
|
||||
* @param vaultOperations must not be {@literal null}.
|
||||
* @param properties must not be {@literal null}.
|
||||
*/
|
||||
public VaultConfigTemplate(VaultOperations vaultOperations, VaultProperties properties) {
|
||||
|
||||
Assert.notNull(vaultOperations, "VaultOperations must not be null!");
|
||||
Assert.notNull(properties, "VaultProperties must not be null!");
|
||||
|
||||
this.vaultOperations = vaultOperations;
|
||||
this.properties = properties;
|
||||
this.callback = new VaultConfigSessionCallback(log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> read(SecureBackendAccessor secureBackendAccessor) {
|
||||
|
||||
Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be null!");
|
||||
|
||||
VaultClientResponse response = vaultOperations.doWithVault("{backend}/{key}",
|
||||
secureBackendAccessor.variables(), callback);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK) {
|
||||
return secureBackendAccessor
|
||||
.transformProperties(response.getBody().getData());
|
||||
}
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
log.info(String
|
||||
.format("Could not locate PropertySource: %s", "key not found"));
|
||||
}
|
||||
else if (properties.isFailFast()) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Could not locate PropertySource and the fail fast property is set, failing Status %d %s",
|
||||
response.getStatusCode().value(), response.getMessage()));
|
||||
}
|
||||
else {
|
||||
log.warn(String.format("Could not locate PropertySource: Status %d %s",
|
||||
response.getStatusCode().value(), response.getMessage()));
|
||||
}
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
static class VaultConfigSessionCallback implements SessionCallback {
|
||||
|
||||
private final Log log;
|
||||
|
||||
public VaultConfigSessionCallback(Log log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultClientResponse doWithVault(URI uri, VaultSession session) {
|
||||
log.info(String.format("Fetching config from Vault at: %s", uri));
|
||||
return session.read(uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.vault.VaultClientResponse;
|
||||
|
||||
/**
|
||||
* Interface that specified a basic set of Vault operations, implemented by
|
||||
* {@link VaultTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface VaultOperations {
|
||||
|
||||
/**
|
||||
* @return the operations interface to interact with Vault configuration data.
|
||||
*/
|
||||
VaultConfigOperations opsForConfig();
|
||||
|
||||
/**
|
||||
* Executes a Vault {@link SessionCallback}. Allows to interact with Vault in an
|
||||
* authenticated session.
|
||||
*
|
||||
* @param path the path of the resource, e.g. {@code transit/encrypt/foo}, must not be
|
||||
* empty or {@literal null}.
|
||||
* @param sessionCallback the request.
|
||||
* @return
|
||||
*/
|
||||
<T> T doWithVault(String path, SessionCallback sessionCallback);
|
||||
|
||||
/**
|
||||
* Executes a Vault {@link SessionCallback}. Allows to interact with Vault in an
|
||||
* authenticated session.
|
||||
*
|
||||
* @param pathTemplate the path of the resource, e.g. {@code transit/ key}/foo}, must
|
||||
* not be empty or {@literal null}. * @param variables the variables for expansion of
|
||||
* the {@code pathTemplate}, must not be {@literal null}.
|
||||
* @param sessionCallback the request.
|
||||
* @return
|
||||
*/
|
||||
<T> T doWithVault(String pathTemplate, Map<String, ?> variables,
|
||||
SessionCallback sessionCallback);
|
||||
|
||||
/**
|
||||
* Callback to execute actions within an authenticated {@link VaultSession}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface SessionCallback {
|
||||
|
||||
/**
|
||||
* Callback method.
|
||||
*
|
||||
* @param uri the URI that is used for the request, must not be {@literal null}.
|
||||
* @param session session to use, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<T> T doWithVault(URI uri, VaultSession session);
|
||||
}
|
||||
|
||||
/**
|
||||
* An authenticated Vault session.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface VaultSession {
|
||||
|
||||
/**
|
||||
* Read data from the given Vault {@code uri}.
|
||||
*
|
||||
* @param uri must not be {@literal null}.
|
||||
* @return the {@link VaultClientResponse}.
|
||||
*/
|
||||
public VaultClientResponse read(URI uri);
|
||||
|
||||
/**
|
||||
* Write data to the given Vault {@code uri}.
|
||||
*
|
||||
* @param uri must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return the {@link VaultClientResponse}.
|
||||
*/
|
||||
public VaultClientResponse write(URI uri, Object entity);
|
||||
}
|
||||
}
|
||||
@@ -19,57 +19,42 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
/**
|
||||
* A {@link EnumerablePropertySource} backed by {@link VaultClient}.
|
||||
* A {@link EnumerablePropertySource} backed by {@link VaultConfigOperations}.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CommonsLog
|
||||
class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
|
||||
class VaultPropertySource extends EnumerablePropertySource<VaultConfigOperations> {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
private final SecureBackendAccessor secureBackendAccessor;
|
||||
private final Map<String, String> properties = new LinkedHashMap<>();
|
||||
private final ClientAuthentication clientAuthentication;
|
||||
private final transient VaultState vaultState;
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultPropertySource}.
|
||||
*
|
||||
* @param vaultClient must not be {@literal null}.
|
||||
* @param clientAuthentication mist not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param properties must not be {@literal null}.
|
||||
* @param state shared Vault state, must not be {@literal null}.
|
||||
* @param secureBackendAccessor must not be {@literal null}.
|
||||
*/
|
||||
public VaultPropertySource(VaultClient vaultClient,
|
||||
ClientAuthentication clientAuthentication, VaultProperties properties,
|
||||
VaultState state, SecureBackendAccessor secureBackendAccessor) {
|
||||
public VaultPropertySource(VaultConfigOperations operations,
|
||||
VaultProperties properties, SecureBackendAccessor secureBackendAccessor) {
|
||||
|
||||
super(secureBackendAccessor.getName(), vaultClient);
|
||||
super(secureBackendAccessor.getName(), operations);
|
||||
|
||||
Assert.notNull(vaultClient, "VaultClient must not be null!");
|
||||
Assert.notNull(operations, "VaultConfigOperations must not be null!");
|
||||
Assert.notNull(properties, "VaultProperties must not be null!");
|
||||
Assert.notNull(state, "VaultState must not be null!");
|
||||
Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be null!");
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null!");
|
||||
|
||||
this.vaultProperties = properties;
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.vaultState = state;
|
||||
this.secureBackendAccessor = secureBackendAccessor;
|
||||
}
|
||||
|
||||
@@ -79,8 +64,7 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
|
||||
public void init() {
|
||||
|
||||
try {
|
||||
Map<String, String> values = this.source.read(this.secureBackendAccessor,
|
||||
obtainToken());
|
||||
Map<String, String> values = this.source.read(this.secureBackendAccessor);
|
||||
if (values != null) {
|
||||
this.properties.putAll(values);
|
||||
}
|
||||
@@ -102,42 +86,6 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
|
||||
}
|
||||
}
|
||||
|
||||
private VaultToken obtainToken() {
|
||||
|
||||
if (vaultState.getToken() != null) {
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
if (vaultProperties.getAuthentication() == AuthenticationMethod.TOKEN) {
|
||||
|
||||
Assert.hasText(vaultProperties.getToken(), "Vault Token must not be empty");
|
||||
vaultState.setToken(clientAuthentication.login());
|
||||
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
if (vaultProperties.getAuthentication() == AuthenticationMethod.AWS_EC2) {
|
||||
|
||||
vaultState.setToken(clientAuthentication.login());
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
if (vaultProperties.getAuthentication() == AuthenticationMethod.APPID) {
|
||||
|
||||
AppIdProperties appIdProperties = vaultProperties.getAppId();
|
||||
Assert.hasText(vaultProperties.getApplicationName(),
|
||||
"AppId must not be empty");
|
||||
Assert.hasText(appIdProperties.getAppIdPath(), "AppIdPath must not be empty");
|
||||
|
||||
vaultState.setToken(clientAuthentication.login());
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("Authentication method %s not supported",
|
||||
vaultProperties.getAuthentication()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return this.properties.get(name);
|
||||
@@ -148,4 +96,4 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
|
||||
Set<String> strings = this.properties.keySet();
|
||||
return strings.toArray(new String[strings.size()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import static org.springframework.cloud.vault.config.SecureBackendAccessors.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -25,8 +23,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.SecureBackendAccessor;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
@@ -36,6 +32,8 @@ import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.cloud.vault.config.SecureBackendAccessors.*;
|
||||
|
||||
/**
|
||||
* {@link PropertySourceLocator} using {@link VaultClient}.
|
||||
*
|
||||
@@ -44,125 +42,124 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class VaultPropertySourceLocator implements PropertySourceLocator {
|
||||
|
||||
private final VaultClient vaultClient;
|
||||
private final ClientAuthentication clientAuthentication;
|
||||
private final VaultProperties properties;
|
||||
private final VaultGenericBackendProperties genericBackendProperties;
|
||||
private final Collection<SecureBackendAccessor> backendAccessors;
|
||||
private final VaultConfigOperations operations;
|
||||
private final VaultProperties properties;
|
||||
private final VaultGenericBackendProperties genericBackendProperties;
|
||||
private final Collection<SecureBackendAccessor> backendAccessors;
|
||||
|
||||
private transient final VaultState vaultState = new VaultState();
|
||||
/**
|
||||
* Creates a new {@link VaultPropertySourceLocator}.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param properties must not be {@literal null}.
|
||||
* @param genericBackendProperties must not be {@literal null}.
|
||||
* @param backendAccessors must not be {@literal null}.
|
||||
*/
|
||||
public VaultPropertySourceLocator(VaultConfigOperations operations,
|
||||
VaultProperties properties,
|
||||
VaultGenericBackendProperties genericBackendProperties,
|
||||
Collection<SecureBackendAccessor> backendAccessors) {
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultPropertySourceLocator}.
|
||||
*
|
||||
* @param vaultClient must not be {@literal null}.
|
||||
* @param clientAuthentication must not be {@literal null}.
|
||||
* @param properties must not be {@literal null}.
|
||||
* @param genericBackendProperties must not be {@literal null}.
|
||||
* @param backendAccessors must not be {@literal null}.
|
||||
*/
|
||||
public VaultPropertySourceLocator(VaultClient vaultClient, ClientAuthentication clientAuthentication,
|
||||
VaultProperties properties, VaultGenericBackendProperties genericBackendProperties,
|
||||
Collection<SecureBackendAccessor> backendAccessors) {
|
||||
Assert.notNull(operations, "VaultConfigOperations must not be null");
|
||||
Assert.notNull(properties, "VaultProperties must not be null");
|
||||
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
|
||||
Assert.notNull(genericBackendProperties,
|
||||
"VaultGenericBackendProperties must not be null");
|
||||
|
||||
Assert.notNull(vaultClient, "VaultClient must not be null");
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
Assert.notNull(properties, "VaultProperties must not be null");
|
||||
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
|
||||
Assert.notNull(genericBackendProperties, "VaultGenericBackendProperties must not be null");
|
||||
this.operations = operations;
|
||||
this.properties = properties;
|
||||
this.backendAccessors = backendAccessors;
|
||||
this.genericBackendProperties = genericBackendProperties;
|
||||
}
|
||||
|
||||
this.vaultClient = vaultClient;
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.properties = properties;
|
||||
this.backendAccessors = backendAccessors;
|
||||
this.genericBackendProperties = genericBackendProperties;
|
||||
}
|
||||
@Override
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
|
||||
@Override
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
CompositePropertySource propertySource = createCompositePropertySource((ConfigurableEnvironment) environment);
|
||||
initialize(propertySource);
|
||||
|
||||
CompositePropertySource propertySource = createCompositePropertySource((ConfigurableEnvironment) environment);
|
||||
initialize(propertySource);
|
||||
return propertySource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return propertySource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private List<String> buildContexts(ConfigurableEnvironment env) {
|
||||
|
||||
private List<String> buildContexts(ConfigurableEnvironment env) {
|
||||
String appName = env.getProperty("spring.application.name");
|
||||
List<String> profiles = Arrays.asList(env.getActiveProfiles());
|
||||
List<String> contexts = new ArrayList<>();
|
||||
|
||||
String appName = env.getProperty("spring.application.name");
|
||||
List<String> profiles = Arrays.asList(env.getActiveProfiles());
|
||||
List<String> contexts = new ArrayList<>();
|
||||
String defaultContext = genericBackendProperties.getDefaultContext();
|
||||
if (StringUtils.hasText(defaultContext)) {
|
||||
contexts.add(defaultContext);
|
||||
}
|
||||
|
||||
String defaultContext = genericBackendProperties.getDefaultContext();
|
||||
if (StringUtils.hasText(defaultContext)) {
|
||||
contexts.add(defaultContext);
|
||||
}
|
||||
addProfiles(contexts, defaultContext, profiles);
|
||||
|
||||
addProfiles(contexts, defaultContext, profiles);
|
||||
if (StringUtils.hasText(appName)) {
|
||||
|
||||
if (StringUtils.hasText(appName)) {
|
||||
if (!contexts.contains(appName)) {
|
||||
contexts.add(appName);
|
||||
}
|
||||
|
||||
if (!contexts.contains(appName)) {
|
||||
contexts.add(appName);
|
||||
}
|
||||
addProfiles(contexts, appName, profiles);
|
||||
}
|
||||
|
||||
addProfiles(contexts, appName, profiles);
|
||||
}
|
||||
Collections.reverse(contexts);
|
||||
return contexts;
|
||||
}
|
||||
|
||||
Collections.reverse(contexts);
|
||||
return contexts;
|
||||
}
|
||||
protected CompositePropertySource createCompositePropertySource(
|
||||
ConfigurableEnvironment environment) {
|
||||
|
||||
protected CompositePropertySource createCompositePropertySource(ConfigurableEnvironment environment) {
|
||||
CompositePropertySource propertySource = new CompositePropertySource("vault");
|
||||
|
||||
CompositePropertySource propertySource = new CompositePropertySource("vault");
|
||||
if (genericBackendProperties.isEnabled()) {
|
||||
|
||||
if (genericBackendProperties.isEnabled()) {
|
||||
List<String> contexts = buildContexts(environment);
|
||||
for (String propertySourceContext : contexts) {
|
||||
|
||||
List<String> contexts = buildContexts(environment);
|
||||
for (String propertySourceContext : contexts) {
|
||||
if (StringUtils.hasText(propertySourceContext)) {
|
||||
|
||||
if (StringUtils.hasText(propertySourceContext)) {
|
||||
VaultPropertySource vaultPropertySource = createVaultPropertySource(generic(
|
||||
genericBackendProperties.getBackend(), propertySourceContext));
|
||||
|
||||
VaultPropertySource vaultPropertySource = createVaultPropertySource(
|
||||
generic(genericBackendProperties.getBackend(), propertySourceContext));
|
||||
propertySource.addPropertySource(vaultPropertySource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
propertySource.addPropertySource(vaultPropertySource);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (SecureBackendAccessor backendAccessor : backendAccessors) {
|
||||
|
||||
for (SecureBackendAccessor backendAccessor : backendAccessors) {
|
||||
VaultPropertySource vaultPropertySource = createVaultPropertySource(backendAccessor);
|
||||
propertySource.addPropertySource(vaultPropertySource);
|
||||
}
|
||||
return propertySource;
|
||||
}
|
||||
|
||||
VaultPropertySource vaultPropertySource = createVaultPropertySource(backendAccessor);
|
||||
propertySource.addPropertySource(vaultPropertySource);
|
||||
}
|
||||
return propertySource;
|
||||
}
|
||||
protected void initialize(CompositePropertySource propertySource) {
|
||||
|
||||
protected void initialize(CompositePropertySource propertySource) {
|
||||
for (PropertySource<?> source : propertySource.getPropertySources()) {
|
||||
((VaultPropertySource) source).init();
|
||||
}
|
||||
}
|
||||
|
||||
for (PropertySource<?> source : propertySource.getPropertySources()) {
|
||||
((VaultPropertySource) source).init();
|
||||
}
|
||||
}
|
||||
private VaultPropertySource createVaultPropertySource(SecureBackendAccessor accessor) {
|
||||
return new VaultPropertySource(this.operations, this.properties, accessor);
|
||||
}
|
||||
|
||||
private VaultPropertySource createVaultPropertySource(SecureBackendAccessor accessor) {
|
||||
return new VaultPropertySource(this.vaultClient, this.clientAuthentication, this.properties, this.vaultState, accessor);
|
||||
}
|
||||
private void addProfiles(List<String> contexts, String baseContext,
|
||||
List<String> profiles) {
|
||||
|
||||
private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
|
||||
for (String profile : profiles) {
|
||||
String context = baseContext
|
||||
+ this.genericBackendProperties.getProfileSeparator() + profile;
|
||||
|
||||
for (String profile : profiles) {
|
||||
String context = baseContext + this.genericBackendProperties.getProfileSeparator() + profile;
|
||||
|
||||
if (!contexts.contains(context)) {
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!contexts.contains(context)) {
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultClientResponse;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This class encapsulates main Vault interaction. {@link VaultTemplate} will log into
|
||||
* Vault on initialization and use the token throughout the whole lifetime.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class VaultTemplate implements InitializingBean, VaultOperations {
|
||||
|
||||
private final VaultProperties properties;
|
||||
private final VaultClient client;
|
||||
private final ClientAuthentication clientAuthentication;
|
||||
private final transient VaultState vaultState = new VaultState();
|
||||
private final VaultSession vaultSession;
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultConfigTemplate} for the given {@link VaultProperties},
|
||||
* {@link VaultClient} and {@link ClientAuthentication}.
|
||||
*
|
||||
* @param properties must not be {@literal null}.
|
||||
* @param client must not be {@literal null}.
|
||||
* @param clientAuthentication must not be {@literal null}.
|
||||
*/
|
||||
public VaultTemplate(VaultProperties properties, VaultClient client,
|
||||
ClientAuthentication clientAuthentication) {
|
||||
|
||||
Assert.notNull(properties, "VaultProperties must not be null!");
|
||||
Assert.notNull(client, "VaultClient must not be null!");
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null!");
|
||||
|
||||
this.properties = properties;
|
||||
this.client = client;
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.vaultSession = new VaultSession() {
|
||||
@Override
|
||||
public VaultClientResponse read(URI uri) {
|
||||
return VaultTemplate.this.client.read(uri, getToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultClientResponse write(URI uri, Object entity) {
|
||||
return VaultTemplate.this.client.write(uri, entity, getToken());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
login();
|
||||
}
|
||||
|
||||
private void login() {
|
||||
vaultState.setToken(clientAuthentication.login());
|
||||
}
|
||||
|
||||
private VaultToken getToken() {
|
||||
|
||||
if (vaultState.getToken() == null) {
|
||||
login();
|
||||
}
|
||||
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultConfigOperations opsForConfig() {
|
||||
return new VaultConfigTemplate(this, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T doWithVault(String path, SessionCallback sessionCallback) {
|
||||
|
||||
Assert.notNull(sessionCallback, "SessionCallback must not be null!");
|
||||
|
||||
URI uri = client.buildUri(properties, path);
|
||||
return sessionCallback.doWithVault(uri, vaultSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T doWithVault(String pathTemplate, Map<String, ?> variables,
|
||||
SessionCallback sessionCallback) {
|
||||
|
||||
Assert.notNull(sessionCallback, "SessionCallback must not be null!");
|
||||
|
||||
URI uri = client.buildUri(properties, pathTemplate, variables);
|
||||
return sessionCallback.doWithVault(uri, vaultSession);
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,15 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.IpAddressUserId;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
@@ -33,8 +33,6 @@ import org.springframework.web.client.RestTemplate;
|
||||
*/
|
||||
public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegrationTests {
|
||||
|
||||
private ClientAuthentication clientAuthentication;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
@@ -56,17 +54,14 @@ public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegratio
|
||||
prepare().mapAppId(vaultProperties.getApplicationName());
|
||||
prepare().mapUserId(vaultProperties.getApplicationName(), userId);
|
||||
|
||||
RestTemplate restTemplate = TestRestTemplateFactory.create(vaultProperties);
|
||||
RestTemplate restTemplate = TestRestTemplateFactory
|
||||
.create(vaultProperties);
|
||||
|
||||
this.clientAuthentication = ClientAuthentication.appId(vaultProperties,
|
||||
restTemplate, userIdMechanism);
|
||||
this.vaultClient = new VaultClient(vaultProperties);
|
||||
this.vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
}
|
||||
ClientAuthentication clientAuthentication = ClientAuthentication.appId(
|
||||
vaultProperties, restTemplate, userIdMechanism);
|
||||
|
||||
@Override
|
||||
protected VaultToken createToken() {
|
||||
return clientAuthentication.login();
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
clientAuthentication).opsForConfig();
|
||||
}
|
||||
|
||||
private AppIdProperties configureAppIdProperties() {
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
|
||||
/**
|
||||
* Tests for fail fast option.
|
||||
@@ -32,7 +33,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
public class ApplicationFailFastTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
public void contextLoadsWithFailFast() {
|
||||
try {
|
||||
new SpringApplicationBuilder().sources(ApplicationFailFastTests.class).run(
|
||||
"--server.port=0", "--spring.cloud.vault.failFast=true",
|
||||
@@ -40,7 +41,14 @@ public class ApplicationFailFastTests {
|
||||
fail("failFast option did not produce an exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).hasMessageContaining("fail fast");
|
||||
assertThat(e.getMessage()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextLoadsWithoutFailFast() {
|
||||
new SpringApplicationBuilder().sources(ApplicationFailFastTests.class).run(
|
||||
"--server.port=0", "--spring.cloud.vault.failFast=false",
|
||||
"--spring.cloud.vault.port=9999");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,21 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.cloud.vault.config.SecureBackendAccessors.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.AbstractIntegrationTests;
|
||||
import org.springframework.cloud.vault.TestRestTemplateFactory;
|
||||
import org.springframework.cloud.vault.ClientAuthentication;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.cloud.vault.config.SecureBackendAccessors.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the generic secret backend.
|
||||
*
|
||||
@@ -38,21 +38,23 @@ import org.springframework.cloud.vault.util.Settings;
|
||||
public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
protected VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
protected VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
protected VaultConfigOperations configOperations;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
vaultProperties.setFailFast(false);
|
||||
prepare().writeSecret("app-name", (Map) createData());
|
||||
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
|
||||
|
||||
configOperations = new VaultTemplate(vaultProperties, prepare().newVaultClient(),
|
||||
ClientAuthentication.token(vaultProperties)).opsForConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnSecretsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient
|
||||
.read(generic("secret", "app-name"), createToken());
|
||||
Map<String, String> secretProperties = configOperations.read(generic("secret",
|
||||
"app-name"));
|
||||
|
||||
assertThat(secretProperties).containsAllEntriesOf(createExpectedMap());
|
||||
}
|
||||
@@ -60,28 +62,12 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
@Test
|
||||
public void shouldReturnNullIfNotFound() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient
|
||||
.read(generic("secret", "missing"), createToken());
|
||||
Map<String, String> secretProperties = configOperations.read(generic("secret",
|
||||
"missing"));
|
||||
|
||||
assertThat(secretProperties).isEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldFailOnFailFast() throws Exception {
|
||||
|
||||
vaultProperties.setFailFast(true);
|
||||
vaultClient.read(generic("secret", "missing"), createToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden by subclasses.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected VaultToken createToken() {
|
||||
return Settings.token();
|
||||
}
|
||||
|
||||
private Map<String, Object> createData() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("string", "value");
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.cloud.vault;
|
||||
|
||||
import static org.springframework.cloud.vault.VaultClient.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -51,10 +51,8 @@ public class VaultBootstrapConfiguration {
|
||||
RestTemplate restTemplate = new RestTemplate(
|
||||
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
|
||||
|
||||
VaultProperties vaultProperties = vaultProperties();
|
||||
|
||||
VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
vaultClient.setRest(restTemplate);
|
||||
VaultClient vaultClient = new VaultClient();
|
||||
vaultClient.setRestTemplate(restTemplate);
|
||||
|
||||
return vaultClient;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
package org.springframework.cloud.vault;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -30,120 +28,155 @@ import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
/**
|
||||
* Vault client. This client reads data from Vault secret backends and can authenticate
|
||||
* with Vault to obtain an access token.
|
||||
* Vault client. This client reads data from Vault.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CommonsLog
|
||||
public class VaultClient {
|
||||
|
||||
public static final String API_VERSION = "v1";
|
||||
public static final String VAULT_TOKEN = "X-Vault-Token";
|
||||
|
||||
@Setter
|
||||
private RestTemplate rest = new RestTemplate();
|
||||
@Getter
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private final VaultProperties properties;
|
||||
public VaultClient() {
|
||||
this(new RestTemplate());
|
||||
}
|
||||
|
||||
public VaultClient(VaultProperties properties) {
|
||||
|
||||
Assert.notNull(properties, "VaultProperties must not be null");
|
||||
|
||||
this.properties = properties;
|
||||
public VaultClient(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read secrets using the given {@link SecureBackendAccessor} and {@link VaultToken}.
|
||||
* Read data from the given Vault {@code uri} using the {@link VaultToken}.
|
||||
*
|
||||
* @param secureBackendAccessor must not be {@literal null}.
|
||||
* @param uri must not be {@literal null}.
|
||||
* @param vaultToken must not be {@literal null}.
|
||||
* @return A {@link Map} containing properties.
|
||||
*/
|
||||
public Map<String, String> read(SecureBackendAccessor secureBackendAccessor,
|
||||
VaultToken vaultToken) {
|
||||
public VaultClientResponse read(URI uri, VaultToken vaultToken) {
|
||||
|
||||
Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be empty!");
|
||||
Assert.notNull(uri, "URI must not be empty!");
|
||||
Assert.notNull(vaultToken, "Vault Token must not be null!");
|
||||
|
||||
String url = buildUrl();
|
||||
return exchange(uri, HttpMethod.GET, new HttpEntity<>(createHeaders(vaultToken)));
|
||||
}
|
||||
|
||||
HttpHeaders headers = createHeaders(vaultToken);
|
||||
Exception error = null;
|
||||
String errorBody = null;
|
||||
HttpStatus status = null;
|
||||
/**
|
||||
* Write data to the given Vault {@code uri} using the {@link VaultToken}.
|
||||
*
|
||||
* @param uri must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return A {@link Map} containing properties.
|
||||
*/
|
||||
public VaultClientResponse write(URI uri, Object entity) {
|
||||
|
||||
URI uri = this.rest.getUriTemplateHandler().expand(url,
|
||||
secureBackendAccessor.variables());
|
||||
log.info(String.format("Fetching config from server at: %s", uri));
|
||||
Assert.notNull(uri, "URI must not be empty!");
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
|
||||
return exchange(uri, HttpMethod.POST, new HttpEntity<>(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the given Vault {@code uri} using the {@link VaultToken}.
|
||||
*
|
||||
* @param uri must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param vaultToken must not be {@literal null}.
|
||||
* @return A {@link Map} containing properties.
|
||||
*/
|
||||
public VaultClientResponse write(URI uri, Object entity, VaultToken vaultToken) {
|
||||
|
||||
Assert.notNull(uri, "URI must not be empty!");
|
||||
Assert.notNull(entity, "Vault Token must not be null!");
|
||||
Assert.notNull(vaultToken, "Vault Token must not be null!");
|
||||
|
||||
return exchange(uri, HttpMethod.POST, new HttpEntity<>(entity,
|
||||
createHeaders(vaultToken)));
|
||||
}
|
||||
|
||||
private VaultClientResponse exchange(URI uri, HttpMethod httpMethod,
|
||||
HttpEntity<?> httpEntity) {
|
||||
|
||||
Assert.notNull(uri, "URI must not be empty!");
|
||||
|
||||
try {
|
||||
ResponseEntity<VaultResponse> response = this.rest.exchange(uri,
|
||||
HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class);
|
||||
ResponseEntity<VaultResponse> response = this.restTemplate.exchange(uri,
|
||||
httpMethod, httpEntity, VaultResponse.class);
|
||||
|
||||
status = response.getStatusCode();
|
||||
if (status == HttpStatus.OK) {
|
||||
if (response.getBody().getData() != null) {
|
||||
return secureBackendAccessor
|
||||
.transformProperties(response.getBody().getData());
|
||||
}
|
||||
}
|
||||
return VaultClientResponse.of(response.getBody(), response.getStatusCode(),
|
||||
uri, response.getStatusCode().getReasonPhrase());
|
||||
}
|
||||
catch (HttpServerErrorException | HttpClientErrorException e) {
|
||||
|
||||
if (MediaType.APPLICATION_JSON
|
||||
.includes(e.getResponseHeaders().getContentType())) {
|
||||
errorBody = e.getResponseBodyAsString();
|
||||
String message = e.getResponseBodyAsString();
|
||||
|
||||
if (MediaType.APPLICATION_JSON.includes(e.getResponseHeaders()
|
||||
.getContentType())) {
|
||||
message = VaultErrorMessage.getError(message);
|
||||
}
|
||||
|
||||
status = e.getStatusCode();
|
||||
error = e;
|
||||
return VaultClientResponse.of(null, e.getStatusCode(), uri, message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (status == HttpStatus.NOT_FOUND) {
|
||||
log.info(String.format("Could not locate PropertySource: %s",
|
||||
"key not found"));
|
||||
}
|
||||
else if (status != null) {
|
||||
log.warn(String.format("Could not locate PropertySource: Status %d %s",
|
||||
status.value(), getErrorMessage(error, errorBody)));
|
||||
}
|
||||
else {
|
||||
log.warn(String.format("Could not locate PropertySource: %s",
|
||||
(getErrorMessage(error, errorBody))));
|
||||
}
|
||||
|
||||
if (properties.isFailFast()) {
|
||||
throw new IllegalStateException(
|
||||
"Could not locate PropertySource and the fail fast property is set, failing",
|
||||
error);
|
||||
}
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
private String getErrorMessage(Exception error, String errorBody) {
|
||||
return errorBody == null ? error == null ? "unknown reason" : error.getMessage()
|
||||
: VaultErrorMessage.getError(errorBody);
|
||||
/**
|
||||
* Build the Vault {@link URI} based on the given {@link VaultProperties} and
|
||||
* {@code path}.
|
||||
*
|
||||
* @param properties must not be {@literal null}.
|
||||
* @param path must not be empty or {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static URI buildUri(VaultProperties properties, String path) {
|
||||
return URI.create(createBaseUrlWithPath(properties, path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Vault {@link URI} based on the given {@link VaultProperties} and
|
||||
* {@code pathTemplate}. URI template variables will be expanded using
|
||||
* {@code uriVariables}.
|
||||
*
|
||||
* @param properties must not be {@literal null}.
|
||||
* @param pathTemplate must not be empty or {@literal null}.
|
||||
* @param uriVariables must not be {@literal null}.
|
||||
* @see org.springframework.web.util.UriComponentsBuilder
|
||||
* @return
|
||||
*/
|
||||
public URI buildUri(VaultProperties properties, String pathTemplate,
|
||||
Map<String, ?> uriVariables) {
|
||||
|
||||
Assert.notNull(properties, "VaultProperties must not be null!");
|
||||
Assert.hasText(pathTemplate, "Path must not be empty!");
|
||||
Assert.notNull(properties, "Vault Token must not be null!");
|
||||
|
||||
return restTemplate.getUriTemplateHandler().expand(
|
||||
createBaseUrlWithPath(properties, pathTemplate), uriVariables);
|
||||
}
|
||||
|
||||
private HttpHeaders createHeaders(VaultToken vaultToken) {
|
||||
|
||||
Assert.notNull(vaultToken, "Vault Token must not be null!");
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(VAULT_TOKEN, vaultToken.getToken());
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String buildUrl() {
|
||||
return String.format("%s://%s:%s/%s/{backend}/{key}", this.properties.getScheme(),
|
||||
this.properties.getHost(), this.properties.getPort(), API_VERSION);
|
||||
private static String createBaseUrlWithPath(VaultProperties properties, String path) {
|
||||
|
||||
Assert.notNull(properties, "VaultProperties must not be null!");
|
||||
Assert.hasText(path, "Path must not be empty!");
|
||||
|
||||
return String.format("%s://%s:%s/%s/%s", properties.getScheme(),
|
||||
properties.getHost(), properties.getPort(), API_VERSION, path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Encapsulates the client response used in {@link VaultClient}. Consists of the body,
|
||||
* status code the location and a message. The {@code body} is empty for all
|
||||
* non-successful results.
|
||||
*
|
||||
* This class is immutable.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value(staticConstructor = "of")
|
||||
public class VaultClientResponse {
|
||||
|
||||
private VaultResponse body;
|
||||
private HttpStatus statusCode;
|
||||
private URI uri;
|
||||
private String message;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {@literal true} if the request was completed successfully.
|
||||
*/
|
||||
public boolean isSuccessful() {
|
||||
return body != null && statusCode.is2xxSuccessful();
|
||||
}
|
||||
}
|
||||
@@ -15,20 +15,20 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using various UserIds.
|
||||
@@ -55,11 +55,10 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
|
||||
|
||||
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod(
|
||||
AppIdProperties.IP_ADDRESS, "myapp");
|
||||
RestTemplate restTemplate = TestRestTemplateFactory
|
||||
.create(Settings.createVaultProperties());
|
||||
|
||||
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
|
||||
vaultProperties, restTemplate, new IpAddressUserId());
|
||||
vaultProperties, TestRestTemplateFactory.create(vaultProperties),
|
||||
new IpAddressUserId());
|
||||
|
||||
assertThat(clientAuthentication.login()).isNotNull();
|
||||
}
|
||||
@@ -70,11 +69,9 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
|
||||
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod("my-user-id",
|
||||
"myapp");
|
||||
|
||||
RestTemplate restTemplate = TestRestTemplateFactory
|
||||
.create(Settings.createVaultProperties());
|
||||
|
||||
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
|
||||
vaultProperties, restTemplate, new StaticUserId(vaultProperties));
|
||||
vaultProperties, TestRestTemplateFactory.create(vaultProperties),
|
||||
new StaticUserId(vaultProperties));
|
||||
|
||||
assertThat(clientAuthentication.login()).isNotNull();
|
||||
}
|
||||
@@ -85,11 +82,9 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
|
||||
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod(
|
||||
AppIdProperties.MAC_ADDRESS, "myapp");
|
||||
|
||||
RestTemplate restTemplate = TestRestTemplateFactory
|
||||
.create(Settings.createVaultProperties());
|
||||
|
||||
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
|
||||
vaultProperties, restTemplate, new MacAddressUserId(vaultProperties));
|
||||
vaultProperties, TestRestTemplateFactory.create(vaultProperties),
|
||||
new MacAddressUserId(vaultProperties));
|
||||
|
||||
assertThat(clientAuthentication.login()).isNotNull();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@@ -45,12 +46,13 @@ public class TestRestTemplateFactory {
|
||||
initializeClientHttpRequestFactory(vaultProperties);
|
||||
|
||||
TestRestTemplate testRestTemplate = new TestRestTemplate();
|
||||
testRestTemplate.setErrorHandler(new DefaultResponseErrorHandler());
|
||||
testRestTemplate.setRequestFactory(factoryCache.get());
|
||||
|
||||
return testRestTemplate;
|
||||
}
|
||||
|
||||
protected static void initializeClientHttpRequestFactory(
|
||||
private static void initializeClientHttpRequestFactory(
|
||||
VaultProperties vaultProperties) throws Exception {
|
||||
|
||||
if (factoryCache.get() != null) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
@@ -76,6 +77,15 @@ public class PrepareVault {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultClient}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public VaultClient newVaultClient() {
|
||||
return new VaultClient(restTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Vault and unseal the vault.
|
||||
*
|
||||
@@ -97,8 +107,8 @@ public class PrepareVault {
|
||||
new HttpEntity<>(initializeVault), VaultInitialized.class, parameters);
|
||||
|
||||
if (!initResponse.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot initialize vault: " + initResponse.toString());
|
||||
throw new IllegalStateException("Cannot initialize vault: "
|
||||
+ initResponse.toString());
|
||||
}
|
||||
VaultInitialized initialized = initResponse.getBody();
|
||||
|
||||
@@ -144,8 +154,8 @@ public class PrepareVault {
|
||||
parameters);
|
||||
|
||||
if (!createTokenResponse.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot create token: " + createTokenResponse.toString());
|
||||
throw new IllegalStateException("Cannot create token: "
|
||||
+ createTokenResponse.toString());
|
||||
}
|
||||
|
||||
AuthToken authToken = createTokenResponse.getBody().getAuth();
|
||||
@@ -162,11 +172,19 @@ public class PrepareVault {
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate
|
||||
.getForEntity(SEAL_STATUS_URL_TEMPLATE, String.class, parameters);
|
||||
ResponseEntity<String> exchange = null;
|
||||
try {
|
||||
exchange = restTemplate.getForEntity(SEAL_STATUS_URL_TEMPLATE, String.class,
|
||||
parameters);
|
||||
|
||||
if (exchange.getStatusCode().is2xxSuccessful()) {
|
||||
return true;
|
||||
if (exchange.getStatusCode().is2xxSuccessful()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (HttpStatusCodeException e) {
|
||||
if (e.getStatusCode().is4xxClientError()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (exchange.getStatusCode().is4xxClientError()) {
|
||||
@@ -197,8 +215,8 @@ public class PrepareVault {
|
||||
parameters);
|
||||
|
||||
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot create mount auth backend: " + responseEntity.toString());
|
||||
throw new IllegalStateException("Cannot create mount auth backend: "
|
||||
+ responseEntity.toString());
|
||||
}
|
||||
|
||||
responseEntity.getBody();
|
||||
@@ -239,8 +257,8 @@ public class PrepareVault {
|
||||
parameters);
|
||||
|
||||
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot create mount secret backend: " + responseEntity.toString());
|
||||
throw new IllegalStateException("Cannot create mount secret backend: "
|
||||
+ responseEntity.toString());
|
||||
}
|
||||
|
||||
responseEntity.getBody();
|
||||
@@ -267,8 +285,8 @@ public class PrepareVault {
|
||||
urlTemplate, HttpMethod.GET, entity, MAP_OF_MAPS_TYPE, parameters);
|
||||
|
||||
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot enumerate mounts: " + responseEntity.toString());
|
||||
throw new IllegalStateException("Cannot enumerate mounts: "
|
||||
+ responseEntity.toString());
|
||||
}
|
||||
|
||||
Map<String, Object> body = responseEntity.getBody();
|
||||
@@ -332,8 +350,8 @@ public class PrepareVault {
|
||||
parameters);
|
||||
|
||||
if (!exchange.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Cannot write to %s: %s", path, exchange.getBody()));
|
||||
throw new IllegalStateException(String.format("Cannot write to %s: %s", path,
|
||||
exchange.getBody()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,8 +386,8 @@ public class PrepareVault {
|
||||
appIdData.put("value", "root"); // policy
|
||||
appIdData.put("display_name", "this is my test application");
|
||||
|
||||
write(String.format("auth/%s/map/app-id/%s",
|
||||
vaultProperties.getAppId().getAppIdPath(), appId), appIdData);
|
||||
write(String.format("auth/%s/map/app-id/%s", vaultProperties.getAppId()
|
||||
.getAppIdPath(), appId), appIdData);
|
||||
}
|
||||
|
||||
private HttpHeaders authenticatedHeaders() {
|
||||
|
||||
Reference in New Issue
Block a user