Support AppRole authentication.

We now support AppRole authentication. This authentication method uses a provided RoleId and optionally SecretId to authenticate against Vault.

Fixes gh-39.
This commit is contained in:
Mark Paluch
2016-10-12 17:55:46 +02:00
parent 6ac59e77e0
commit 5a68469c39
6 changed files with 224 additions and 36 deletions

View File

@@ -12,7 +12,7 @@ Specifically for Spring applications:
* 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,
* https://www.vaultproject.io/docs/auth/token.html[Token], https://www.vaultproject.io/docs/auth/app-id.html[AppId], https://www.vaultproject.io/docs/auth/approle.html[AppRole],
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

View File

@@ -10,7 +10,7 @@ Specifically for Spring applications:
* 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,
* https://www.vaultproject.io/docs/auth/token.html[Token], https://www.vaultproject.io/docs/auth/app-id.html[AppId], https://www.vaultproject.io/docs/auth/approle.html[AppRole],
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
@@ -44,4 +44,4 @@ include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/maste
== Contributing
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]

View File

@@ -41,6 +41,7 @@ authentication method
See also: https://www.vaultproject.io/docs/concepts/tokens.html[Vault Documentation: Tokens]
[[vault.authentication.appid]]
=== AppId authentication
Vault supports https://www.vaultproject.io/docs/auth/app-id.html[AppId]
@@ -144,6 +145,46 @@ public class MyUserIdMechanism implements AppIdUserIdMechanism {
See also: https://www.vaultproject.io/docs/auth/app-id.html[Vault Documentation: Using the App ID auth backend]
== AppRole authentication
https://www.vaultproject.io/docs/auth/app-id.html[AppRole] is intended for machine
authentication, like the deprecated (since Vault 0.6.1) <<vault.authentication.appid>>.
AppRole authentication consists of two hard to guess (secret) tokens: RoleId and SecretId.
Spring Vault supports AppRole authentication by providing either RoleId only
or together with a provided SecretId (push or pull mode).
RoleId and optionally SecretId must be provided by configuration,
Spring Vault will not look up these or create a custom SecretId.
[source,yaml]
.bootstrap.yml with AppRole authentication properties
----
spring.cloud.vault:
authentication: APPROLE
app-role:
role-id: bde2076b-cccb-3cf0-d57e-bca7b1e83a52
----
* `role-id` sets the RoleId.
[source,yaml]
.bootstrap.yml with all AppRole authentication properties
----
spring.cloud.vault:
authentication: APPROLE
app-role:
role-id: bde2076b-cccb-3cf0-d57e-bca7b1e83a52
secret-id: 1696536f-1976-73b1-b241-0b4213908d39
app-auth-path: approle
----
* `role-id` sets the RoleId.
* `secret-id` sets the SecretId. SecretId can be omitted if AppRole is configured without requiring SecretId (See `bind_secret_id`)
* `approle-path` sets the path of the approle authentication mount to use
See also: https://www.vaultproject.io/docs/auth/approle.html[Vault Documentation: Using the AppRole auth backend]
=== AWS-EC2 authentication
The https://www.vaultproject.io/docs/auth/aws-ec2.html[aws-ec2]

View File

@@ -37,22 +37,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.authentication.AppIdAuthentication;
import org.springframework.vault.authentication.AppIdAuthenticationOptions;
import org.springframework.vault.authentication.AppIdUserIdMechanism;
import org.springframework.vault.authentication.AwsEc2Authentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ClientCertificateAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthenticationOptions;
import org.springframework.vault.authentication.IpAddressUserId;
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
import org.springframework.vault.authentication.MacAddressUserId;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.authentication.StaticUserId;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.*;
import org.springframework.vault.client.VaultClient;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper;
@@ -92,15 +77,16 @@ public class VaultBootstrapConfiguration {
this.applicationContext = applicationContext;
this.vaultProperties = vaultProperties;
this.vaultSecretBackendDescriptors = applicationContext
.getBeansOfType(VaultSecretBackendDescriptor.class).values();
this.factories = (Collection) applicationContext
.getBeansOfType(SecretBackendMetadataFactory.class).values();
this.vaultSecretBackendDescriptors = applicationContext.getBeansOfType(
VaultSecretBackendDescriptor.class).values();
this.factories = (Collection) applicationContext.getBeansOfType(
SecretBackendMetadataFactory.class).values();
}
@Bean
public VaultPropertySourceLocator vaultPropertySourceLocator(
VaultOperations operations, VaultProperties vaultProperties,
VaultOperations operations,
VaultProperties vaultProperties,
VaultGenericBackendProperties vaultGenericBackendProperties,
ObjectProvider<TaskSchedulerWrapper<? extends TaskScheduler>> taskSchedulerProvider) {
@@ -152,8 +138,8 @@ public class VaultBootstrapConfiguration {
sslConfiguration = SslConfiguration.NONE;
}
return new ClientFactoryWrapper(
ClientHttpRequestFactoryFactory.create(clientOptions, sslConfiguration));
return new ClientFactoryWrapper(ClientHttpRequestFactoryFactory.create(
clientOptions, sslConfiguration));
}
/**
@@ -169,9 +155,8 @@ public class VaultBootstrapConfiguration {
vaultEndpoint.setPort(vaultProperties.getPort());
vaultEndpoint.setScheme(vaultProperties.getScheme());
return new VaultClient(
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(),
vaultEndpoint);
return new VaultClient(clientHttpRequestFactoryWrapper()
.getClientHttpRequestFactory(), vaultEndpoint);
}
/**
@@ -225,7 +210,8 @@ public class VaultBootstrapConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public SessionManager sessionManager(ClientAuthentication clientAuthentication,
public SessionManager sessionManager(
ClientAuthentication clientAuthentication,
ObjectProvider<TaskSchedulerWrapper<? extends AsyncTaskExecutor>> asyncTaskExecutorProvider) {
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
@@ -253,6 +239,9 @@ public class VaultBootstrapConfiguration {
case APPID:
return appIdAuthentication(vaultProperties, vaultClient);
case APPROLE:
return appRoleAuthentication(vaultProperties, vaultClient);
case CERT:
return new ClientCertificateAuthentication(vaultClient);
@@ -264,9 +253,9 @@ public class VaultBootstrapConfiguration {
}
throw new UnsupportedOperationException(
String.format("Client authentication %s not supported",
vaultProperties.getAuthentication()));
throw new UnsupportedOperationException(String.format(
"Client authentication %s not supported",
vaultProperties.getAuthentication()));
}
private ClientAuthentication appIdAuthentication(VaultProperties vaultProperties,
@@ -302,8 +291,8 @@ public class VaultBootstrapConfiguration {
if (StringUtils.hasText(appId.getNetworkInterface())) {
try {
return new MacAddressUserId(
Integer.parseInt(appId.getNetworkInterface()));
return new MacAddressUserId(Integer.parseInt(appId
.getNetworkInterface()));
}
catch (NumberFormatException e) {
return new MacAddressUserId(appId.getNetworkInterface());
@@ -317,6 +306,23 @@ public class VaultBootstrapConfiguration {
}
}
private ClientAuthentication appRoleAuthentication(VaultProperties vaultProperties,
VaultClient vaultClient) {
VaultProperties.AppRoleProperties appRole = vaultProperties.getAppRole();
Assert.hasText(appRole.getRoleId(),
"RoleId (spring.cloud.vault.app-role.role-id) must not be empty");
AppRoleAuthenticationOptions.AppRoleAuthenticationOptionsBuilder builder = AppRoleAuthenticationOptions
.builder().path(appRole.getAppRolePath()).roleId(appRole.getRoleId());
if (StringUtils.hasText(appRole.getSecretId())) {
builder = builder.secretId(appRole.getSecretId());
}
return new AppRoleAuthentication(builder.build(), vaultClient);
}
private ClientAuthentication awsEc2Authentication(VaultProperties vaultProperties,
VaultClient vaultClient) {

View File

@@ -76,6 +76,8 @@ public class VaultProperties {
private AppIdProperties appId = new AppIdProperties();
private AppRoleProperties appRole = new AppRoleProperties();
private AwsEc2Properties awsEc2 = new AwsEc2Properties();
private Ssl ssl = new Ssl();
@@ -125,6 +127,25 @@ public class VaultProperties {
private String userId = MAC_ADDRESS;
}
@Data
public static class AppRoleProperties {
/**
* Mount path of the AppId authentication backend.
*/
private String appRolePath = "approle";
/**
* The RoleId.
*/
private String roleId = null;
/**
* The SecretId.
*/
private String secretId = null;
}
@Data
public static class AwsEc2Properties {
@@ -209,6 +230,6 @@ public class VaultProperties {
}
public enum AuthenticationMethod {
TOKEN, APPID, AWS_EC2, CERT, CUBBYHOLE;
TOKEN, APPID, APPROLE, AWS_EC2, CERT, CUBBYHOLE;
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.Settings;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.cloud.vault.util.Version;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.vault.authentication.IpAddressUserId;
import org.springframework.vault.core.VaultOperations;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.assumeTrue;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Integration test using config infrastructure with AppRole authentication. 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 = VaultConfigAppRoleTests.TestApplication.class, properties = {
"spring.cloud.vault.authentication=approle",
"spring.application.name=VaultConfigAppRoleTests" })
public class VaultConfigAppRoleTests {
@BeforeClass
public static void beforeClass() throws Exception {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
assumeTrue(vaultRule.prepare().getVersion()
.isGreaterThanOrEqualTo(Version.parse("0.6.1")));
VaultProperties vaultProperties = Settings.createVaultProperties();
if (!vaultRule.prepare().hasAuth(vaultProperties.getAppRole().getAppRolePath())) {
vaultRule.prepare().mountAuth(vaultProperties.getAppRole().getAppRolePath());
}
VaultOperations vaultOperations = vaultRule.prepare().getVaultOperations();
String rules = "{ \"name\": \"testpolicy\",\n" //
+ " \"path\": {\n" //
+ " \"*\": { \"policy\": \"read\" }\n" //
+ " }\n" //
+ "}";
vaultOperations.write("sys/policy/testpolicy",
Collections.singletonMap("rules", rules));
String appId = VaultConfigAppRoleTests.class.getSimpleName();
vaultOperations.write("secret/" + VaultConfigAppRoleTests.class.getSimpleName(),
Collections.singletonMap("vault.value", "foo"));
Map<String, String> withSecretId = new HashMap<String, String>();
withSecretId.put("policies", "testpolicy"); // policy
withSecretId.put("bound_cidr_list", "0.0.0.0/0");
withSecretId.put("bind_secret_id", "true");
vaultOperations.write("auth/approle/role/with-secret-id", withSecretId);
String roleId = (String) vaultOperations
.read("auth/approle/role/with-secret-id/role-id").getData()
.get("role_id");
String secretId = (String) vaultOperations
.write(String.format("auth/approle/role/with-secret-id/secret-id",
"with-secret-id"), null).getData().get("secret_id");
System.setProperty("spring.cloud.vault.app-role.role-id", roleId);
System.setProperty("spring.cloud.vault.app-role.secret-id", secretId);
}
@Value("${vault.value}")
String configValue;
@Test
public void contextLoads() {
assertThat(configValue).isEqualTo("foo");
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}