From 87bb19fc825a3cc5a1093cca93d6498bb78b401f 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. --- .../client/SimpleVaultEndpointProvider.java | 50 ++++++++++++++++ .../vault/client/VaultClients.java | 60 ++++++++++++++++--- .../vault/client/VaultEndpointProvider.java | 33 ++++++++++ .../config/AbstractVaultConfiguration.java | 22 +++++-- .../vault/core/VaultTemplate.java | 33 ++++++++-- 5 files changed, 179 insertions(+), 19 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/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 7ac5f8f5..3b72665c 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 @@ -63,11 +63,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(createUriTemplateHandler(endpointProvider)); return restTemplate; } @@ -109,18 +134,23 @@ 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 class PrefixAwareUriTemplateHandler extends DefaultUriTemplateHandler { + 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(uriTemplate), uriVariables); @@ -131,6 +161,20 @@ public class VaultClients { return super.expandInternal(prepareUriTemplate(uriTemplate), uriVariables); } + @Override + public String getBaseUrl() { + + if (endpointProvider != null) { + + VaultEndpoint endpoint = endpointProvider.getVaultEndpoint(); + + return endpoint.getScheme() + "://" + endpoint.getHost() + ":" + + endpoint.getPort() + "/v1"; + } + + return super.getBaseUrl(); + } + /** * Strip/add leading slashes from {@code uriTemplate} depending on whetner the * base url has a trailing slash. 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/VaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java index 2fffddd1..1d151fd6 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 @@ -36,8 +36,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; @@ -89,7 +91,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); } @@ -105,8 +110,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"); @@ -114,16 +135,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(new ClientHttpRequestInterceptor() {