From 2db19ca19ae628d73e55c67a59d1d8672e088df0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 13 Jul 2017 22:28:27 +0200 Subject: [PATCH] Introduce VaultEndpointProvider to allow usage of multiple endpoints within an application. Closes gh-113. --- .../vault/client/ReactiveVaultClients.java | 20 +++- .../client/SimpleVaultEndpointProvider.java | 50 ++++++++++ .../vault/client/VaultClients.java | 96 +++++++++++++++---- .../vault/client/VaultEndpointProvider.java | 33 +++++++ .../config/AbstractVaultConfiguration.java | 22 ++++- .../vault/core/ReactiveVaultTemplate.java | 21 +++- .../vault/core/VaultTemplate.java | 33 +++++-- 7 files changed, 238 insertions(+), 37 deletions(-) create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/client/SimpleVaultEndpointProvider.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/client/VaultEndpointProvider.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java index 3ce69d0f..250f2b2c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java @@ -49,12 +49,28 @@ public class ReactiveVaultClients { */ public static WebClient createWebClient(VaultEndpoint endpoint, ClientHttpConnector connector) { + return createWebClient(SimpleVaultEndpointProvider.of(endpoint), connector); + } - Assert.notNull(endpoint, "VaultEndpoint must not be null"); + /** + * Create a {@link WebClient} configured with {@link VaultEndpoint} and + * {@link ClientHttpConnector}. The client accepts relative URIs without a leading + * slash that are expanded to use {@link VaultEndpoint}. + *

+ * Requires Jackson 2 for Object-to-JSON mapping. + * + * @param endpointProvider must not be {@literal null}. + * @param connector must not be {@literal null}. + * @return the configured {@link WebClient}. + */ + public static WebClient createWebClient(VaultEndpointProvider endpointProvider, + ClientHttpConnector connector) { + + Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null"); Assert.notNull(connector, "ClientHttpConnector must not be null"); UriBuilderFactory uriBuilderFactory = VaultClients - .createUriBuilderFactory(endpoint); + .createUriBuilderFactory(endpointProvider); ExchangeStrategies strategies = ExchangeStrategies.builder() .codecs(configurer -> { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/SimpleVaultEndpointProvider.java b/spring-vault-core/src/main/java/org/springframework/vault/client/SimpleVaultEndpointProvider.java new file mode 100644 index 00000000..ca00a9fb --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/SimpleVaultEndpointProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 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.vault.client; + +import org.springframework.util.Assert; + +/** + * {@link VaultEndpointProvider} returning a static {@link VaultEndpoint}. + * + * @author Mark Paluch + * @since 1.1 + */ +public class SimpleVaultEndpointProvider implements VaultEndpointProvider { + + private final VaultEndpoint endpoint; + + private SimpleVaultEndpointProvider(VaultEndpoint endpoint) { + this.endpoint = endpoint; + } + + /** + * Creates a new {@link VaultEndpointProvider} given {@link VaultEndpoint}. + * + * @param endpoint must not be {@literal null}. + */ + public static VaultEndpointProvider of(VaultEndpoint endpoint) { + + Assert.notNull(endpoint, "VaultEndpoint must not be null"); + + return new SimpleVaultEndpointProvider(endpoint); + } + + @Override + public VaultEndpoint getVaultEndpoint() { + return endpoint; + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java index 21478e8b..50c8260d 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java @@ -32,6 +32,8 @@ import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.DefaultUriTemplateHandler; import org.springframework.web.util.UriBuilder; import org.springframework.web.util.UriBuilderFactory; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; /** * Vault Client factory to create {@link RestTemplate} configured to the needs of @@ -63,11 +65,36 @@ public class VaultClients { */ public static RestTemplate createRestTemplate(VaultEndpoint endpoint, ClientHttpRequestFactory requestFactory) { + return createRestTemplate(SimpleVaultEndpointProvider.of(endpoint), + requestFactory); + } + + /** + * Create a {@link RestTemplate} configured with {@link VaultEndpointProvider} and + * {@link ClientHttpRequestFactory}. The template accepts relative URIs without a + * leading slash that are expanded to use {@link VaultEndpoint}. {@link RestTemplate} + * is configured with a {@link ClientHttpRequestInterceptor} to enforce serialization + * to a byte array prior continuing the request. Eager serialization leads to a known + * request body size that is required to send a + * {@link org.springframework.http.HttpHeaders#CONTENT_LENGTH} request header. + * Otherwise, Vault will deny body processing. + *

+ * Requires Jackson 2 for Object-to-JSON mapping. + * + * @param endpointProvider must not be {@literal null}. + * @param requestFactory must not be {@literal null}. + * @return the {@link RestTemplate}. + * @see org.springframework.http.client.Netty4ClientHttpRequestFactory + * @see MappingJackson2HttpMessageConverter + * @since 1.1 + */ + public static RestTemplate createRestTemplate(VaultEndpointProvider endpointProvider, + ClientHttpRequestFactory requestFactory) { RestTemplate restTemplate = createRestTemplate(); restTemplate.setRequestFactory(requestFactory); - restTemplate.setUriTemplateHandler(createUriTemplateHandler(endpoint)); + restTemplate.setUriTemplateHandler(createUriBuilderFactory(endpointProvider)); return restTemplate; } @@ -102,26 +129,29 @@ public class VaultClients { } private static DefaultUriTemplateHandler createUriTemplateHandler( - VaultEndpoint endpoint) { + VaultEndpointProvider endpointProvider) { - String baseUrl = String.format("%s://%s:%s/%s/", endpoint.getScheme(), - endpoint.getHost(), endpoint.getPort(), "v1"); - - DefaultUriTemplateHandler defaultUriTemplateHandler = new PrefixAwareUriTemplateHandler(); - defaultUriTemplateHandler.setBaseUrl(baseUrl); - return defaultUriTemplateHandler; + return new PrefixAwareUriTemplateHandler(endpointProvider); } - public static UriBuilderFactory createUriBuilderFactory(VaultEndpoint endpoint) { - - String baseUrl = String.format("%s://%s:%s/%s/", endpoint.getScheme(), - endpoint.getHost(), endpoint.getPort(), "v1"); - - return new PrefixAwareUriBuilderFactory(baseUrl); + public static UriBuilderFactory createUriBuilderFactory( + VaultEndpointProvider endpointProvider) { + return new PrefixAwareUriBuilderFactory(endpointProvider); } public static class PrefixAwareUriTemplateHandler extends DefaultUriTemplateHandler { + @Nullable + private final VaultEndpointProvider endpointProvider; + + public PrefixAwareUriTemplateHandler() { + this.endpointProvider = null; + } + + public PrefixAwareUriTemplateHandler(VaultEndpointProvider endpointProvider) { + this.endpointProvider = endpointProvider; + } + @Override protected URI expandInternal(String uriTemplate, Map uriVariables) { return super.expandInternal(prepareUriTemplate(getBaseUrl(), uriTemplate), @@ -133,6 +163,18 @@ public class VaultClients { return super.expandInternal(prepareUriTemplate(getBaseUrl(), uriTemplate), uriVariables); } + + @Override + public String getBaseUrl() { + + if (endpointProvider != null) { + + VaultEndpoint endpoint = endpointProvider.getVaultEndpoint(); + return toBaseUri(endpoint); + } + + return super.getBaseUrl(); + } } /** @@ -140,22 +182,34 @@ public class VaultClients { */ public static class PrefixAwareUriBuilderFactory extends DefaultUriBuilderFactory { - private final String baseUri; + private final VaultEndpointProvider endpointProvider; - public PrefixAwareUriBuilderFactory(String baseUri) { - super(baseUri); - this.baseUri = baseUri; + public PrefixAwareUriBuilderFactory(VaultEndpointProvider endpointProvider) { + this.endpointProvider = endpointProvider; } @Override public UriBuilder uriString(String uriTemplate) { - return super.uriString(prepareUriTemplate(baseUri, uriTemplate)); + + VaultEndpoint endpoint = endpointProvider.getVaultEndpoint(); + + String baseUri = toBaseUri(endpoint); + UriComponents uriComponents = UriComponentsBuilder.fromUriString( + prepareUriTemplate(baseUri, uriTemplate)).build(); + + return UriComponentsBuilder.fromUriString(baseUri).uriComponents( + uriComponents); } } + private static String toBaseUri(VaultEndpoint endpoint) { + return endpoint.getScheme() + "://" + endpoint.getHost() + ":" + + endpoint.getPort() + "/v1"; + } + /** - * Strip/add leading slashes from {@code uriTemplate} depending on wheter the base url - * has a trailing slash. + * Strip/add leading slashes from {@code uriTemplate} depending on wheter the base + * url* has a trailing slash. * * @param uriTemplate * @return diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultEndpointProvider.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultEndpointProvider.java new file mode 100644 index 00000000..973145b6 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultEndpointProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 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.vault.client; + +/** + * Component that provides a {@link VaultEndpoint}. Allows to use a different + * {@link VaultEndpoint} for each Vault request. + * + * @author Mark Paluch + * @since 1.1 + */ +public interface VaultEndpointProvider { + + /** + * Provides access to {@link VaultEndpoint}. + * + * @return the {@link VaultEndpoint}. + */ + VaultEndpoint getVaultEndpoint(); +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java index 20ee52c9..44f9769a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java @@ -29,8 +29,10 @@ import org.springframework.util.Assert; import org.springframework.vault.authentication.ClientAuthentication; import org.springframework.vault.authentication.LifecycleAwareSessionManager; import org.springframework.vault.authentication.SessionManager; +import org.springframework.vault.client.SimpleVaultEndpointProvider; import org.springframework.vault.client.VaultClients; import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.client.VaultEndpointProvider; import org.springframework.vault.core.VaultTemplate; import org.springframework.vault.core.lease.SecretLeaseContainer; import org.springframework.vault.support.ClientOptions; @@ -54,6 +56,15 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw */ public abstract VaultEndpoint vaultEndpoint(); + /** + * @return a {@link VaultEndpointProvider} returning the value of + * {@link #vaultEndpoint()}. + * @since 1.1 + */ + public VaultEndpointProvider vaultEndpointProvider() { + return SimpleVaultEndpointProvider.of(vaultEndpoint()); + } + /** * Annotate with {@link Bean} in case you want to expose a * {@link ClientAuthentication} instance to the @@ -67,14 +78,15 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw * Create a {@link VaultTemplate}. * * @return the {@link VaultTemplate}. - * @see #vaultEndpoint() + * @see #vaultEndpointProvider() * @see #clientHttpRequestFactoryWrapper() * @see #sessionManager() */ @Bean public VaultTemplate vaultTemplate() { - return new VaultTemplate(vaultEndpoint(), clientHttpRequestFactoryWrapper() - .getClientHttpRequestFactory(), sessionManager()); + return new VaultTemplate(vaultEndpointProvider(), + clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(), + sessionManager()); } /** @@ -147,11 +159,11 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw * Construct a {@link RestOperations} object configured for Vault usage. * * @return the {@link RestOperations} to be used for Vault access. - * @see #vaultEndpoint() + * @see #vaultEndpointProvider() * @see #clientHttpRequestFactoryWrapper() */ public RestOperations restOperations() { - return VaultClients.createRestTemplate(vaultEndpoint(), + return VaultClients.createRestTemplate(vaultEndpointProvider(), clientHttpRequestFactoryWrapper().getClientHttpRequestFactory()); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java index 5703720e..df0ac143 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java @@ -31,7 +31,9 @@ import org.springframework.vault.VaultException; import org.springframework.vault.authentication.SessionManager; import org.springframework.vault.authentication.VaultTokenSupplier; import org.springframework.vault.client.ReactiveVaultClients; +import org.springframework.vault.client.SimpleVaultEndpointProvider; import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.client.VaultEndpointProvider; import org.springframework.vault.client.VaultHttpHeaders; import org.springframework.vault.client.VaultResponses; import org.springframework.vault.support.VaultResponse; @@ -69,8 +71,21 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations { */ public ReactiveVaultTemplate(VaultEndpoint vaultEndpoint, ClientHttpConnector connector, VaultTokenSupplier vaultTokenSupplier) { + this(SimpleVaultEndpointProvider.of(vaultEndpoint), connector, vaultTokenSupplier); + } - Assert.notNull(vaultEndpoint, "VaultEndpoint must not be null"); + /** + * Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpointProvider}, + * {@link ClientHttpConnector} and {@link VaultTokenSupplier}. + * + * @param endpointProvider must not be {@literal null}. + * @param connector must not be {@literal null}. + * @param vaultTokenSupplier must not be {@literal null}. + */ + public ReactiveVaultTemplate(VaultEndpointProvider endpointProvider, + ClientHttpConnector connector, VaultTokenSupplier vaultTokenSupplier) { + + Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null"); Assert.notNull(connector, "ClientHttpConnector must not be null"); Assert.notNull(vaultTokenSupplier, "AuthenticationSupplier must not be null"); @@ -82,10 +97,10 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations { }).build(); })); - this.statelessClient = ReactiveVaultClients.createWebClient(vaultEndpoint, + this.statelessClient = ReactiveVaultClients.createWebClient(endpointProvider, connector); this.sessionClient = ReactiveVaultClients - .createWebClient(vaultEndpoint, connector).mutate().filter(filter) + .createWebClient(endpointProvider, connector).mutate().filter(filter) .build(); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java index e5672633..6a3de550 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java @@ -32,8 +32,10 @@ import org.springframework.util.Assert; import org.springframework.vault.authentication.ClientAuthentication; import org.springframework.vault.authentication.SessionManager; import org.springframework.vault.authentication.SimpleSessionManager; +import org.springframework.vault.client.SimpleVaultEndpointProvider; import org.springframework.vault.client.VaultClients; import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.client.VaultEndpointProvider; import org.springframework.vault.client.VaultHttpHeaders; import org.springframework.vault.client.VaultResponses; import org.springframework.vault.support.VaultResponse; @@ -77,7 +79,10 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - this.sessionTemplate = createSessionTemplate(vaultEndpoint, requestFactory); + VaultEndpointProvider endpointProvider = SimpleVaultEndpointProvider + .of(vaultEndpoint); + + this.sessionTemplate = createSessionTemplate(endpointProvider, requestFactory); this.plainTemplate = VaultClients.createRestTemplate(vaultEndpoint, requestFactory); } @@ -93,8 +98,24 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa public VaultTemplate(VaultEndpoint vaultEndpoint, ClientHttpRequestFactory clientHttpRequestFactory, SessionManager sessionManager) { + this(SimpleVaultEndpointProvider.of(vaultEndpoint), clientHttpRequestFactory, + sessionManager); + } - Assert.notNull(vaultEndpoint, "VaultEndpoint must not be null"); + /** + * Create a new {@link VaultTemplate} with a {@link VaultEndpointProvider}, + * {@link ClientHttpRequestFactory} and {@link SessionManager}. + * + * @param endpointProvider must not be {@literal null}. + * @param clientHttpRequestFactory must not be {@literal null}. + * @param sessionManager must not be {@literal null}. + * @since 1.1 + */ + public VaultTemplate(VaultEndpointProvider endpointProvider, + ClientHttpRequestFactory clientHttpRequestFactory, + SessionManager sessionManager) { + + Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null"); Assert.notNull(clientHttpRequestFactory, "ClientHttpRequestFactory must not be null"); Assert.notNull(sessionManager, "SessionManager must not be null"); @@ -102,16 +123,16 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa this.sessionManager = sessionManager; this.dedicatedSessionManager = false; - this.sessionTemplate = createSessionTemplate(vaultEndpoint, + this.sessionTemplate = createSessionTemplate(endpointProvider, clientHttpRequestFactory); - this.plainTemplate = VaultClients.createRestTemplate(vaultEndpoint, + this.plainTemplate = VaultClients.createRestTemplate(endpointProvider, clientHttpRequestFactory); } - private RestTemplate createSessionTemplate(VaultEndpoint endpoint, + private RestTemplate createSessionTemplate(VaultEndpointProvider endpointProvider, ClientHttpRequestFactory requestFactory) { - RestTemplate restTemplate = VaultClients.createRestTemplate(endpoint, + RestTemplate restTemplate = VaultClients.createRestTemplate(endpointProvider, requestFactory); restTemplate.getInterceptors().add(