Support RabbitMQ credential generation

fixes gh-10
This commit is contained in:
Mark Paluch
2016-06-19 17:40:56 +02:00
parent ddf9d9de91
commit 625e60174f
12 changed files with 371 additions and 1 deletions

1
.gitignore vendored
View File

@@ -17,5 +17,6 @@ _site/
download/
/vault/
/consul/
/rabbitmq/
work
build/

View File

@@ -3,6 +3,7 @@ language: java
services:
- mysql
- postgresql
- rabbitmq
jdk:
- oraclejdk8
@@ -18,6 +19,8 @@ install:
- src/test/bash/install_consul.sh
- src/test/bash/local_run_vault.sh &
- src/test/bash/local_run_consul.sh &
- sudo rabbitmq-plugins enable rabbitmq_management
- sudo service rabbitmq-server restart
before_script:
- mysql -e "CREATE USER 'spring' IDENTIFIED by 'vault';"

View File

@@ -233,6 +233,28 @@ spring.cloud.vault:
See also: https://www.vaultproject.io/docs/secrets/consul/index.html[Vault Documentation: Setting up Consul with Vault]
[[vault-client-rabbitmq]]
=== RabbitMQ
Spring Cloud Vault allows to obtain credentials for RabbitMQ.
The integration can be enabled by setting `spring.cloud.vault.rabbit.enabled=true`
(default `false`). Username and password are stored in `spring.rabbit.username`
and `spring.rabbit.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.rabbit.username-property` and
`spring.cloud.vault.rabbit.password-property`.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
rabbit:
enabled: true
----
See also: https://www.vaultproject.io/docs/secrets/rabbit/index.html[Vault Documentation: Setting up RabbitMQ with Vault]
[[vault-client-fail-fast]]
== Vault Client Fail Fast

View File

@@ -57,6 +57,11 @@
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>

View File

@@ -102,6 +102,8 @@ public class VaultProperties {
private Consul consul = new Consul();
private Rabbitmq rabbitmq = new Rabbitmq();
/**
* Application name for AppId authentication.
*/
@@ -279,6 +281,38 @@ public class VaultProperties {
private String tokenProperty = "spring.cloud.consul.token";
}
@Data
public static class Rabbitmq implements DatabaseSecretProperties {
/**
* Enable rabbitmq backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* RabbitMQ backend path.
*/
@NotEmpty
private String backend = "rabbitmq";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.rabbitmq.username";
/**
* Target property for the obtained password.
*/
@NotEmpty
private String passwordProperty = "spring.rabbitmq.password";
}
/**
* Configuration properties interface for database secrets.
*/

View File

@@ -106,6 +106,11 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
if (consul.isEnabled()) {
accessors.add(SecureBackendAccessors.consul(consul));
}
VaultProperties.Rabbitmq rabbitmq = vaultProperties.getRabbitmq();
if (rabbitmq.isEnabled()) {
accessors.add(SecureBackendAccessors.database(rabbitmq));
}
return accessors;
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
/**
* Integration tests for {@link VaultClient} using the rabbitmq secret backend. This test
* requires a running RabbitMQ instance, see {@link #RABBITMQ_URI}.
*
* @author Mark Paluch
*/
public class RabbitMQSecretIntegrationTests extends AbstractIntegrationTests {
private final static int RABBITMQ_HTTP_MANAGEMENT_PORT = 15672;
private final static String RABBITMQ_HOST = "localhost";
private final static String RABBITMQ_USERNAME = "guest";
private final static String RABBITMQ_PASSWORD = "guest";
private final static String RABBITMQ_URI = String
.format("http://%s:%d", RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT);
private final static String VHOSTS_ROLE = "{\"/\":{\"write\": \".*\", \"read\": \".*\"}}";
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.Rabbitmq rabbitmq = vaultProperties.getRabbitmq();
/**
* Initialize the mysql secret backend.
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
assumeTrue(CanConnect.to(new InetSocketAddress(RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT)));
rabbitmq.setEnabled(true);
rabbitmq.setRole("readonly");
if (!prepare().hasSecret(rabbitmq.getBackend())) {
prepare().mountSecret(rabbitmq.getBackend());
}
Map<String, String> connection = new HashMap<>();
connection.put("connection_uri", RABBITMQ_URI);
connection.put("username", RABBITMQ_USERNAME);
connection.put("password", RABBITMQ_PASSWORD);
prepare().write(String.format("%s/config/connection", rabbitmq.getBackend()),
connection);
prepare().write(String.format("%s/roles/%s", rabbitmq.getBackend(), rabbitmq.getRole()),
Collections.singletonMap("vhosts", VHOSTS_ROLE));
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
}
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(database(rabbitmq),
Settings.token());
assertThat(secretProperties).containsKeys("spring.rabbitmq.username",
"spring.rabbitmq.password");
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.configclient;
import static org.junit.Assume.*;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* Integration tests using the rabbitmq 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)
@SpringApplicationConfiguration(classes = VaultRabbitMQTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.rabbitmq.enabled=true",
"spring.cloud.vault.rabbitmq.role=readonly",
"spring.rabbitmq.address=localhost" })
public class VaultRabbitMQTests {
private final static int RABBITMQ_HTTP_MANAGEMENT_PORT = 15672;
private final static int RABBITMQ_PORT = 5672;
private final static String RABBITMQ_HOST = "localhost";
private final static String RABBITMQ_USERNAME = "guest";
private final static String RABBITMQ_PASSWORD = "guest";
private final static String RABBITMQ_URI = String.format("http://%s:%d",
RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT);
private final static String VHOSTS_ROLE = "{\"/\":{\"write\": \".*\", \"read\": \".*\"}}";
/**
* Initialize the rabbitmq secret backend.
*
* @throws Exception
*/
@BeforeClass
public static void beforeClass() throws Exception {
assumeTrue(CanConnect
.to(new InetSocketAddress(RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT)));
VaultRule vaultRule = new VaultRule();
vaultRule.before();
if (!vaultRule.prepare().hasSecret("rabbitmq")) {
vaultRule.prepare().mountSecret("rabbitmq");
}
Map<String, String> connection = new HashMap<>();
connection.put("connection_uri", RABBITMQ_URI);
connection.put("username", RABBITMQ_USERNAME);
connection.put("password", RABBITMQ_PASSWORD);
vaultRule.prepare().write(String.format("rabbitmq/config/connection"),
connection);
vaultRule.prepare().write(String.format("rabbitmq/roles/readonly"),
Collections.singletonMap("vhosts", VHOSTS_ROLE));
}
@Value("${spring.rabbitmq.username}")
String username;
@Value("${spring.rabbitmq.password}")
String password;
@Autowired
org.springframework.amqp.rabbit.connection.ConnectionFactory connectionFactory;
@Test
public void shouldConnectSpringConnectionFactory() {
connectionFactory.createConnection().close();
}
@Test
public void shouldConnectUsingRabbitMQClient() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST);
factory.setPort(RABBITMQ_PORT);
factory.setUsername(username);
factory.setPassword(password);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.close();
connection.close();
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}

View File

@@ -0,0 +1,47 @@
#!/bin/bash
###########################################################################
# Download and Install RabbitMQ #
# This script is prepared for caching of the download directory #
###########################################################################
RABBITMQ_VER="3.6.2"
UNAME=$(uname -s | tr '[:upper:]' '[:lower:]')
if [[ ${UNAME} == "darwin" ]] ; then
RABBITMQ_ZIP="rabbitmq-server-mac-standalone-${RABBITMQ_VER}.tar.xz"
else
echo "Installation of RabbitMQ on ${UNAME} not supported by this script"
exit 1
fi
IGNORE_CERTS="${IGNORE_CERTS:-no}"
# cleanup
mkdir -p rabbitmq
if [[ ! -f "download/${RABBITMQ_ZIP}" ]] ; then
cd download
# install Vault
if [[ "${IGNORE_CERTS}" == "no" ]] ; then
echo "Downloading RabbitMQ with certs verification"
wget "https://www.rabbitmq.com/releases/rabbitmq-server/v${RABBITMQ_VER}/${RABBITMQ_ZIP}"
else
echo "WARNING... Downloading RabbitMQ WITHOUT certs verification"
wget "https://www.rabbitmq.com/releases/rabbitmq-server/v${RABBITMQ_VER}/${RABBITMQ_ZIP}" --no-check-certificate
fi
if [[ $? != 0 ]] ; then
echo "Cannot download RabbitMQ"
exit 1
fi
cd ..
fi
cd rabbitmq
if [[ -d ebin ]] ; then
rm -Rf ../rabbitmq/*
fi
tar xzf ../download/${RABBITMQ_ZIP}

View File

@@ -6,7 +6,6 @@
BASEDIR=`dirname $0`/../../..
#!/bin/bash
mkdir -p ${BASEDIR}/consul/config
mkdir -p ${BASEDIR}/consul/data

View File

@@ -0,0 +1,20 @@
#!/bin/bash
###########################################################################
# Start RabbitMQ on localhost #
###########################################################################
BASEDIR=`dirname $0`/../../..
RABBITMQ_VER="3.6.2"
cd rabbitmq/rabbitmq_server-${RABBITMQ_VER}
if [[ ! -f etc/rabbitmq/rabbitmq.config ]] ; then
cp etc/rabbitmq/rabbitmq.config.example etc/rabbitmq/rabbitmq.config
fi
sbin/rabbitmq-plugins enable rabbitmq_management
sbin/rabbitmq-server
exit $?

View File

@@ -5,5 +5,7 @@
###########################################################################
BASEDIR=`dirname $0`/../../..
./vault/vault server -config=${BASEDIR}/spring-cloud-vault-config/src/test/resources/vault.conf
exit $?