diff --git a/.travis.yml b/.travis.yml index 066a0f68..31b3a2c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,15 @@ services: - mysql - postgresql - rabbitmq + - mongodb + +addons: + apt: + sources: + - mongodb-3.0-precise + packages: + - mongodb-org-server + - mongodb-org-shell jdk: - oraclejdk8 @@ -26,6 +35,8 @@ before_script: - mysql -e "CREATE USER 'spring' IDENTIFIED by 'vault';" - mysql -uroot -e "GRANT ALL PRIVILEGES ON *.* TO 'spring'@'%' WITH GRANT OPTION;"; - psql -U postgres -c "CREATE ROLE spring WITH LOGIN PASSWORD 'vault' CREATEROLE CREATEUSER;" + - |- + mongo admin --eval "db.createUser({user: 'spring', pwd:'vault', roles:['root']});" - sleep 30 # wait until Cassandra is up - apache-cassandra-2.2.6/bin/cqlsh localhost -u cassandra -p cassandra -e "CREATE USER 'spring' WITH PASSWORD 'vault' SUPERUSER" diff --git a/README.adoc b/README.adoc index 455fa927..6a7a5d32 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ // Do not edit this file (e.g. go instead to docs/src/main/asciidoc) -Spring Cloud Vault Config provides client-side support for externalized configuration in a distributed system. With https://www.vaultproject.io[Hashicorp's Vault] you have a central place to manage external secret properties for applications across all environments. Vault can manage static and dynamic secrets such as username/password for remote applications/resources and provide credentials for external services such as MySQL, PostgreSQL, Apache Cassandra, Consul, AWS and more. +Spring Cloud Vault Config provides client-side support for externalized configuration in a distributed system. With https://www.vaultproject.io[Hashicorp's Vault] you have a central place to manage external secret properties for applications across all environments. Vault can manage static and dynamic secrets such as username/password for remote applications/resources and provide credentials for external services such as MySQL, PostgreSQL, Apache Cassandra, MongoDB, Consul, AWS and more. == Features @@ -9,8 +9,9 @@ Spring Cloud Vault Config provides client-side support for externalized configur Specifically for Spring applications: -* Bind to the Config Server and initialize Spring `Environment` with remote property sources +* Retrieve secrets from Vault and initialize Spring `Environment` with remote property sources * Obtain secrets secured with SSL +* Generate credentials for MySQL, PostgreSQL, Apache Cassandra, MongoDB, Consul, AWS, and RabbitMQ. * https://www.vaultproject.io/docs/auth/token.html[Token], https://www.vaultproject.io/docs/auth/app-id.html[AppId] authentication, and https://www.vaultproject.io/docs/auth/aws-ec2.html[AWS-EC2] authentication * Bootstrap application context: a parent context for the main application that can be trained to do anything @@ -37,7 +38,7 @@ $ src/test/bash/install_vault.sh $ src/test/bash/create_certificates.sh ---- -NOTE: `create_certificates.sh` creates certificates in `work/ca` and a JKS truststore `work/keystore.jsk`. If you want to run Spring Cloud Vault using this quickstart guide you need to configure the truststore the `spring.cloud.vault.ssl.trust-store` property to `file:work/keystore.jks`. +NOTE: `create_certificates.sh` creates certificates in `work/ca` and a JKS truststore `work/keystore.jks`. If you want to run Spring Cloud Vault using this quickstart guide you need to configure the truststore the `spring.cloud.vault.ssl.trust-store` property to `file:work/keystore.jks`. *Start Vault server* diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index ba183f35..842761bb 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -7,8 +7,9 @@ include::intro.adoc[] Specifically for Spring applications: -* Bind to the Config Server and initialize Spring `Environment` with remote property sources +* Retrieve secrets from Vault and initialize Spring `Environment` with remote property sources * Obtain secrets secured with SSL +* Generate credentials for MySQL, PostgreSQL, Apache Cassandra, MongoDB, Consul, AWS, and RabbitMQ. * https://www.vaultproject.io/docs/auth/token.html[Token], https://www.vaultproject.io/docs/auth/app-id.html[AppId] authentication, and https://www.vaultproject.io/docs/auth/aws-ec2.html[AWS-EC2] authentication * Bootstrap application context: a parent context for the main application that can be trained to do anything diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index 34685c17..9ed7bc58 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -1,2 +1,2 @@ -Spring Cloud Vault Config provides client-side support for externalized configuration in a distributed system. With https://www.vaultproject.io[Hashicorp's Vault] you have a central place to manage external secret properties for applications across all environments. Vault can manage static and dynamic secrets such as username/password for remote applications/resources and provide credentials for external services such as MySQL, PostgreSQL, Apache Cassandra, Consul, AWS and more. \ No newline at end of file +Spring Cloud Vault Config provides client-side support for externalized configuration in a distributed system. With https://www.vaultproject.io[Hashicorp's Vault] you have a central place to manage external secret properties for applications across all environments. Vault can manage static and dynamic secrets such as username/password for remote applications/resources and provide credentials for external services such as MySQL, PostgreSQL, Apache Cassandra, MongoDB, Consul, AWS and more. \ No newline at end of file diff --git a/docs/src/main/asciidoc/spring-cloud-vault-config.adoc b/docs/src/main/asciidoc/spring-cloud-vault-config.adoc index defdd7be..517fae0d 100644 --- a/docs/src/main/asciidoc/spring-cloud-vault-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-vault-config.adoc @@ -490,6 +490,7 @@ mechanism to more easily roll keys. Spring Cloud Vault integrates with these backends: * <> +* <> * <> * <> @@ -547,6 +548,40 @@ spring.cloud.vault: See also: https://www.vaultproject.io/docs/secrets/cassandra/index.html[Vault Documentation: Setting up Apache Cassandra with Vault] +[[vault-client-database-mongodb]] +=== MongoDB + +Spring Cloud Vault can obtain credentials for MongoDB. +The integration can be enabled by setting +`spring.cloud.vault.mongodb.enabled=true` (default `false`) and +providing the role name with `spring.cloud.vault.mongodb.role=…`. + +Username and password are stored in `spring.data.mongodb.username` +and `spring.data.mongodb.password` so using Spring Boot will +pick up the generated credentials without further configuration. +You can configure the property names by setting +`spring.cloud.vault.mongodb.username-property` and +`spring.cloud.vault.mongodb.password-property`. + +[source,yaml] +---- +spring.cloud.vault: + mongodb: + enabled: true + role: readonly + backend: mongodb + username-property: spring.data.mongodb.username + password-property: spring.data.mongodb.password +---- + +* `enabled` setting this value to `true` enables the MongodB backend config usage +* `role` sets the role name of the MongoDB role definition +* `backend` sets the path of the MongoDB mount to use +* `username-property` sets the property name in which the MongoDB username is stored +* `password-property` sets the property name in which the MongoDB password is stored + +See also: https://www.vaultproject.io/docs/secrets/mongodb/index.html[Vault Documentation: Setting up MongoDB with Vault] + [[vault-client-database-mysql]] === MySQL diff --git a/spring-cloud-vault-config-databases/pom.xml b/spring-cloud-vault-config-databases/pom.xml index 2d6ffe22..f4800c32 100644 --- a/spring-cloud-vault-config-databases/pom.xml +++ b/spring-cloud-vault-config-databases/pom.xml @@ -33,6 +33,12 @@ test + + org.springframework.boot + spring-boot-starter-data-mongodb + test + + io.netty netty-all @@ -65,6 +71,13 @@ 2.1.10 test + + + org.mongodb + mongodb-driver-core + 3.2.2 + test + 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 ad4dcdfd..829ea1e4 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 @@ -30,7 +30,9 @@ import org.springframework.util.Assert; * @author Mark Paluch */ @Configuration -@EnableConfigurationProperties +@EnableConfigurationProperties({ VaultMySqlProperties.class, + VaultPostgreSqlProperties.class, VaultCassandraProperties.class, + VaultMongoProperties.class }) public class VaultConfigDatabaseBootstrapConfiguration { @Bean @@ -38,21 +40,6 @@ public class VaultConfigDatabaseBootstrapConfiguration { return new DatabaseSecureBackendAccessorFactory(); } - @Bean - public VaultMySqlProperties mySqlProperties() { - return new VaultMySqlProperties(); - } - - @Bean - public VaultPostgreSqlProperties postgreSqlProperties() { - return new VaultPostgreSqlProperties(); - } - - @Bean - public VaultCassandraProperties cassandraProperties() { - return new VaultCassandraProperties(); - } - static class DatabaseSecureBackendAccessorFactory implements SecureBackendAccessorFactory { @@ -102,7 +89,7 @@ public class VaultConfigDatabaseBootstrapConfiguration { public Map transformProperties( Map input) { - Map result = new HashMap(); + Map result = new HashMap<>(); result.put(properties.getUsernameProperty(), input.get("username")); result.put(properties.getPasswordProperty(), input.get("password")); diff --git a/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultMongoProperties.java b/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultMongoProperties.java new file mode 100644 index 00000000..b8d5742a --- /dev/null +++ b/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultMongoProperties.java @@ -0,0 +1,59 @@ +/* + * 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.databases; + +import org.hibernate.validator.constraints.NotEmpty; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Data; + +/** + * Configuration properties for Vault using the MongoDB integration. + * + * @author Mark Paluch + */ +@ConfigurationProperties("spring.cloud.vault.mongodb") +@Data +public class VaultMongoProperties implements DatabaseSecretProperties { + + /** + * Enable mongodb backend usage. + */ + private boolean enabled = false; + + /** + * Role name for credentials. + */ + private String role; + + /** + * Cassandra backend path. + */ + @NotEmpty + private String backend = "mongodb"; + + /** + * Target property for the obtained username. + */ + @NotEmpty + private String usernameProperty = "spring.data.mongodb.username"; + + /** + * Target property for the obtained password. + */ + @NotEmpty + private String passwordProperty = "spring.data.mongodb.password"; +} diff --git a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MongoSecretIntegrationTests.java b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MongoSecretIntegrationTests.java new file mode 100644 index 00000000..da8e976f --- /dev/null +++ b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/MongoSecretIntegrationTests.java @@ -0,0 +1,97 @@ +/* + * 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.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.config.VaultConfigOperations; +import org.springframework.cloud.vault.config.VaultConfigTemplate; +import org.springframework.cloud.vault.config.VaultProperties; +import org.springframework.cloud.vault.util.CanConnect; +import org.springframework.cloud.vault.util.IntegrationTestSupport; +import org.springframework.cloud.vault.util.Settings; +import org.springframework.vault.core.VaultOperations; + +/** + * Integration tests for {@link VaultConfigTemplate} using the mongodb secret backend. + * This test requires a running MongoDB instance, see {@link #ROOT_CREDENTIALS}. + * + * @author Mark Paluch + */ +public class MongoSecretIntegrationTests extends IntegrationTestSupport { + + private final static int MONGODB_PORT = 27017; + private final static String MONGODB_HOST = "localhost"; + private final static String ROOT_CREDENTIALS = String.format( + "mongodb://spring:vault@%s:%d/admin?ssl=false", MONGODB_HOST, MONGODB_PORT); + private final static String ROLES = "[ \"readWrite\", { \"role\": \"read\", \"db\": \"admin\" } ]"; + + private VaultProperties vaultProperties = Settings.createVaultProperties(); + private VaultConfigOperations configOperations; + private VaultMongoProperties mongodb = new VaultMongoProperties(); + + /** + * Initialize the mongodb secret backend. + * + * @throws Exception + */ + @Before + public void setUp() throws Exception { + + assumeTrue(CanConnect.to(new InetSocketAddress(MONGODB_HOST, MONGODB_PORT))); + + mongodb.setEnabled(true); + mongodb.setRole("readonly"); + + if (!prepare().hasSecretBackend(mongodb.getBackend())) { + prepare().mountSecret(mongodb.getBackend()); + } + + VaultOperations vaultOperations = vaultRule.prepare().getVaultOperations(); + + vaultOperations.write(String.format("%s/config/connection", mongodb.getBackend()), + Collections.singletonMap("uri", ROOT_CREDENTIALS)); + + Map role = new HashMap<>(); + role.put("db", "admin"); + role.put("roles", ROLES); + + vaultOperations.write( + String.format("%s/roles/%s", mongodb.getBackend(), mongodb.getRole()), + role); + + configOperations = new VaultConfigTemplate(vaultOperations, vaultProperties); + } + + @Test + public void shouldCreateCredentialsCorrectly() throws Exception { + + Map secretProperties = configOperations + .read(forDatabase(mongodb)); + + assertThat(secretProperties).containsKeys("spring.data.mongodb.username", + "spring.data.mongodb.password"); + } +} diff --git a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/VaultConfigMongoTests.java b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/VaultConfigMongoTests.java new file mode 100644 index 00000000..020b728f --- /dev/null +++ b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/VaultConfigMongoTests.java @@ -0,0 +1,129 @@ +/* + * 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.databases; + +import static org.junit.Assume.*; + +import java.net.InetSocketAddress; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.vault.util.CanConnect; +import org.springframework.cloud.vault.util.VaultRule; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.vault.core.VaultOperations; + +import com.mongodb.MongoClient; +import com.mongodb.client.MongoDatabase; + +/** + * Integration tests using the mongodb secret backend. In case this test should fail because + * of SSL make sure you run the test within the + * spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is + * referenced with {@code ../work/keystore.jks}. + * + * @author Mark Paluch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = VaultConfigMongoTests.TestApplication.class, properties = { + "spring.cloud.vault.mongodb.enabled=true", + "spring.cloud.vault.mongodb.role=readonly", + "spring.data.mongodb.url=mongodb://localhost", + "spring.data.mongodb.database=admin" }) +public class VaultConfigMongoTests { + + private final static int MONGODB_PORT = 27017; + private final static String MONGODB_HOST = "localhost"; + private final static String ROOT_CREDENTIALS = String.format( + "mongodb://spring:vault@%s:%d/admin?ssl=false", MONGODB_HOST, MONGODB_PORT); + private final static String ROLES = "[ \"readWrite\", { \"role\": \"read\", \"db\": \"admin\" } ]"; + + /** + * Initialize the mysql secret backend. + * + * @throws Exception + */ + @BeforeClass + public static void beforeClass() throws Exception { + + assumeTrue(CanConnect.to(new InetSocketAddress(MONGODB_HOST, MONGODB_PORT))); + + VaultRule vaultRule = new VaultRule(); + vaultRule.before(); + + if (!vaultRule.prepare().hasSecretBackend("mongodb")) { + vaultRule.prepare().mountSecret("mongodb"); + } + + VaultOperations vaultOperations = vaultRule.prepare().getVaultOperations(); + + vaultOperations.write("mongodb/config/connection", + Collections.singletonMap("uri", ROOT_CREDENTIALS)); + + Map role = new HashMap<>(); + role.put("db", "admin"); + role.put("roles", ROLES); + + vaultOperations.write("mongodb/roles/readonly", role); + } + + @Value("${spring.data.mongodb.username}") + String username; + + @Value("${spring.data.mongodb.password}") + String password; + + @Autowired + MongoClient mongoClient; + + @Test + public void shouldConnectUsingDataSource() throws SQLException { + + MongoDatabase mongoDatabase = mongoClient.getDatabase("admin"); + + List collections = mongoDatabase.listCollections() + .into(new ArrayList()); + + for (Document collection : collections) { + if (collection.getString("name").equals("hello")) { + mongoDatabase.getCollection(collection.getString("name")).drop(); + } + } + + mongoDatabase.createCollection("hello"); + } + + @SpringBootApplication + public static class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + } +} diff --git a/src/test/bash/install_vault.sh b/src/test/bash/install_vault.sh index 6cb231ed..07f7f719 100755 --- a/src/test/bash/install_vault.sh +++ b/src/test/bash/install_vault.sh @@ -6,7 +6,7 @@ ########################################################################### -VAULT_VER="0.6.0" +VAULT_VER="${VAULT_VER:-0.6.1}" UNAME=$(uname -s | tr '[:upper:]' '[:lower:]') VAULT_ZIP="vault_${VAULT_VER}_${UNAME}_amd64.zip" IGNORE_CERTS="${IGNORE_CERTS:-no}"