Refactor ClientAuthentication into own component

The authentication API can be provided into VaultClient by implementing a ClientAuthentication.
This commit is contained in:
Mark Paluch
2016-07-04 22:02:52 +02:00
parent 28280f5dae
commit 232aae9ba8
13 changed files with 533 additions and 220 deletions

View File

@@ -91,5 +91,4 @@ public class AwsSecretIntegrationTests extends AbstractIntegrationTests {
assertThat(secretProperties).containsKeys("cloud.aws.credentials.accessKey",
"cloud.aws.credentials.secretKey");
}
}

View File

@@ -98,5 +98,4 @@ public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests {
assertThat(secretProperties).containsKeys("spring.rabbitmq.username",
"spring.rabbitmq.password");
}
}

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.cloud.vault.config;
import static org.springframework.cloud.vault.VaultBootstrapConfiguration.*;
import java.util.Collection;
import java.util.Map;
import javax.annotation.PostConstruct;
@@ -23,6 +26,8 @@ import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.AppIdUserIdMechanism;
import org.springframework.cloud.vault.ClientAuthentication;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultBootstrapConfiguration;
import org.springframework.cloud.vault.VaultClient;
@@ -32,6 +37,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author Mark Paluch
@@ -55,13 +61,51 @@ public class VaultConfigBootstrapConfiguration implements ApplicationContextAwar
@Bean
public VaultPropertySourceLocator vaultPropertySourceLocator(VaultClient vaultClient,
VaultProperties vaultProperties,
VaultGenericBackendProperties vaultGenericBackendProperties) {
VaultGenericBackendProperties vaultGenericBackendProperties,
ClientFactoryWrapper clientFactoryWrapper) {
Collection<SecureBackendAccessor> backendAccessors = SecureBackendFactories
.createBackendAcessors(vaultSecretBackends, factories);
return new VaultPropertySourceLocator(vaultClient, vaultProperties,
vaultGenericBackendProperties, backendAccessors);
ClientAuthentication clientAuthentication = clientAuthentication(
applicationContext, clientFactoryWrapper, vaultProperties);
return new VaultPropertySourceLocator(vaultClient, clientAuthentication,
vaultProperties, vaultGenericBackendProperties, backendAccessors);
}
private ClientAuthentication clientAuthentication(
ApplicationContext applicationContext,
ClientFactoryWrapper clientFactoryWrapper, VaultProperties vaultProperties) {
RestTemplate restTemplate = new RestTemplate(
clientFactoryWrapper.getClientHttpRequestFactory());
ClientAuthentication clientAuthentication;
if (vaultProperties
.getAuthentication() == VaultProperties.AuthenticationMethod.TOKEN) {
clientAuthentication = ClientAuthentication.token(vaultProperties);
}
else if (vaultProperties
.getAuthentication() == VaultProperties.AuthenticationMethod.APPID) {
Map<String, AppIdUserIdMechanism> appIdUserIdMechanisms = applicationContext
.getBeansOfType(AppIdUserIdMechanism.class);
if (!appIdUserIdMechanisms.isEmpty()) {
clientAuthentication = ClientAuthentication.appId(vaultProperties,
restTemplate, appIdUserIdMechanisms.values().iterator().next());
}
else {
clientAuthentication = ClientAuthentication.appId(vaultProperties,
restTemplate);
}
}
else {
clientAuthentication = ClientAuthentication.create(vaultProperties,
restTemplate);
}
return clientAuthentication;
}
@Override

View File

@@ -19,6 +19,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.cloud.vault.ClientAuthentication;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
@@ -42,19 +43,21 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
private final VaultProperties vaultProperties;
private final SecureBackendAccessor secureBackendAccessor;
private final Map<String, String> properties = new LinkedHashMap<>();
private final ClientAuthentication clientAuthentication;
private final transient VaultState vaultState;
/**
* Creates a new {@link VaultPropertySource}.
*
* @param vaultClient must not be {@literal null}.
* @param vaultClient must not be {@literal null}.
* @param clientAuthentication mist not be {@literal null}.
* @param properties must not be {@literal null}.
* @param state shared Vault state, must not be {@literal null}.
* @param secureBackendAccessor must not be {@literal null}.
* @param state shared Vault state, must not be {@literal null}.
* @param secureBackendAccessor must not be {@literal null}.
*/
public VaultPropertySource(VaultClient vaultClient,
VaultProperties properties, VaultState state,
SecureBackendAccessor secureBackendAccessor) {
ClientAuthentication clientAuthentication, VaultProperties properties,
VaultState state, SecureBackendAccessor secureBackendAccessor) {
super(secureBackendAccessor.getName(), vaultClient);
@@ -62,8 +65,10 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
Assert.notNull(properties, "VaultProperties must not be null!");
Assert.notNull(state, "VaultState must not be null!");
Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be null!");
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null!");
this.vaultProperties = properties;
this.clientAuthentication = clientAuthentication;
this.vaultState = state;
this.secureBackendAccessor = secureBackendAccessor;
}
@@ -106,7 +111,7 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
if (vaultProperties.getAuthentication() == AuthenticationMethod.TOKEN) {
Assert.hasText(vaultProperties.getToken(), "Vault Token must not be empty");
vaultState.setToken(VaultToken.of(vaultProperties.getToken()));
vaultState.setToken(clientAuthentication.login());
return vaultState.getToken();
}
@@ -118,7 +123,7 @@ class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
"AppId must not be empty");
Assert.hasText(appIdProperties.getAppIdPath(), "AppIdPath must not be empty");
vaultState.setToken(source.createToken());
vaultState.setToken(clientAuthentication.login());
return vaultState.getToken();
}

View File

@@ -25,6 +25,7 @@ import java.util.Collections;
import java.util.List;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.vault.ClientAuthentication;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
@@ -43,129 +44,125 @@ import org.springframework.util.StringUtils;
*/
class VaultPropertySourceLocator implements PropertySourceLocator {
private final VaultClient vaultClient;
private final VaultProperties properties;
private final VaultGenericBackendProperties genericBackendProperties;
private final Collection<SecureBackendAccessor> backendAccessors;
private final VaultClient vaultClient;
private final ClientAuthentication clientAuthentication;
private final VaultProperties properties;
private final VaultGenericBackendProperties genericBackendProperties;
private final Collection<SecureBackendAccessor> backendAccessors;
private transient final VaultState vaultState = new VaultState();
private transient final VaultState vaultState = new VaultState();
/**
* Creates a new {@link VaultPropertySourceLocator}.
* @param vaultClient must not be {@literal null}.
* @param properties must not be {@literal null}.
* @param genericBackendProperties must not be {@literal null}.
* @param backendAccessors must not be {@literal null}.
*/
public VaultPropertySourceLocator(VaultClient vaultClient, VaultProperties properties,
VaultGenericBackendProperties genericBackendProperties,
Collection<SecureBackendAccessor> backendAccessors) {
/**
* Creates a new {@link VaultPropertySourceLocator}.
*
* @param vaultClient must not be {@literal null}.
* @param clientAuthentication must not be {@literal null}.
* @param properties must not be {@literal null}.
* @param genericBackendProperties must not be {@literal null}.
* @param backendAccessors must not be {@literal null}.
*/
public VaultPropertySourceLocator(VaultClient vaultClient, ClientAuthentication clientAuthentication,
VaultProperties properties, VaultGenericBackendProperties genericBackendProperties,
Collection<SecureBackendAccessor> backendAccessors) {
Assert.notNull(vaultClient, "VaultClient must not be null");
Assert.notNull(properties, "VaultProperties must not be null");
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
Assert.notNull(genericBackendProperties,
"VaultGenericBackendProperties must not be null");
Assert.notNull(vaultClient, "VaultClient must not be null");
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
Assert.notNull(properties, "VaultProperties must not be null");
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
Assert.notNull(genericBackendProperties, "VaultGenericBackendProperties must not be null");
this.vaultClient = vaultClient;
this.properties = properties;
this.backendAccessors = backendAccessors;
this.genericBackendProperties = genericBackendProperties;
}
this.vaultClient = vaultClient;
this.clientAuthentication = clientAuthentication;
this.properties = properties;
this.backendAccessors = backendAccessors;
this.genericBackendProperties = genericBackendProperties;
}
@Override
public PropertySource<?> locate(Environment environment) {
@Override
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
if (environment instanceof ConfigurableEnvironment) {
CompositePropertySource propertySource = createCompositePropertySource(
(ConfigurableEnvironment) environment);
initialize(propertySource);
CompositePropertySource propertySource = createCompositePropertySource((ConfigurableEnvironment) environment);
initialize(propertySource);
return propertySource;
}
return null;
}
return propertySource;
}
return null;
}
private List<String> buildContexts(ConfigurableEnvironment env) {
private List<String> buildContexts(ConfigurableEnvironment env) {
String appName = env.getProperty("spring.application.name");
List<String> profiles = Arrays.asList(env.getActiveProfiles());
List<String> contexts = new ArrayList<>();
String appName = env.getProperty("spring.application.name");
List<String> profiles = Arrays.asList(env.getActiveProfiles());
List<String> contexts = new ArrayList<>();
String defaultContext = genericBackendProperties.getDefaultContext();
if (StringUtils.hasText(defaultContext)) {
contexts.add(defaultContext);
}
String defaultContext = genericBackendProperties.getDefaultContext();
if (StringUtils.hasText(defaultContext)) {
contexts.add(defaultContext);
}
addProfiles(contexts, defaultContext, profiles);
addProfiles(contexts, defaultContext, profiles);
if (StringUtils.hasText(appName)) {
if (StringUtils.hasText(appName)) {
if (!contexts.contains(appName)) {
contexts.add(appName);
}
if (!contexts.contains(appName)) {
contexts.add(appName);
}
addProfiles(contexts, appName, profiles);
}
addProfiles(contexts, appName, profiles);
}
Collections.reverse(contexts);
return contexts;
}
Collections.reverse(contexts);
return contexts;
}
protected CompositePropertySource createCompositePropertySource(
ConfigurableEnvironment environment) {
protected CompositePropertySource createCompositePropertySource(ConfigurableEnvironment environment) {
CompositePropertySource propertySource = new CompositePropertySource("vault");
CompositePropertySource propertySource = new CompositePropertySource("vault");
if (genericBackendProperties.isEnabled()) {
if (genericBackendProperties.isEnabled()) {
List<String> contexts = buildContexts(environment);
for (String propertySourceContext : contexts) {
List<String> contexts = buildContexts(environment);
for (String propertySourceContext : contexts) {
if (StringUtils.hasText(propertySourceContext)) {
if (StringUtils.hasText(propertySourceContext)) {
VaultPropertySource vaultPropertySource = createVaultPropertySource(
generic(genericBackendProperties.getBackend(),
propertySourceContext));
VaultPropertySource vaultPropertySource = createVaultPropertySource(
generic(genericBackendProperties.getBackend(), propertySourceContext));
propertySource.addPropertySource(vaultPropertySource);
}
}
}
propertySource.addPropertySource(vaultPropertySource);
}
}
}
for (SecureBackendAccessor backendAccessor : backendAccessors) {
for (SecureBackendAccessor backendAccessor : backendAccessors) {
VaultPropertySource vaultPropertySource = createVaultPropertySource(
backendAccessor);
propertySource.addPropertySource(vaultPropertySource);
}
return propertySource;
}
VaultPropertySource vaultPropertySource = createVaultPropertySource(backendAccessor);
propertySource.addPropertySource(vaultPropertySource);
}
return propertySource;
}
protected void initialize(CompositePropertySource propertySource) {
protected void initialize(CompositePropertySource propertySource) {
for (PropertySource<?> source : propertySource.getPropertySources()) {
((VaultPropertySource) source).init();
}
}
for (PropertySource<?> source : propertySource.getPropertySources()) {
((VaultPropertySource) source).init();
}
}
private VaultPropertySource createVaultPropertySource(
SecureBackendAccessor accessor) {
return new VaultPropertySource(this.vaultClient, this.properties, this.vaultState,
accessor);
}
private VaultPropertySource createVaultPropertySource(SecureBackendAccessor accessor) {
return new VaultPropertySource(this.vaultClient, this.clientAuthentication, this.properties, this.vaultState, accessor);
}
private void addProfiles(List<String> contexts, String baseContext,
List<String> profiles) {
private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
for (String profile : profiles) {
String context = baseContext
+ this.genericBackendProperties.getProfileSeparator() + profile;
for (String profile : profiles) {
String context = baseContext + this.genericBackendProperties.getProfileSeparator() + profile;
if (!contexts.contains(context)) {
contexts.add(context);
}
}
}
if (!contexts.contains(context)) {
contexts.add(context);
}
}
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.vault.config;
import org.junit.Before;
import org.springframework.cloud.vault.ClientAuthentication;
import org.springframework.cloud.vault.IpAddressUserId;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
@@ -23,6 +24,7 @@ import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.cloud.vault.VaultToken;
import org.springframework.cloud.vault.util.Settings;
import org.springframework.web.client.RestTemplate;
/**
* Integration tests for {@link VaultClient} using {@link AuthenticationMethod#APPID}.
@@ -31,6 +33,8 @@ import org.springframework.cloud.vault.util.Settings;
*/
public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegrationTests {
private ClientAuthentication clientAuthentication;
@Before
public void setUp() throws Exception {
@@ -52,14 +56,17 @@ public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegratio
prepare().mapAppId(vaultProperties.getApplicationName());
prepare().mapUserId(vaultProperties.getApplicationName(), userId);
RestTemplate restTemplate = TestRestTemplateFactory.create(vaultProperties);
this.clientAuthentication = ClientAuthentication.appId(vaultProperties,
restTemplate, userIdMechanism);
this.vaultClient = new VaultClient(vaultProperties);
this.vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
this.vaultClient.setAppIdUserIdMechanism(userIdMechanism);
}
@Override
protected VaultToken createToken() {
return vaultClient.createToken();
return clientAuthentication.login();
}
private AppIdProperties configureAppIdProperties() {

View File

@@ -0,0 +1,77 @@
/*
* 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 org.springframework.web.client.RestTemplate;
/**
* @author Mark Paluch
*/
public abstract class ClientAuthentication {
/**
* Perform a login to Vault and return a {@link VaultToken}.
*
* @return a {@link VaultToken}.
*/
public abstract VaultToken login();
/**
* Creates a Token-based authentication adapter.
*
* @param vaultProperties must not be {@literal null}.
* @return the {@link ClientAuthentication} adapter.
*/
public static ClientAuthentication token(VaultProperties vaultProperties) {
return new TokenClientAuthentication(vaultProperties);
}
/**
* Creates a generic authentication adapter.
*
* @param vaultProperties must not be {@literal null}.
* @return the {@link ClientAuthentication} adapter.
*/
public static ClientAuthentication create(VaultProperties vaultProperties,
RestTemplate restTemplate) {
return new DefaultClientAuthentication(vaultProperties, restTemplate);
}
/**
* Creates an AppId-based authentication adapter.
*
* @param vaultProperties must not be {@literal null}.
* @return the {@link ClientAuthentication} adapter.
*/
public static ClientAuthentication appId(VaultProperties vaultProperties,
RestTemplate restTemplate) {
return new DefaultClientAuthentication(vaultProperties, restTemplate);
}
/**
* Creates an AppId-based authentication adapter.
*
* @param vaultProperties must not be {@literal null}.
* @param restTemplate must not be {@literal null}.
* @param userIdMechanism must not be {@literal null}.
* @return the {@link ClientAuthentication} adapter.
*/
public static ClientAuthentication appId(VaultProperties vaultProperties,
RestTemplate restTemplate, AppIdUserIdMechanism userIdMechanism) {
return new DefaultClientAuthentication(vaultProperties, restTemplate,
userIdMechanism);
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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.springframework.cloud.vault.VaultClient.*;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import lombok.Value;
import lombok.extern.apachecommons.CommonsLog;
/**
* Default implementation of {@link ClientAuthentication}.
*
* @author Mark Paluch
*/
@CommonsLog
class DefaultClientAuthentication extends ClientAuthentication {
private final VaultProperties properties;
private final RestTemplate restTemplate;
private final AppIdUserIdMechanism appIdUserIdMechanism;
/**
* Creates a {@link DefaultClientAuthentication} using {@link VaultProperties} and
* {@link RestTemplate}.
*
* @param properties must not be {@literal null}
* @param restTemplate must not be {@literal null}
*/
public DefaultClientAuthentication(VaultProperties properties,
RestTemplate restTemplate) {
Assert.notNull(properties, "VaultProperties must not be null");
Assert.notNull(restTemplate, "RestTemplate must not be null");
this.properties = properties;
this.restTemplate = restTemplate;
this.appIdUserIdMechanism = null;
}
/**
* Creates a {@link DefaultClientAuthentication} using {@link VaultProperties} and
* {@link RestTemplate} for AppId authentication.
*
* @param properties must not be {@literal null}
* @param restTemplate must not be {@literal null}
* @param appIdUserIdMechanism must not be {@literal null}
*/
public DefaultClientAuthentication(VaultProperties properties,
RestTemplate restTemplate, AppIdUserIdMechanism appIdUserIdMechanism) {
Assert.notNull(properties, "VaultProperties must not be null");
Assert.notNull(restTemplate, "RestTemplate must not be null");
Assert.notNull(appIdUserIdMechanism, "AppIdUserIdMechanism must not be null");
this.properties = properties;
this.restTemplate = restTemplate;
this.appIdUserIdMechanism = appIdUserIdMechanism;
}
@Override
public VaultToken login() {
if (properties.getAuthentication() == VaultProperties.AuthenticationMethod.APPID
&& appIdUserIdMechanism != null) {
VaultProperties.AppIdProperties appId = properties.getAppId();
return createTokenUsingAppId(new AppIdTuple(properties.getApplicationName(),
appIdUserIdMechanism.createUserId()), appId);
}
throw new UnsupportedOperationException(
String.format("Cannot create a token for auth method %s",
properties.getAuthentication()));
}
private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple,
VaultProperties.AppIdProperties appId) {
String url = buildUrl();
Map<String, String> variables = new HashMap<>();
variables.put("backend", "auth/" + appId.getAppIdPath());
variables.put("key", "login");
Map<String, String> login = getAppIdLogin(appIdTuple);
try {
ResponseEntity<VaultResponse> response = restTemplate.postForEntity(url,
new HttpEntity<>(login), VaultResponse.class, variables);
HttpStatus status = response.getStatusCode();
if (!status.is2xxSuccessful()) {
throw new IllegalStateException("Cannot login using app-id");
}
VaultResponse body = response.getBody();
String token = (String) body.getAuth().get("client_token");
log.debug("Login successful using AppId authentication");
return VaultToken.of(token, body.getLeaseDuration());
}
catch (HttpStatusCodeException e) {
if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
throw new IllegalStateException(
String.format("Cannot login using app-id: %s",
VaultErrorMessage.getError(e.getResponseBodyAsString())));
}
throw e;
}
}
private Map<String, String> getAppIdLogin(AppIdTuple appIdTuple) {
Map<String, String> login = new HashMap<>();
login.put("app_id", appIdTuple.getAppId());
login.put("user_id", appIdTuple.getUserId());
return login;
}
@Value
private static class AppIdTuple {
private String appId;
private String userId;
}
private String buildUrl() {
return String.format("%s://%s:%s/%s/{backend}/{key}", this.properties.getScheme(),
this.properties.getHost(), this.properties.getPort(), API_VERSION);
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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 org.springframework.util.Assert;
/**
* Static Token-based client authentication method.
*
* @author Mark Paluch
*/
class TokenClientAuthentication extends ClientAuthentication {
private final VaultProperties vaultProperties;
TokenClientAuthentication(VaultProperties vaultProperties) {
Assert.notNull(vaultProperties);
Assert.isTrue(
vaultProperties
.getAuthentication() == VaultProperties.AuthenticationMethod.TOKEN,
String.format("Authentication must be Token but is %s",
vaultProperties.getAuthentication()));
Assert.hasText(vaultProperties.getToken(), "Token must not be empty");
this.vaultProperties = vaultProperties;
}
@Override
public VaultToken login() {
return VaultToken.of(vaultProperties.getToken());
}
}

View File

@@ -16,12 +16,9 @@
package org.springframework.cloud.vault;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -54,15 +51,10 @@ public class VaultBootstrapConfiguration {
RestTemplate restTemplate = new RestTemplate(
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
VaultClient vaultClient = new VaultClient(vaultProperties());
vaultClient.setRest(restTemplate);
VaultProperties vaultProperties = vaultProperties();
Map<String, AppIdUserIdMechanism> appIdUserIdMechanisms = applicationContext
.getBeansOfType(AppIdUserIdMechanism.class);
if (!appIdUserIdMechanisms.isEmpty()) {
vaultClient.setAppIdUserIdMechanism(
appIdUserIdMechanisms.values().iterator().next());
}
VaultClient vaultClient = new VaultClient(vaultProperties);
vaultClient.setRest(restTemplate);
return vaultClient;
}
@@ -101,7 +93,7 @@ public class VaultBootstrapConfiguration {
/**
* Wrapper for {@link ClientHttpRequestFactory} to not expose the bean globally.
*/
static class ClientFactoryWrapper implements InitializingBean, DisposableBean {
public static class ClientFactoryWrapper implements InitializingBean, DisposableBean {
private final ClientHttpRequestFactory clientHttpRequestFactory;

View File

@@ -17,11 +17,8 @@ package org.springframework.cloud.vault;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -34,7 +31,6 @@ import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import lombok.Setter;
import lombok.Value;
import lombok.extern.apachecommons.CommonsLog;
/**
@@ -53,9 +49,6 @@ public class VaultClient {
@Setter
private RestTemplate rest = new RestTemplate();
@Setter
private AppIdUserIdMechanism appIdUserIdMechanism;
private final VaultProperties properties;
public VaultClient(VaultProperties properties) {
@@ -121,8 +114,7 @@ public class VaultClient {
}
else if (status != null) {
log.warn(String.format("Could not locate PropertySource: Status %d %s",
status.value(),
getErrorMessage(error, errorBody)));
status.value(), getErrorMessage(error, errorBody)));
}
else {
log.warn(String.format("Could not locate PropertySource: %s",
@@ -138,10 +130,9 @@ public class VaultClient {
return Collections.emptyMap();
}
protected String getErrorMessage(Exception error, String errorBody) {
return errorBody == null
? error == null ? "unknown reason" : error.getMessage()
: errorBody;
private String getErrorMessage(Exception error, String errorBody) {
return errorBody == null ? error == null ? "unknown reason" : error.getMessage()
: VaultErrorMessage.getError(errorBody);
}
private HttpHeaders createHeaders(VaultToken vaultToken) {
@@ -151,77 +142,8 @@ public class VaultClient {
return headers;
}
/**
* Creates a token using a configured authentication mechanism.
*
* @return the {@link VaultToken}.
*/
public VaultToken createToken() {
if (properties.getAuthentication() == AuthenticationMethod.APPID
&& appIdUserIdMechanism != null) {
AppIdProperties appId = properties.getAppId();
return createTokenUsingAppId(new AppIdTuple(properties.getApplicationName(),
appIdUserIdMechanism.createUserId()), appId);
}
throw new UnsupportedOperationException(
String.format("Cannot create a token for auth method %s",
properties.getAuthentication()));
}
private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple,
AppIdProperties appId) {
String url = buildUrl();
Map<String, String> variables = new HashMap<>();
variables.put("backend", "auth/" + appId.getAppIdPath());
variables.put("key", "login");
Map<String, String> login = getAppIdLogin(appIdTuple);
try {
ResponseEntity<VaultResponse> response = this.rest.exchange(url,
HttpMethod.POST, new HttpEntity<>(login), VaultResponse.class,
variables);
HttpStatus status = response.getStatusCode();
if (!status.is2xxSuccessful()) {
throw new IllegalStateException("Cannot login using app-id");
}
VaultResponse body = response.getBody();
String token = (String) body.getAuth().get("client_token");
return VaultToken.of(token, body.getLeaseDuration());
}
catch (HttpClientErrorException e) {
if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
throw new IllegalStateException(String.format(
"Cannot login using app-id: %s", e.getResponseBodyAsString()));
}
throw e;
}
}
private Map<String, String> getAppIdLogin(AppIdTuple appIdTuple) {
Map<String, String> login = new HashMap<>();
login.put("app_id", appIdTuple.getAppId());
login.put("user_id", appIdTuple.getUserId());
return login;
}
private String buildUrl() {
return String.format("%s://%s:%s/%s/{backend}/{key}", this.properties.getScheme(),
this.properties.getHost(), this.properties.getPort(), API_VERSION);
}
@Value
private static class AppIdTuple {
private String appId;
private String userId;
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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 java.io.IOException;
import java.util.Collection;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Utility to obtain a Vault error message.
*
* @author Mark Paluch
*/
class VaultErrorMessage {
private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* Obtain the error message from a JSON response.
*
* @param json
* @return
*/
static String getError(String json) {
if (json.contains("\"errors\":")) {
try {
Map<String, Object> map = OBJECT_MAPPER.readValue(json.getBytes(),
Map.class);
if (map.containsKey("errors")) {
Collection<String> errors = (Collection<String>) map.get("errors");
if (errors.size() == 1) {
return errors.iterator().next();
}
return errors.toString();
}
}
catch (IOException o_O) {
// ignore
}
}
return json;
}
}

View File

@@ -28,6 +28,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.cloud.vault.util.Settings;
import org.springframework.web.client.RestTemplate;
/**
* Integration tests for {@link VaultClient} using various UserIds.
@@ -52,14 +53,15 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
@Test
public void loginUsingIpAddressShouldCreateAToken() throws Exception {
VaultClient vaultClient = new VaultClient(
prepareAppIdAuthenticationMethod(AppIdProperties.IP_ADDRESS, "myapp"));
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod(
AppIdProperties.IP_ADDRESS, "myapp");
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createVaultProperties());
vaultClient.setRest(
TestRestTemplateFactory.create(Settings.createVaultProperties()));
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
vaultProperties, restTemplate, new IpAddressUserId());
vaultClient.setAppIdUserIdMechanism(new IpAddressUserId());
assertThat(vaultClient.createToken()).isNotNull();
assertThat(clientAuthentication.login()).isNotNull();
}
@Test
@@ -67,11 +69,14 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod("my-user-id",
"myapp");
VaultClient vaultClient = new VaultClient(vaultProperties);
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
vaultClient.setAppIdUserIdMechanism(new StaticUserId(vaultProperties));
assertThat(vaultClient.createToken()).isNotNull();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createVaultProperties());
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
vaultProperties, restTemplate, new StaticUserId(vaultProperties));
assertThat(clientAuthentication.login()).isNotNull();
}
@Test
@@ -79,11 +84,14 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod(
AppIdProperties.MAC_ADDRESS, "myapp");
VaultClient vaultClient = new VaultClient(vaultProperties);
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
vaultClient.setAppIdUserIdMechanism(new MacAddressUserId(vaultProperties));
assertThat(vaultClient.createToken()).isNotNull();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createVaultProperties());
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
vaultProperties, restTemplate, new MacAddressUserId(vaultProperties));
assertThat(clientAuthentication.login()).isNotNull();
}
@Test
@@ -96,11 +104,11 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
AppIdProperties.IP_ADDRESS, "myapp");
vaultProperties.setApplicationName("foobar");
VaultClient vaultClient = new VaultClient(vaultProperties);
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
vaultClient.setAppIdUserIdMechanism(new MacAddressUserId(vaultProperties));
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
vaultProperties, TestRestTemplateFactory.create(vaultProperties),
new MacAddressUserId(vaultProperties));
vaultClient.createToken();
clientAuthentication.login();
fail("Missing IllegalStateException");
}