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:
@@ -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
|
||||
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user