diff --git a/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java b/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java index f0e8a8e6..ada04a31 100644 --- a/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java +++ b/spring-cloud-vault-config-aws/src/test/java/org/springframework/cloud/vault/config/aws/AwsSecretIntegrationTests.java @@ -91,5 +91,4 @@ public class AwsSecretIntegrationTests extends AbstractIntegrationTests { assertThat(secretProperties).containsKeys("cloud.aws.credentials.accessKey", "cloud.aws.credentials.secretKey"); } - } diff --git a/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java b/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java index 746750ab..af242183 100644 --- a/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java +++ b/spring-cloud-vault-config-rabbitmq/src/test/java/org/springframework/cloud/vault/config/rabbitmq/RabbitMqSecretIntegrationTests.java @@ -98,5 +98,4 @@ public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests { assertThat(secretProperties).containsKeys("spring.rabbitmq.username", "spring.rabbitmq.password"); } - } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java index 4d439179..6d99551c 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapConfiguration.java @@ -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 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 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 diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java index 1447782c..6884931f 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySource.java @@ -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 { private final VaultProperties vaultProperties; private final SecureBackendAccessor secureBackendAccessor; private final Map 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 { 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 { 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 { "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(); } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java index 8ff382ad..41f5faec 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java @@ -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 backendAccessors; + private final VaultClient vaultClient; + private final ClientAuthentication clientAuthentication; + private final VaultProperties properties; + private final VaultGenericBackendProperties genericBackendProperties; + private final Collection 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 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 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 buildContexts(ConfigurableEnvironment env) { + private List buildContexts(ConfigurableEnvironment env) { - String appName = env.getProperty("spring.application.name"); - List profiles = Arrays.asList(env.getActiveProfiles()); - List contexts = new ArrayList<>(); + String appName = env.getProperty("spring.application.name"); + List profiles = Arrays.asList(env.getActiveProfiles()); + List 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 contexts = buildContexts(environment); - for (String propertySourceContext : contexts) { + List 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 contexts, String baseContext, - List profiles) { + private void addProfiles(List contexts, String baseContext, List 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); + } + } + } } \ No newline at end of file diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java index 6f65889e..da78f53f 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/AppIdAuthenticationIntegrationTests.java @@ -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() { diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientAuthentication.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientAuthentication.java new file mode 100644 index 00000000..6810dc56 --- /dev/null +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientAuthentication.java @@ -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); + } +} diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java new file mode 100644 index 00000000..be6eb8d3 --- /dev/null +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java @@ -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 variables = new HashMap<>(); + variables.put("backend", "auth/" + appId.getAppIdPath()); + variables.put("key", "login"); + + Map login = getAppIdLogin(appIdTuple); + + try { + ResponseEntity 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 getAppIdLogin(AppIdTuple appIdTuple) { + + Map 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); + } +} diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/TokenClientAuthentication.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/TokenClientAuthentication.java new file mode 100644 index 00000000..b89a4db7 --- /dev/null +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/TokenClientAuthentication.java @@ -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()); + } +} diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java index 3da3fe09..3eae7de1 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java @@ -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 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; diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java index 08b7f48f..c91c2f24 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java @@ -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 variables = new HashMap<>(); - variables.put("backend", "auth/" + appId.getAppIdPath()); - variables.put("key", "login"); - - Map login = getAppIdLogin(appIdTuple); - - try { - ResponseEntity 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 getAppIdLogin(AppIdTuple appIdTuple) { - - Map 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; - } } diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultErrorMessage.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultErrorMessage.java new file mode 100644 index 00000000..fe2ce115 --- /dev/null +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultErrorMessage.java @@ -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 map = OBJECT_MAPPER.readValue(json.getBytes(), + Map.class); + if (map.containsKey("errors")) { + + Collection errors = (Collection) map.get("errors"); + if (errors.size() == 1) { + return errors.iterator().next(); + } + return errors.toString(); + } + + } + catch (IOException o_O) { + // ignore + } + } + return json; + } +} diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java index f5d3e7d3..707645b5 100644 --- a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java @@ -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"); }