diff --git a/docs/src/main/asciidoc/spring-cloud-vault.adoc b/docs/src/main/asciidoc/spring-cloud-vault.adoc index f1a9d6f8..75b2076b 100644 --- a/docs/src/main/asciidoc/spring-cloud-vault.adoc +++ b/docs/src/main/asciidoc/spring-cloud-vault.adoc @@ -893,6 +893,7 @@ Spring Cloud Vault integrates with these backends: * <> * <> +* <> * <> * <> * <> @@ -996,6 +997,40 @@ spring.cloud.vault: See also: https://www.vaultproject.io/docs/secrets/cassandra/index.html[Vault Documentation: Setting up Apache Cassandra with Vault] +[[vault.config.backends.elasticsearch]] +=== Elasticsearch + +Spring Cloud Vault can obtain since version 3.0 credentials for Elasticsearch. +The integration can be enabled by setting +`spring.cloud.vault.elasticsearch.enabled=true` (default `false`) and providing the role name with `spring.cloud.vault.elasticsearch.role=…`. + +Username and password are available from `spring.elasticsearch.rest.username` +and `spring.elasticsearch.rest.password` properties so using Spring Boot will pick up the generated credentials without further configuration. +You can configure the property names by setting +`spring.cloud.vault.elasticsearch.username-property` and +`spring.cloud.vault.elasticsearch.password-property`. + +==== +[source,yaml] +---- +spring.cloud.vault: + elasticsearch: + enabled: true + role: readonly + backend: mongodb + username-property: spring.elasticsearch.rest.username + password-property: spring.elasticsearch.rest.password +---- +==== + +* `enabled` setting this value to `true` enables the Elasticsearch database backend config usage +* `role` sets the role name of the Elasticsearch role definition +* `backend` sets the path of the Elasticsearch mount to use +* `username-property` sets the property name in which the Elasticsearch username is stored +* `password-property` sets the property name in which the Elasticsearch password is stored + +See also: https://www.vaultproject.io/docs/secrets/databases/elasticdb[Vault Documentation: Setting up Elasticsearch with Vault] + [[vault.config.backends.mongodb]] === MongoDB 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 7960722e..700cf587 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 @@ -41,7 +41,8 @@ import org.springframework.vault.core.util.PropertyTransformer; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties({ VaultMySqlProperties.class, VaultPostgreSqlProperties.class, VaultCassandraProperties.class, - VaultMongoProperties.class, VaultDatabaseProperties.class }) + VaultMongoProperties.class, VaultElasticsearchProperties.class, + VaultDatabaseProperties.class }) public class VaultConfigDatabaseBootstrapConfiguration { @Bean diff --git a/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultElasticsearchProperties.java b/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultElasticsearchProperties.java new file mode 100644 index 00000000..a5be4e04 --- /dev/null +++ b/spring-cloud-vault-config-databases/src/main/java/org/springframework/cloud/vault/config/databases/VaultElasticsearchProperties.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016-2020 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 + * + * https://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 javax.validation.constraints.NotEmpty; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * Configuration properties for Vault using the Elasticsearch integration. + * + * @author Mark Paluch + * @since 3.0 + */ +@ConfigurationProperties("spring.cloud.vault.elasticsearch") +@Validated +public class VaultElasticsearchProperties implements DatabaseSecretProperties { + + /** + * Enable elasticsearch backend usage. + */ + private boolean enabled = false; + + /** + * Role name for credentials. + */ + private String role; + + /** + * Enable static role usage. + */ + private boolean staticRole = false; + + /** + * Database backend path. + */ + @NotEmpty + private String backend = "database"; + + /** + * Target property for the obtained username. + */ + @NotEmpty + private String usernameProperty = "spring.elasticsearch.rest.username"; + + /** + * Target property for the obtained password. + */ + @NotEmpty + private String passwordProperty = "spring.elasticsearch.rest.password"; + + @Override + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getRole() { + return this.role; + } + + public void setRole(String role) { + this.role = role; + } + + @Override + public boolean isStaticRole() { + return this.staticRole; + } + + public void setStaticRole(boolean staticRole) { + this.staticRole = staticRole; + } + + @Override + public String getBackend() { + return this.backend; + } + + public void setBackend(String backend) { + this.backend = backend; + } + + @Override + public String getUsernameProperty() { + return this.usernameProperty; + } + + public void setUsernameProperty(String usernameProperty) { + this.usernameProperty = usernameProperty; + } + + @Override + public String getPasswordProperty() { + return this.passwordProperty; + } + + public void setPasswordProperty(String passwordProperty) { + this.passwordProperty = passwordProperty; + } + +} diff --git a/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/ElasticsearchSecretIntegrationTests.java b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/ElasticsearchSecretIntegrationTests.java new file mode 100644 index 00000000..9b74edee --- /dev/null +++ b/spring-cloud-vault-config-databases/src/test/java/org/springframework/cloud/vault/config/databases/ElasticsearchSecretIntegrationTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016-2020 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 + * + * https://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 java.net.InetSocketAddress; +import java.util.LinkedHashMap; +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.cloud.vault.util.Version; +import org.springframework.vault.core.VaultOperations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; +import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecretBackendMetadataFactory.forDatabase; + +/** + * Integration tests for {@link VaultConfigTemplate} using the elasticsearch database + * backend. This test requires a running Elasticearch instance, see + * {@link #ELASTICSEARCH_HOST}. Make sure to configure {#link ES_HOME} accordingly. + * + * @author Mark Paluch + */ +public class ElasticsearchSecretIntegrationTests extends IntegrationTestSupport { + + private static final int ELASTICSEARCH_PORT = 9200; + + private static final String ELASTICSEARCH_HOST = "localhost"; + + private static final String ES_HOME = "configure me"; + + private VaultProperties vaultProperties = Settings.createVaultProperties(); + + private VaultConfigOperations configOperations; + + private VaultElasticsearchProperties elasticsearch = new VaultElasticsearchProperties(); + + /** + * Initialize the elasticsearch secret backend. + */ + @Before + public void setUp() { + + assumeTrue(CanConnect + .to(new InetSocketAddress(ELASTICSEARCH_HOST, ELASTICSEARCH_PORT))); + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo(Version.parse("1.3.0"))); + + this.elasticsearch.setEnabled(true); + this.elasticsearch.setRole("readonly"); + + if (!prepare().hasSecretBackend(this.elasticsearch.getBackend())) { + prepare().mountSecret(this.elasticsearch.getBackend()); + } + + VaultOperations vaultOperations = this.vaultRule.prepare().getVaultOperations(); + String database = "elasticsearch"; + + Map config = new LinkedHashMap<>(); + config.put("plugin_name", "elasticsearch-database-plugin"); + config.put("allowed_roles", "readonly"); + config.put("username", "elastic"); + config.put("password", "elastic"); + config.put("url", + String.format("http://%s:%d", ELASTICSEARCH_HOST, ELASTICSEARCH_PORT)); + + config.put("ca_cert", String.format("%s/elastic-stack-ca.crt", ES_HOME)); + config.put("client_cert", String.format("%s/elastic-certificates.crt", ES_HOME)); + config.put("client_key", String.format("%s/elastic-certificates.key", ES_HOME)); + + vaultOperations.write( + String.format("%s/config/%s", this.elasticsearch.getBackend(), database), + config); + + Map role = new LinkedHashMap<>(); + role.put("db_name", database); + role.put("creation_statements", + "{\"elasticsearch_role_definition\": {\"indices\": [{\"names\":[\"*\"], \"privileges\":[\"read\"]}]}}"); + role.put("default_ttl", "1h"); + + vaultOperations.write(this.elasticsearch.getBackend() + "/roles/" + + this.elasticsearch.getRole(), role); + + this.configOperations = new VaultConfigTemplate(vaultOperations, + this.vaultProperties); + } + + @Test + public void shouldCreateCredentialsCorrectly() { + + Map secretProperties = this.configOperations + .read(forDatabase(this.elasticsearch)).getData(); + + assertThat(secretProperties).containsKeys("spring.elasticsearch.rest.username", + "spring.elasticsearch.rest.password"); + } + +}