diff --git a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java index 12eb29a4..fa513d44 100644 --- a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java +++ b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java @@ -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; diff --git a/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java b/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java index ada04a31..b09d52db 100644 --- a/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java +++ b/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java @@ -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 secretProperties = vaultClient.read(forAws(aws), - Settings.token()); + Map secretProperties = configOperations.read(forAws(aws)); assertThat(secretProperties).containsKeys("cloud.aws.credentials.accessKey", "cloud.aws.credentials.secretKey"); diff --git a/spring-cloud-vault-config-consul/src/main/java/org/springframework/cloud/vault/config/consul/VaultConfigConsulBootstrapConfiguration.java b/spring-cloud-vault-config-consul/src/main/java/org/springframework/cloud/vault/config/consul/VaultConfigConsulBootstrapConfiguration.java index 456cb397..c6b44b5d 100644 --- a/spring-cloud-vault-config-consul/src/main/java/org/springframework/cloud/vault/config/consul/VaultConfigConsulBootstrapConfiguration.java +++ b/spring-cloud-vault-config-consul/src/main/java/org/springframework/cloud/vault/config/consul/VaultConfigConsulBootstrapConfiguration.java @@ -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; diff --git a/spring-cloud-vault-config-consul/src/test/java/org/springframework/cloud/vault/config/consul/ConsulSecretIntegrationTests.java b/spring-cloud-vault-config-consul/src/test/java/org/springframework/cloud/vault/config/consul/ConsulSecretIntegrationTests.java index c97a84d4..d7c7d19f 100644 --- a/spring-cloud-vault-config-consul/src/test/java/org/springframework/cloud/vault/config/consul/ConsulSecretIntegrationTests.java +++ b/spring-cloud-vault-config-consul/src/test/java/org/springframework/cloud/vault/config/consul/ConsulSecretIntegrationTests.java @@ -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 secretProperties = vaultClient.read(forConsul(consul), - Settings.token()); + Map secretProperties = configOperations.read(forConsul(consul)); assertThat(secretProperties).containsKeys("spring.cloud.consul.token"); } diff --git a/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultConfigDatabaseBootstrapConfiguration.java b/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultConfigDatabaseBootstrapConfiguration.java index e529438b..c269b10c 100644 --- a/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultConfigDatabaseBootstrapConfiguration.java +++ b/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultConfigDatabaseBootstrapConfiguration.java @@ -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; diff --git a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/CassandraSecretIntegrationTests.java b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/CassandraSecretIntegrationTests.java index 028e10f6..b0d410e0 100644 --- a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/CassandraSecretIntegrationTests.java +++ b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/CassandraSecretIntegrationTests.java @@ -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 secretProperties = vaultClient.read(forDatabase(cassandra), - Settings.token()); + Map secretProperties = configOperations + .read(forDatabase(cassandra)); assertThat(secretProperties).containsKeys("spring.data.cassandra.username", "spring.data.cassandra.password"); diff --git a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MySqlSecretIntegrationTests.java b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MySqlSecretIntegrationTests.java index 5b34971a..67abefef 100644 --- a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MySqlSecretIntegrationTests.java +++ b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MySqlSecretIntegrationTests.java @@ -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 secretProperties = vaultClient.read(forDatabase(mySql), - Settings.token()); + Map secretProperties = configOperations.read(forDatabase(mySql)); assertThat(secretProperties).containsKeys("spring.datasource.username", "spring.datasource.password"); } - } diff --git a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/PostgreSqlSecretIntegrationTests.java b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/PostgreSqlSecretIntegrationTests.java index 084a0df7..c9745663 100644 --- a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/PostgreSqlSecretIntegrationTests.java +++ b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/PostgreSqlSecretIntegrationTests.java @@ -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 secretProperties = vaultClient.read(forDatabase(postgreSql), - Settings.token()); + Map secretProperties = configOperations + .read(forDatabase(postgreSql)); assertThat(secretProperties).containsKeys("spring.datasource.username", "spring.datasource.password"); diff --git a/spring-cloud-vault-config-rabbitmq/src/main/java/org/springframework/cloud/vault/config/rabbitmq/VaultConfigRabbitMqBootstrapConfiguration.java b/spring-cloud-vault-config-rabbitmq/src/main/java/org/springframework/cloud/vault/config/rabbitmq/VaultConfigRabbitMqBootstrapConfiguration.java index 4c060065..3d064e5a 100644 --- a/spring-cloud-vault-config-rabbitmq/src/main/java/org/springframework/cloud/vault/config/rabbitmq/VaultConfigRabbitMqBootstrapConfiguration.java +++ b/spring-cloud-vault-config-rabbitmq/src/main/java/org/springframework/cloud/vault/config/rabbitmq/VaultConfigRabbitMqBootstrapConfiguration.java @@ -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; diff --git a/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java b/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java index af242183..83e6edda 100644 --- a/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java +++ b/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java @@ -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 secretProperties = vaultClient.read(forRabbitMq(rabbitmq), - Settings.token()); + Map secretProperties = configOperations + .read(forRabbitMq(rabbitmq)); assertThat(secretProperties).containsKeys("spring.rabbitmq.username", "spring.rabbitmq.password"); diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/SecureBackendAccessor.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessor.java similarity index 95% rename from spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/SecureBackendAccessor.java rename to spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessor.java index 2855b6f7..437a07d3 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/SecureBackendAccessor.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.vault; +package org.springframework.cloud.vault.config; import java.util.Map; diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessorFactory.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessorFactory.java index bc73b26a..c90818d7 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessorFactory.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessorFactory.java @@ -15,7 +15,6 @@ */ package org.springframework.cloud.vault.config; -import org.springframework.cloud.vault.SecureBackendAccessor; import org.springframework.cloud.vault.VaultSecretBackend; /** diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessors.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessors.java index 76bb9879..48beb94c 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessors.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendAccessors.java @@ -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; /** diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendFactories.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendFactories.java index 1d67485f..24a98c6a 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendFactories.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecureBackendFactories.java @@ -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; diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java index 6d99551c..9e7016ac 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java @@ -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 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 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(); } } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigOperations.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigOperations.java new file mode 100644 index 00000000..3381f2fe --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigOperations.java @@ -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 read(SecureBackendAccessor secureBackendAccessor); + +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigTemplate.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigTemplate.java new file mode 100644 index 00000000..232921c7 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigTemplate.java @@ -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 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); + } + } + +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultOperations.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultOperations.java new file mode 100644 index 00000000..b801590c --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultOperations.java @@ -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 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 doWithVault(String pathTemplate, Map 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 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); + } +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java index 4567058b..6fcbcea0 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java @@ -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 { +class VaultPropertySource extends EnumerablePropertySource { private final VaultProperties vaultProperties; private final SecureBackendAccessor secureBackendAccessor; private final Map 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 { public void init() { try { - Map values = this.source.read(this.secureBackendAccessor, - obtainToken()); + Map values = this.source.read(this.secureBackendAccessor); if (values != null) { this.properties.putAll(values); } @@ -102,42 +86,6 @@ class VaultPropertySource extends EnumerablePropertySource { } } - 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 { Set strings = this.properties.keySet(); return strings.toArray(new String[strings.size()]); } -} \ No newline at end of file +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java index 41f5faec..7e83765f 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java @@ -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 backendAccessors; + private final VaultConfigOperations operations; + private final VaultProperties properties; + private final VaultGenericBackendProperties genericBackendProperties; + private final Collection 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 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 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 buildContexts(ConfigurableEnvironment env) { - private List buildContexts(ConfigurableEnvironment env) { + String appName = env.getProperty("spring.application.name"); + List profiles = Arrays.asList(env.getActiveProfiles()); + List contexts = new ArrayList<>(); - String appName = env.getProperty("spring.application.name"); - List profiles = Arrays.asList(env.getActiveProfiles()); - List 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 contexts = buildContexts(environment); + for (String propertySourceContext : contexts) { - List 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 contexts, String baseContext, + List profiles) { - private void addProfiles(List contexts, String baseContext, List 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); - } - } - } -} \ No newline at end of file + if (!contexts.contains(context)) { + contexts.add(context); + } + } + } +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java new file mode 100644 index 00000000..bd41cf86 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java @@ -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 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 doWithVault(String pathTemplate, Map variables, + SessionCallback sessionCallback) { + + Assert.notNull(sessionCallback, "SessionCallback must not be null!"); + + URI uri = client.buildUri(properties, pathTemplate, variables); + return sessionCallback.doWithVault(uri, vaultSession); + } +} diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java index da78f53f..9283a060 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java @@ -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() { diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ApplicationFailFastTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ApplicationFailFastTests.java index 8dda78f6..f2bc280f 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ApplicationFailFastTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ApplicationFailFastTests.java @@ -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(); } } -} \ No newline at end of file + + @Test + public void contextLoadsWithoutFailFast() { + new SpringApplicationBuilder().sources(ApplicationFailFastTests.class).run( + "--server.port=0", "--spring.cloud.vault.failFast=false", + "--spring.cloud.vault.port=9999"); + } +} diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretIntegrationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretIntegrationTests.java index f82dc21f..4fa141bc 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretIntegrationTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretIntegrationTests.java @@ -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 secretProperties = vaultClient - .read(generic("secret", "app-name"), createToken()); + Map 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 secretProperties = vaultClient - .read(generic("secret", "missing"), createToken()); + Map 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 createData() { Map data = new HashMap<>(); data.put("string", "value"); diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java index 292c6f95..31ad845b 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java @@ -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; diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java index 3eae7de1..0536227b 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java @@ -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; } diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java index c91c2f24..35fd2400 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java @@ -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 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 response = this.rest.exchange(uri, - HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class); + ResponseEntity 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 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); } + } diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClientResponse.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClientResponse.java new file mode 100644 index 00000000..e29065fe --- /dev/null +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClientResponse.java @@ -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(); + } +} diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java index 707645b5..337cbf34 100644 --- a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java @@ -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(); } diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java index 4d7c66bf..b7841e6c 100644 --- a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java @@ -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) { diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/PrepareVault.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/PrepareVault.java index 22c0f1d5..fb444d00 100644 --- a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/PrepareVault.java +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/PrepareVault.java @@ -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 parameters = parameters(vaultProperties); - ResponseEntity exchange = restTemplate - .getForEntity(SEAL_STATUS_URL_TEMPLATE, String.class, parameters); + ResponseEntity 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 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() {