Support RabbitMQ credential generation
fixes gh-10
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,5 +17,6 @@ _site/
|
||||
download/
|
||||
/vault/
|
||||
/consul/
|
||||
/rabbitmq/
|
||||
work
|
||||
build/
|
||||
|
||||
@@ -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';"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/test/bash/install_rabbitmq.sh
Executable file
47
src/test/bash/install_rabbitmq.sh
Executable 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}
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
BASEDIR=`dirname $0`/../../..
|
||||
|
||||
#!/bin/bash
|
||||
mkdir -p ${BASEDIR}/consul/config
|
||||
mkdir -p ${BASEDIR}/consul/data
|
||||
|
||||
|
||||
20
src/test/bash/local_run_rabbitmq.sh
Executable file
20
src/test/bash/local_run_rabbitmq.sh
Executable 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 $?
|
||||
@@ -5,5 +5,7 @@
|
||||
###########################################################################
|
||||
|
||||
BASEDIR=`dirname $0`/../../..
|
||||
|
||||
./vault/vault server -config=${BASEDIR}/spring-cloud-vault-config/src/test/resources/vault.conf
|
||||
|
||||
exit $?
|
||||
|
||||
Reference in New Issue
Block a user