Refactor ClientAuthentication into own component
The authentication API can be provided into VaultClient by implementing a ClientAuthentication.
This commit is contained in:
@@ -91,5 +91,4 @@ public class AwsSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
assertThat(secretProperties).containsKeys("cloud.aws.credentials.accessKey",
|
||||
"cloud.aws.credentials.secretKey");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,5 +98,4 @@ public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
assertThat(secretProperties).containsKeys("spring.rabbitmq.username",
|
||||
"spring.rabbitmq.password");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user