Initial reactive Vault client support.

Map<String, String> data = new HashMap<>();
data.put("firstname", "Walter");
data.put("password", "Secret");

Mono<VaultResponseSupport<Person>> response = vaultOperations.write("secret/mykey", data)
    .then(vaultOperations.read("secret/mykey", Person.class));

See gh-25.
This commit is contained in:
Mark Paluch
2017-07-06 20:03:43 +02:00
parent 1f5edfc3f5
commit ef1fc0761c
28 changed files with 1965 additions and 244 deletions

View File

@@ -40,6 +40,7 @@ import org.springframework.web.client.RestOperations;
*
* @author Mark Paluch
* @since 2.0
* @see AuthenticationSteps
*/
public class AuthenticationStepsExecutor implements ClientAuthentication {

View File

@@ -0,0 +1,192 @@
/*
* 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.authentication;
import java.util.List;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpEntity;
import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.AuthenticationSteps.HttpRequest;
import org.springframework.vault.authentication.AuthenticationSteps.HttpRequestNode;
import org.springframework.vault.authentication.AuthenticationSteps.MapStep;
import org.springframework.vault.authentication.AuthenticationSteps.Node;
import org.springframework.vault.authentication.AuthenticationSteps.OnNextStep;
import org.springframework.vault.authentication.AuthenticationSteps.SupplierStep;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
/**
* {@link VaultTokenSupplier} using {@link AuthenticationSteps} to create an
* authentication flow emitting {@link VaultToken}.
* <p>
* This class uses {@link WebClient} for non-blocking and reactive HTTP access. The
* {@link AuthenticationSteps authentication flow} is materialized as reactive sequence
* postponing execution until {@link Mono#subscribe() subscription}.
*
* @author Mark Paluch
* @since 2.0
* @see AuthenticationSteps
*/
public class AuthenticationStepsOperator implements VaultTokenSupplier {
private static final Log logger = LogFactory.getLog(AppIdAuthentication.class);
private final AuthenticationSteps chain;
private final WebClient webClient;
/**
* Create a new {@link AuthenticationStepsOperator} given {@link AuthenticationSteps}
* and {@link WebClient}.
*
* @param steps must not be {@literal null}.
* @param webClient must not be {@literal null}.
*/
public AuthenticationStepsOperator(AuthenticationSteps steps, WebClient webClient) {
Assert.notNull(steps, "AuthenticationSteps must not be null");
Assert.notNull(webClient, "WebClient must not be null");
this.chain = steps;
this.webClient = webClient;
}
@Override
@SuppressWarnings("unchecked")
public Mono<VaultToken> getVaultToken() throws VaultException {
Mono<Object> state = Mono.just(Undefinded.INSTANCE);
for (Node<?> o : chain.steps) {
if (logger.isDebugEnabled()) {
logger.debug(String
.format("Executing %s with current state %s", o, state));
}
if (o instanceof HttpRequestNode) {
state = state.flatMap(stateObject -> doHttpRequest(
(HttpRequestNode<Object>) o, stateObject));
}
if (o instanceof AuthenticationSteps.MapStep) {
state = state.map(stateObject -> doMapStep((MapStep<Object, Object>) o,
stateObject));
}
if (o instanceof OnNextStep) {
state = state.doOnNext(stateObject -> doOnNext((OnNextStep<Object>) o,
stateObject));
}
if (o instanceof AuthenticationSteps.SupplierStep<?>) {
state = state
.map(stateObject -> doSupplierStep((SupplierStep<Object>) o));
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Executed %s with current state %s", o, state));
}
}
return state
.map(stateObject -> {
if (stateObject instanceof VaultToken) {
return (VaultToken) stateObject;
}
if (stateObject instanceof VaultResponse) {
VaultResponse response = (VaultResponse) stateObject;
return LoginTokenUtil.from(response.getAuth());
}
throw new IllegalStateException(
String.format(
"Cannot retrieve VaultToken from authentication chain. Got instead %s",
stateObject));
});
}
private static Object doSupplierStep(SupplierStep<Object> supplierStep) {
return supplierStep.get();
}
private static Object doMapStep(MapStep<Object, Object> o, Object state) {
return o.apply(state);
}
private static Object doOnNext(OnNextStep<Object> o, Object state) {
return o.apply(state);
}
private Mono<Object> doHttpRequest(HttpRequestNode<Object> step, Object state) {
HttpRequest<Object> definition = step.getDefinition();
HttpEntity<?> entity = getEntity(definition.getEntity(), state);
RequestBodySpec spec;
if (definition.getUri() == null) {
spec = webClient.method(definition.getMethod()).uri(
definition.getUriTemplate(), (Object[]) definition.getUrlVariables());
}
else {
spec = webClient.method(definition.getMethod()).uri(definition.getUri());
}
for (Entry<String, List<String>> header : entity.getHeaders().entrySet()) {
spec = spec.header(header.getKey(), header.getValue().get(0));
}
if (entity.getBody() != null && !entity.getBody().equals(Undefinded.INSTANCE)) {
return spec.syncBody(entity.getBody()).retrieve()
.bodyToMono(definition.getResponseType());
}
return spec.retrieve().bodyToMono(definition.getResponseType());
}
private static HttpEntity<?> getEntity(HttpEntity<?> entity, Object state) {
if (entity == null) {
return state == null ? HttpEntity.EMPTY : new HttpEntity<>(state);
}
if (entity.getBody() == null && state != null) {
return new HttpEntity<>(state, entity.getHeaders());
}
return entity;
}
static class Undefinded {
static final Undefinded INSTANCE = new Undefinded();
private Undefinded() {
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.authentication;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import reactor.core.publisher.Mono;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
/**
* Default implementation of {@link VaultTokenSupplier} caching the {@link VaultToken}
* from a delegate {@link VaultTokenSupplier}.
*
* @author Mark Paluch
* @since 2.0
* @see VaultTokenSupplier
* @see VaultToken
*/
public class CachingVaultTokenSupplier implements VaultTokenSupplier {
private final static Mono<VaultToken> EMPTY = Mono.empty();
private final VaultTokenSupplier clientAuthentication;
private final AtomicReference<Mono<VaultToken>> tokenRef = new AtomicReference<>(
EMPTY);
private CachingVaultTokenSupplier(VaultTokenSupplier clientAuthentication) {
this.clientAuthentication = clientAuthentication;
}
/**
* Creates a new {@link CachingVaultTokenSupplier} given a {@link VaultTokenSupplier
* delegate supplier}.
*
* @param delegate must not be {@literal null}.
* @return the {@link CachingVaultTokenSupplier} for a {@link VaultTokenSupplier
* delegate supplier}.
*/
public static CachingVaultTokenSupplier of(VaultTokenSupplier delegate) {
return new CachingVaultTokenSupplier(delegate);
}
@Override
public Mono<VaultToken> getVaultToken() throws VaultException {
if (Objects.equals(tokenRef.get(), EMPTY)) {
tokenRef.compareAndSet(EMPTY, clientAuthentication.getVaultToken().cache());
}
return tokenRef.get();
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.authentication;
import reactor.core.publisher.Mono;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
/**
* {@link VaultTokenSupplier} provides a {@link VaultToken} to be used for authenticated
* Vault access. Implementing classes usually use a login method to login and return a
* {@link VaultToken} implementing {@link #getVaultToken()}.
*
* @author Mark Paluch
*/
@FunctionalInterface
public interface VaultTokenSupplier {
/**
* Return a {@link VaultToken}. This can declare a Vault login flow to obtain a
* {@link VaultToken token}.
*
* @return a {@link Mono} with the {@link VaultToken}.
*/
Mono<VaultToken> getVaultToken() throws VaultException;
}

View File

@@ -0,0 +1,76 @@
/*
* 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.core.codec.ByteArrayDecoder;
import org.springframework.core.codec.ByteArrayEncoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.codec.CodecConfigurer.CustomCodecs;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriBuilderFactory;
/**
* Vault Client factory to create {@link WebClient} configured to the needs of accessing
* Vault.
*
* @author Mark Paluch
* @since 2.0
*/
public class ReactiveVaultClients {
/**
* 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}.
* <p>
* Requires Jackson 2 for Object-to-JSON mapping.
*
* @param endpoint must not be {@literal null}.
* @param connector must not be {@literal null}.
* @return the configured {@link WebClient}.
*/
public static WebClient createWebClient(VaultEndpoint endpoint,
ClientHttpConnector connector) {
Assert.notNull(endpoint, "VaultEndpoint must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
UriBuilderFactory uriBuilderFactory = VaultClients
.createUriBuilderFactory(endpoint);
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
CustomCodecs cc = configurer.customCodecs();
cc.decoder(new ByteArrayDecoder());
cc.decoder(new Jackson2JsonDecoder());
cc.decoder(StringDecoder.allMimeTypes(false));
cc.encoder(new ByteArrayEncoder());
cc.encoder(new Jackson2JsonEncoder());
}).build();
return WebClient.builder().uriBuilderFactory(uriBuilderFactory)
.exchangeStrategies(strategies).clientConnector(connector).build();
}
}

View File

@@ -27,7 +27,10 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
/**
* Vault Client factory to create {@link RestTemplate} configured to the needs of
@@ -108,54 +111,82 @@ public class VaultClients {
return defaultUriTemplateHandler;
}
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 class PrefixAwareUriTemplateHandler extends DefaultUriTemplateHandler {
@Override
protected URI expandInternal(String uriTemplate, Map<String, ?> uriVariables) {
return super.expandInternal(prepareUriTemplate(uriTemplate), uriVariables);
return super.expandInternal(prepareUriTemplate(getBaseUrl(), uriTemplate),
uriVariables);
}
@Override
protected URI expandInternal(String uriTemplate, Object... uriVariables) {
return super.expandInternal(prepareUriTemplate(uriTemplate), uriVariables);
return super.expandInternal(prepareUriTemplate(getBaseUrl(), uriTemplate),
uriVariables);
}
}
/**
* @since 2.0
*/
public static class PrefixAwareUriBuilderFactory extends DefaultUriBuilderFactory {
private final String baseUri;
public PrefixAwareUriBuilderFactory(String baseUri) {
super(baseUri);
this.baseUri = baseUri;
}
/**
* Strip/add leading slashes from {@code uriTemplate} depending on wheter the base
* url has a trailing slash.
*
* @param uriTemplate
* @return
*/
private String prepareUriTemplate(String uriTemplate) {
@Override
public UriBuilder uriString(String uriTemplate) {
return super.uriString(prepareUriTemplate(baseUri, uriTemplate));
}
}
if (getBaseUrl() != null) {
if (uriTemplate.startsWith("/") && getBaseUrl().endsWith("/")) {
return uriTemplate.substring(1);
}
/**
* Strip/add leading slashes from {@code uriTemplate} depending on wheter the base url
* has a trailing slash.
*
* @param uriTemplate
* @return
*/
static String prepareUriTemplate(String baseUrl, String uriTemplate) {
if (!uriTemplate.startsWith("/") && !getBaseUrl().endsWith("/")) {
return "/" + uriTemplate;
}
return uriTemplate;
if (baseUrl != null) {
if (uriTemplate.startsWith("/") && baseUrl.endsWith("/")) {
return uriTemplate.substring(1);
}
try {
URI uri = URI.create(uriTemplate);
if (uri.getHost() != null) {
return uriTemplate;
}
}
catch (IllegalArgumentException e) {
}
if (!uriTemplate.startsWith("/")) {
if (!uriTemplate.startsWith("/") && !baseUrl.endsWith("/")) {
return "/" + uriTemplate;
}
return uriTemplate;
}
try {
URI uri = URI.create(uriTemplate);
if (uri.getHost() != null) {
return uriTemplate;
}
}
catch (IllegalArgumentException e) {
}
if (!uriTemplate.startsWith("/")) {
return "/" + uriTemplate;
}
return uriTemplate;
}
}

View File

@@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -77,14 +78,19 @@ public abstract class VaultResponses {
Assert.notNull(e, "HttpStatusCodeException must not be null");
String message = VaultResponses.getError(e.getResponseBodyAsString());
return buildException(e.getStatusCode(), path,
VaultResponses.getError(e.getResponseBodyAsString()));
}
public static VaultException buildException(HttpStatus statusCode, String path,
String message) {
if (StringUtils.hasText(message)) {
return new VaultException(String.format("Status %s %s: %s",
e.getStatusCode(), path, message));
return new VaultException(String.format("Status %s %s: %s", statusCode, path,
message));
}
return new VaultException(String.format("Status %s %s", e.getStatusCode(), path));
return new VaultException(String.format("Status %s %s", statusCode, path));
}
/**

View File

@@ -0,0 +1,116 @@
/*
* 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.config;
import reactor.core.publisher.Mono;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
import org.springframework.vault.authentication.AuthenticationStepsOperator;
import org.springframework.vault.authentication.CachingVaultTokenSupplier;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.ReactiveVaultClients;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.support.ClientOptions;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Base class for Spring Vault configuration using JavaConfig for a reactive
* infrastructure.
* <p>
* Reactive Vault support creates a {@link VaultTokenSupplier} (for the session token)
* from the configured {@link #clientAuthentication()}. The authentication object must
* implement {@link AuthenticationStepsFactory} exposing
* {@link org.springframework.vault.authentication.AuthenticationSteps} to obtain
* authentication using reactive infrastructure.
*
* @author Mark Paluch
* @since 2.0
*/
public abstract class AbstractReactiveVaultConfiguration extends
AbstractVaultConfiguration {
/**
* Create a {@link ReactiveVaultTemplate}.
*
* @return the {@link ReactiveVaultTemplate}.
* @see #vaultEndpoint()
* @see #clientHttpConnector()
* @see #vaultTokenSupplier()
*/
@Bean
public ReactiveVaultTemplate reactiveVaultTemplate() {
return new ReactiveVaultTemplate(vaultEndpoint(), clientHttpConnector(),
vaultTokenSupplier());
}
/**
* Construct a {@link VaultTokenSupplier} using {@link #clientAuthentication()}. This
* {@link SessionManager} uses {@link #threadPoolTaskScheduler()}.
*
* @return the {@link VaultTokenSupplier} for Vault session token management.
* @see VaultTokenSupplier
* @see #clientAuthentication()
*/
@Bean
public VaultTokenSupplier vaultTokenSupplier() {
ClientAuthentication clientAuthentication = clientAuthentication();
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
if (clientAuthentication instanceof TokenAuthentication) {
TokenAuthentication authentication = (TokenAuthentication) clientAuthentication;
return () -> Mono.just(authentication.login());
}
if (clientAuthentication instanceof AuthenticationStepsFactory) {
AuthenticationStepsFactory factory = (AuthenticationStepsFactory) clientAuthentication;
WebClient webClient = ReactiveVaultClients.createWebClient(vaultEndpoint(),
clientHttpConnector());
AuthenticationStepsOperator stepsOperator = new AuthenticationStepsOperator(
factory.getAuthenticationSteps(), webClient);
return CachingVaultTokenSupplier.of(stepsOperator);
}
throw new IllegalStateException(
String.format(
"Cannot construct VaultTokenSupplier from %s. "
+ "ClientAuthentication must implement AuthenticationStepsFactory or be TokenAuthentication",
clientAuthentication));
}
/**
* Create a {@link ClientHttpConnector} configured with {@link ClientOptions} and
* {@link org.springframework.vault.support.SslConfiguration}.
*
* @return the {@link ClientHttpConnector} instance.
* @see #clientOptions()
* @see #sslConfiguration()
*/
public ClientHttpConnector clientHttpConnector() {
return ClientHttpConnectorFactory.create(clientOptions(), sslConfiguration());
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.config;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.concurrent.atomic.AtomicLong;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import reactor.ipc.netty.resources.PoolResources;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.createKeyManagerFactory;
import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.createTrustManagerFactory;
import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.hasSslConfiguration;
/**
* Factory for {@link ClientHttpConnector} that supports
* {@link ReactorClientHttpConnector}.
*
* @author Mark Paluch
* @since 2.0
*/
public class ClientHttpConnectorFactory {
private static final AtomicLong POOL_COUNTER = new AtomicLong();
/**
* Create a {@link ClientHttpConnector} for the given {@link ClientOptions} and
* {@link SslConfiguration}.
*
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new {@link ClientHttpConnector}.
*/
public static ClientHttpConnector create(ClientOptions options,
SslConfiguration sslConfiguration) {
return new ReactorClientHttpConnector(builder -> {
if (hasSslConfiguration(sslConfiguration)) {
builder.sslSupport(sslContextBuilder -> {
configureSsl(sslConfiguration, sslContextBuilder);
}).poolResources(
PoolResources.elastic("vault-http-"
+ POOL_COUNTER.incrementAndGet()));
}
builder.sslHandshakeTimeout(options.getConnectionTimeout());
builder.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
Math.toIntExact(options.getConnectionTimeout().toMillis()));
});
}
private static void configureSsl(SslConfiguration sslConfiguration,
SslContextBuilder sslContextBuilder) {
try {
if (sslConfiguration.getTrustStore() != null) {
sslContextBuilder.trustManager(createTrustManagerFactory(sslConfiguration
.getTrustStoreConfiguration()));
}
if (sslConfiguration.getKeyStore() != null) {
sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration
.getKeyStoreConfiguration()));
}
}
catch (GeneralSecurityException | IOException e) {
throw new IllegalStateException(e);
}
}
}

View File

@@ -139,7 +139,7 @@ public class ClientHttpRequestFactoryFactory {
return sslContext;
}
private static KeyManagerFactory createKeyManagerFactory(
static KeyManagerFactory createKeyManagerFactory(
KeyStoreConfiguration keyStoreConfiguration) throws GeneralSecurityException,
IOException {
@@ -158,7 +158,7 @@ public class ClientHttpRequestFactoryFactory {
return keyManagerFactory;
}
private static TrustManagerFactory createTrustManagerFactory(
static TrustManagerFactory createTrustManagerFactory(
KeyStoreConfiguration keyStoreConfiguration) throws GeneralSecurityException,
IOException {
@@ -191,7 +191,7 @@ public class ClientHttpRequestFactoryFactory {
}
}
private static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
return sslConfiguration.getTrustStore() != null
|| sslConfiguration.getKeyStore() != null;
}

View File

@@ -0,0 +1,123 @@
/*
* 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.core;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientException;
/**
* Interface that specifies a basic set of Vault operations executed on a reactive
* infrastructure, implemented by {@link ReactiveVaultTemplate}. This is the main entry
* point to interact with Vault in an authenticated and unauthenticated context.
* <p>
* {@link ReactiveVaultOperations} allows execution of callback methods. Callbacks can
* execute requests within a {@link ReactiveVaultOperations#doWithSession(Function)
* session context} and the {@link ReactiveVaultOperations#doWithVault(Function) without a
* session}.
*
* @author Mark Paluch
* @see ReactiveVaultOperations#doWithSession(Function)
* @see ReactiveVaultOperations#doWithVault(Function)
* @see WebClient
* @see VaultTemplate
* @see VaultTokenOperations
* @see org.springframework.vault.authentication.VaultTokenSupplier
*/
public interface ReactiveVaultOperations {
/**
* Read from a secret backend. Reading data using this method is suitable for secret
* backends that do not require a request body.
*
* @param path must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
Mono<VaultResponse> read(String path);
/**
* Read from a secret backend. Reading data using this method is suitable for secret
* backends that do not require a request body.
*
* @param path must not be {@literal null}.
* @param responseType must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
<T> Mono<VaultResponseSupport<T>> read(String path, Class<T> responseType);
/**
* Enumerate keys from a secret backend.
*
* @param path must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
Flux<String> list(String path);
/**
* Write to a secret backend.
*
* @param path must not be {@literal null}.
* @param body the body, may be {@literal null} if absent.
* @return the configuration data. May be empty but never {@literal null}.
*/
Mono<Void> write(String path, Object body);
/**
* Delete a path in the secret backend.
*
* @param path must not be {@literal null}.
*/
Mono<Void> delete(String path);
/**
* Executes a Vault {@link RestOperationsCallback}. Allows to interact with Vault
* using {@link org.springframework.web.client.RestOperations} without requiring a
* session.
*
* @param clientCallback the request.
* @return the {@link RestOperationsCallback} return value.
* @throws VaultException when a
* {@link org.springframework.web.client.HttpStatusCodeException} occurs.
* @throws WebClientException exceptions from
* {@link org.springframework.web.reactive.function.client.WebClient}.
*/
<V, T extends Publisher<V>> T doWithVault(
Function<WebClient, ? super T> clientCallback) throws VaultException,
WebClientException;
/**
* Executes a Vault {@link RestOperationsCallback}. Allows to interact with Vault in
* an authenticated session.
*
* @param sessionCallback the request.
* @return the {@link RestOperationsCallback} return value.
* @throws VaultException when a
* {@link org.springframework.web.client.HttpStatusCodeException} occurs.
* @throws WebClientException exceptions from
* {@link org.springframework.web.reactive.function.client.WebClient}.
*/
<V, T extends Publisher<V>> T doWithSession(
Function<WebClient, ? super T> sessionCallback) throws VaultException,
WebClientException;
}

View File

@@ -0,0 +1,219 @@
/*
* 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.core;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
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.VaultEndpoint;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.VaultResponses;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientException;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunction.ofRequestProcessor;
/**
* This class encapsulates main Vault interaction. {@link ReactiveVaultTemplate} will log
* into Vault on initialization and use the token throughout the whole lifetime.
*
* @author Mark Paluch
* @see SessionManager
*/
public class ReactiveVaultTemplate implements ReactiveVaultOperations {
private final WebClient statelessClient;
private final WebClient sessionClient;
/**
* Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpoint},
* {@link ClientHttpConnector} and {@link VaultTokenSupplier}.
*
* @param vaultEndpoint must not be {@literal null}.
* @param connector must not be {@literal null}.
* @param vaultTokenSupplier must not be {@literal null}.
*/
public ReactiveVaultTemplate(VaultEndpoint vaultEndpoint,
ClientHttpConnector connector, VaultTokenSupplier vaultTokenSupplier) {
Assert.notNull(vaultEndpoint, "VaultEndpoint must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
Assert.notNull(vaultTokenSupplier, "AuthenticationSupplier must not be null");
ExchangeFilterFunction filter = ofRequestProcessor(request -> vaultTokenSupplier
.getVaultToken().map(token -> {
return ClientRequest.from(request).headers(headers -> {
headers.set(VaultHttpHeaders.VAULT_TOKEN, token.getToken());
}).build();
}));
this.statelessClient = ReactiveVaultClients.createWebClient(vaultEndpoint,
connector);
this.sessionClient = ReactiveVaultClients
.createWebClient(vaultEndpoint, connector).mutate().filter(filter)
.build();
}
@Override
public Mono<VaultResponse> read(String path) {
Assert.hasText(path, "Path must not be empty");
return doRead(path, VaultResponse.class);
}
@SuppressWarnings("unchecked")
@Override
public <T> Mono<VaultResponseSupport<T>> read(String path, Class<T> responseType) {
ParameterizedTypeReference<VaultResponseSupport<T>> ref = VaultResponses
.getTypeReference(responseType);
return sessionClient.get().uri(path).exchange().flatMap(mapResponse(ref, path));
}
@Override
@SuppressWarnings("unchecked")
public Flux<String> list(String path) {
Assert.hasText(path, "Path must not be empty");
Mono<VaultListResponse> read = doRead(
String.format("%s?list=true", path.endsWith("/") ? path : (path + "/")),
VaultListResponse.class);
return read.filter(
response -> response.getData() != null
&& response.getData().containsKey("keys")) //
.flatMapIterable(
response -> (List<String>) response.getData().get("keys"));
}
@Override
public Mono<Void> write(String path, Object body) {
Assert.hasText(path, "Path must not be empty");
return sessionClient.post().uri(path).syncBody(body).exchange()
.flatMap(mapResponse(VaultResponse.class, path)).then();
}
@Override
public Mono<Void> delete(String path) {
Assert.hasText(path, "Path must not be empty");
return sessionClient.delete().uri(path).exchange()
.flatMap(mapResponse(String.class, path)).then();
}
@Override
@SuppressWarnings("unchecked")
public <V, T extends Publisher<V>> T doWithVault(
Function<WebClient, ? super T> clientCallback) throws VaultException,
WebClientException {
Assert.notNull(clientCallback, "Client callback must not be null");
try {
return (T) clientCallback.apply(statelessClient);
}
catch (HttpStatusCodeException e) {
throw VaultResponses.buildException(e);
}
}
@Override
@SuppressWarnings("unchecked")
public <V, T extends Publisher<V>> T doWithSession(
Function<WebClient, ? super T> sessionCallback) throws VaultException,
WebClientException {
Assert.notNull(sessionCallback, "Session callback must not be null");
try {
return (T) sessionCallback.apply(sessionClient);
}
catch (HttpStatusCodeException e) {
throw VaultResponses.buildException(e);
}
}
private <T> Mono<T> doRead(String path, Class<T> responseType) {
return doWithSession(client -> client.get() //
.uri(path).exchange().flatMap(mapResponse(responseType, path)));
}
private static <T> Function<ClientResponse, Mono<? extends T>> mapResponse(
Class<T> bodyType, String path) {
return response -> isSuccess(response) ? response.bodyToMono(bodyType)
: mapOtherwise(response, path);
}
private static <T> Function<ClientResponse, Mono<? extends T>> mapResponse(
ParameterizedTypeReference<T> typeReference, String path) {
return response -> isSuccess(response) ? response.body(BodyExtractors
.toMono(typeReference)) : mapOtherwise(response, path);
}
private static boolean isSuccess(ClientResponse response) {
return response.statusCode().is2xxSuccessful();
}
private static <T> Mono<? extends T> mapOtherwise(ClientResponse response, String path) {
if (response.statusCode() == HttpStatus.NOT_FOUND) {
return Mono.empty();
}
return response.bodyToMono(String.class).flatMap(
body -> {
String error = VaultResponses.getError(body);
return Mono.error(VaultResponses.buildException(
response.statusCode(), path, error));
});
}
private static class VaultListResponse extends
VaultResponseSupport<Map<String, Object>> {
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.authentication;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.springframework.vault.util.IntegrationTestSupport;
/**
* Integration test base class for {@link AppIdAuthentication} tests.
*
* @author Mark Paluch
*/
public abstract class AppIdAuthenticationIntegrationTestBase extends
IntegrationTestSupport {
@Before
public void before() {
if (!prepare().hasAuth("app-id")) {
prepare().mountAuth("app-id");
}
prepare().getVaultOperations().doWithSession(restOperations -> {
Map<String, String> appIdData = new HashMap<String, String>();
appIdData.put("value", "dummy"); // policy
appIdData.put("display_name", "this is my test application");
restOperations.postForEntity("auth/app-id/map/app-id/myapp", appIdData,
Map.class);
Map<String, String> userIdData = new HashMap<String, String>();
userIdData.put("value", "myapp"); // name of the app-id
userIdData.put("cidr_block", "0.0.0.0/0");
restOperations.postForEntity(
"auth/app-id/map/user-id/static-userid-value", userIdData,
Map.class);
return null;
});
}
}

View File

@@ -15,15 +15,10 @@
*/
package org.springframework.vault.authentication;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
@@ -35,35 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Mark Paluch
*/
public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport {
@Before
public void before() {
if (!prepare().hasAuth("app-id")) {
prepare().mountAuth("app-id");
}
prepare().getVaultOperations().doWithSession(restOperations -> {
Map<String, String> appIdData = new HashMap<String, String>();
appIdData.put("value", "dummy"); // policy
appIdData.put("display_name", "this is my test application");
restOperations.postForEntity("auth/app-id/map/app-id/myapp", appIdData,
Map.class);
Map<String, String> userIdData = new HashMap<String, String>();
userIdData.put("value", "myapp"); // name of the app-id
userIdData.put("cidr_block", "0.0.0.0/0");
restOperations.postForEntity(
"auth/app-id/map/user-id/static-userid-value", userIdData,
Map.class);
return null;
});
}
public class AppIdAuthenticationIntegrationTests extends
AppIdAuthenticationIntegrationTestBase {
@Test
public void shouldLoginSuccessfully() {
@@ -97,45 +65,4 @@ public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport
new AppIdAuthentication(options, restTemplate).login();
}
@Test
public void authenticationStepsShouldLoginSuccessfully() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("myapp") //
.userIdMechanism(new StaticUserId("static-userid-value")) //
.build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AppIdAuthentication authentication = new AppIdAuthentication(options,
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).isNotEmpty();
}
@Test(expected = VaultException.class)
public void authenticationStepsLoginShouldFail() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("wrong") //
.userIdMechanism(new StaticUserId("wrong")) //
.build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AuthenticationSteps authenticationChain = new AppIdAuthentication(options,
restTemplate).getAuthenticationSteps();
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authenticationChain, restTemplate);
executor.login();
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.authentication;
import org.junit.Test;
import reactor.test.StepVerifier;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestWebClientFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Integration tests for {@link AppIdAuthentication} using
* {@link AuthenticationStepsOperator}.
*
* @author Mark Paluch
*/
public class AppIdAuthenticationOperatorIntegrationTests extends
AppIdAuthenticationIntegrationTestBase {
WebClient webClient = TestWebClientFactory.create(Settings.createSslConfiguration());
@Test
public void authenticationStepsShouldLoginSuccessfully() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("myapp") //
.userIdMechanism(new StaticUserId("static-userid-value")) //
.build();
AppIdAuthentication authentication = new AppIdAuthentication(options,
new RestTemplate());
AuthenticationStepsOperator supplier = new AuthenticationStepsOperator(
authentication.getAuthenticationSteps(), webClient);
StepVerifier.create(supplier.getVaultToken()).expectNextCount(1).verifyComplete();
}
@Test
public void authenticationStepsLoginShouldFail() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("wrong") //
.userIdMechanism(new StaticUserId("wrong")) //
.build();
AppIdAuthentication authentication = new AppIdAuthentication(options,
new RestTemplate());
AuthenticationStepsOperator supplier = new AuthenticationStepsOperator(
authentication.getAuthenticationSteps(), webClient);
StepVerifier.create(supplier.getVaultToken()).expectError().verify();
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.authentication;
import org.junit.Test;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link AppIdAuthentication} using
* {@link AuthenticationStepsExecutor}.
*
* @author Mark Paluch
*/
public class AppIdAuthenticationStepsIntegrationTests extends
AppIdAuthenticationIntegrationTestBase {
@Test
public void authenticationStepsShouldLoginSuccessfully() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("myapp") //
.userIdMechanism(new StaticUserId("static-userid-value")) //
.build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AppIdAuthentication authentication = new AppIdAuthentication(options,
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).isNotEmpty();
}
@Test(expected = VaultException.class)
public void authenticationStepsLoginShouldFail() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("wrong") //
.userIdMechanism(new StaticUserId("wrong")) //
.build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AuthenticationSteps authenticationChain = new AppIdAuthentication(options,
restTemplate).getAuthenticationSteps();
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authenticationChain, restTemplate);
executor.login();
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.authentication;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
/**
* Unit tests for {@link AuthenticationStepsOperator}.
*
* @author Mark Paluch
*/
public class AuthenticationStepsOperatorUnitTests {
@Before
public void before() throws Exception {
}
@Test
public void justTokenShouldLogin() {
AuthenticationSteps steps = AuthenticationSteps.just(VaultToken.of("my-token"));
StepVerifier.create(login(steps)).expectNext(VaultToken.of("my-token"))
.verifyComplete();
}
@Test
public void supplierOfStringShouldLoginWithMap() {
AuthenticationSteps steps = AuthenticationSteps.fromSupplier(() -> "my-token")
.login(VaultToken::of);
StepVerifier.create(login(steps)).expectNext(VaultToken.of("my-token"))
.verifyComplete();
}
@Test
public void justLoginRequestShouldLogin() {
ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST,
"/auth/cert/login");
MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setBody("{"
+ "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}"
+ "}");
ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(
Mono.just(response));
WebClient webClient = WebClient.builder().clientConnector(connector).build();
AuthenticationSteps steps = AuthenticationSteps.just(post("/auth/{path}/login",
"cert").as(VaultResponse.class));
StepVerifier.create(login(steps, webClient))
.expectNext(VaultToken.of("my-token")).verifyComplete();
}
@Test
public void justLoginShouldFail() {
ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST,
"/auth/cert/login");
MockClientHttpResponse response = new MockClientHttpResponse(
HttpStatus.BAD_REQUEST);
ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(
Mono.just(response));
WebClient webClient = WebClient.builder().clientConnector(connector).build();
AuthenticationSteps steps = AuthenticationSteps.just(post("/auth/{path}/login",
"cert").as(VaultResponse.class));
StepVerifier.create(login(steps, webClient)).expectError().verify();
}
private Mono<VaultToken> login(AuthenticationSteps steps) {
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(steps,
WebClient.create());
return operator.getVaultToken();
}
private Mono<VaultToken> login(AuthenticationSteps steps, WebClient webClient) {
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(steps,
webClient);
return operator.getVaultToken();
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.authentication;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import org.assertj.core.util.Files;
import org.junit.Before;
import org.springframework.core.io.FileSystemResource;
import org.springframework.vault.core.RestOperationsCallback;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
import org.springframework.vault.util.IntegrationTestSupport;
import static org.springframework.vault.util.Settings.createSslConfiguration;
import static org.springframework.vault.util.Settings.findWorkDir;
/**
* Integration test base class for {@link ClientCertificateAuthentication} tests.
*
* @author Mark Paluch
*/
public abstract class ClientCertificateAuthenticationIntegrationTestBase extends
IntegrationTestSupport {
@Before
public void before() {
if (!prepare().hasAuth("cert")) {
prepare().mountAuth("cert");
}
prepare().getVaultOperations().doWithSession(
(RestOperationsCallback<Object>) restOperations -> {
File workDir = findWorkDir();
String certificate = Files.contentOf(new File(workDir,
"ca/certs/client.cert.pem"), StandardCharsets.US_ASCII);
return restOperations.postForEntity("auth/cert/certs/my-role",
Collections.singletonMap("certificate", certificate),
Map.class);
});
}
static SslConfiguration prepareCertAuthenticationMethod() {
SslConfiguration original = createSslConfiguration();
return new SslConfiguration(new KeyStoreConfiguration(new FileSystemResource(
new File(findWorkDir(), "client-cert.jks")), "changeit".toCharArray(),
null), original.getTrustStoreConfiguration());
}
}

View File

@@ -15,33 +15,19 @@
*/
package org.springframework.vault.authentication;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import org.assertj.core.util.Files;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
import org.springframework.vault.core.RestOperationsCallback;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.vault.util.Settings.createSslConfiguration;
import static org.springframework.vault.util.Settings.findWorkDir;
/**
* Integration tests for {@link ClientCertificateAuthentication}.
@@ -49,27 +35,7 @@ import static org.springframework.vault.util.Settings.findWorkDir;
* @author Mark Paluch
*/
public class ClientCertificateAuthenticationIntegrationTests extends
IntegrationTestSupport {
@Before
public void before() {
if (!prepare().hasAuth("cert")) {
prepare().mountAuth("cert");
}
prepare().getVaultOperations().doWithSession(
(RestOperationsCallback<Object>) restOperations -> {
File workDir = findWorkDir();
String certificate = Files.contentOf(new File(workDir,
"ca/certs/client.cert.pem"), StandardCharsets.US_ASCII);
return restOperations.postForEntity("auth/cert/certs/my-role",
Collections.singletonMap("certificate", certificate),
Map.class);
});
}
ClientCertificateAuthenticationIntegrationTestBase {
@Test
public void shouldLoginSuccessfully() {
@@ -98,48 +64,4 @@ public class ClientCertificateAuthenticationIntegrationTests extends
new ClientCertificateAuthentication(restTemplate).login();
}
@Test
public void authenticationStepsShouldLoginSuccessfully() {
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), prepareCertAuthenticationMethod());
RestTemplate restTemplate = VaultClients.createRestTemplate(
TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).isNotEmpty();
}
// Compatibility for Vault 0.6.0 and below. Vault 0.6.1 fixed that issue and we
// receive a VaultException here.
@Test(expected = NestedRuntimeException.class)
public void authenticationStepsLoginShouldFail() {
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), Settings.createSslConfiguration());
RestTemplate restTemplate = VaultClients.createRestTemplate(
TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
AuthenticationSteps steps = new ClientCertificateAuthentication(restTemplate)
.getAuthenticationSteps();
new AuthenticationStepsExecutor(steps, restTemplate).login();
}
private SslConfiguration prepareCertAuthenticationMethod() {
SslConfiguration original = createSslConfiguration();
return new SslConfiguration(new KeyStoreConfiguration(new FileSystemResource(
new File(findWorkDir(), "client-cert.jks")), "changeit".toCharArray(),
null), original.getTrustStoreConfiguration());
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.authentication;
import org.junit.Test;
import reactor.test.StepVerifier;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.vault.util.TestWebClientFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Integration tests for {@link ClientCertificateAuthentication} using
* {@link AuthenticationStepsOperator}.
*
* @author Mark Paluch
*/
public class ClientCertificateAuthenticationOperatorIntegrationTests extends
ClientCertificateAuthenticationIntegrationTestBase {
@Test
public void authenticationStepsShouldLoginSuccessfully() {
WebClient webClient = TestWebClientFactory
.create(prepareCertAuthenticationMethod());
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), prepareCertAuthenticationMethod());
RestTemplate restTemplate = VaultClients.createRestTemplate(
TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
restTemplate);
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
authentication.getAuthenticationSteps(), webClient);
StepVerifier.create(operator.getVaultToken()).expectNextCount(1).verifyComplete();
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.authentication;
import org.junit.Test;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ClientCertificateAuthentication} using
* {@link AuthenticationStepsExecutor}.
*
* @author Mark Paluch
*/
public class ClientCertificateAuthenticationStepsIntegrationTests extends
ClientCertificateAuthenticationIntegrationTestBase {
@Test
public void authenticationStepsShouldLoginSuccessfully() {
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), prepareCertAuthenticationMethod());
RestTemplate restTemplate = VaultClients.createRestTemplate(
TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).isNotEmpty();
}
// Compatibility for Vault 0.6.0 and below. Vault 0.6.1 fixed that issue and we
// receive a VaultException here.
@Test(expected = NestedRuntimeException.class)
public void authenticationStepsLoginShouldFail() {
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), Settings.createSslConfiguration());
RestTemplate restTemplate = VaultClients.createRestTemplate(
TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
AuthenticationSteps steps = new ClientCertificateAuthentication(restTemplate)
.getAuthenticationSteps();
new AuthenticationStepsExecutor(steps, restTemplate).login();
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.authentication;
import java.util.Map;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.util.IntegrationTestSupport;
import static org.junit.Assume.assumeNotNull;
/**
* Integration test base class for {@link CubbyholeAuthentication} tests.
*
* @author Mark Paluch
*/
public abstract class CubbyholeAuthenticationIntegrationTestBase extends
IntegrationTestSupport {
protected Map<String, String> prepareWrappedToken() {
ResponseEntity<VaultResponse> response = prepare().getVaultOperations()
.doWithSession(
restOperations -> {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Vault-Wrap-TTL", "10m");
return restOperations.exchange("auth/token/create",
HttpMethod.POST, new HttpEntity<Object>(headers),
VaultResponse.class);
});
Map<String, String> wrapInfo = response.getBody().getWrapInfo();
// Response Wrapping requires Vault 0.6.0+
assumeNotNull(wrapInfo);
return wrapInfo;
}
}

View File

@@ -19,28 +19,22 @@ import java.util.Map;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.Assume.assumeNotNull;
/**
* Integration tests for {@link CubbyholeAuthentication}.
*
* @author Mark Paluch
*/
public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupport {
public class CubbyholeAuthenticationIntegrationTests extends
CubbyholeAuthenticationIntegrationTestBase {
@Test
public void shouldCreateWrappedToken() {
@@ -60,28 +54,6 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
}
@Test
public void authenticationStepsShouldCreateWrappedToken() {
Map<String, String> wrapInfo = prepareWrappedToken();
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of(initialToken)).wrapped().build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
}
@Test
public void loginShouldFail() {
@@ -103,24 +75,4 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
}
}
private Map<String, String> prepareWrappedToken() {
ResponseEntity<VaultResponse> response = prepare().getVaultOperations()
.doWithSession(
restOperations -> {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Vault-Wrap-TTL", "10m");
return restOperations.exchange("auth/token/create",
HttpMethod.POST, new HttpEntity<Object>(headers),
VaultResponse.class);
});
Map<String, String> wrapInfo = response.getBody().getWrapInfo();
// Response Wrapping requires Vault 0.6.0+
assumeNotNull(wrapInfo);
return wrapInfo;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.authentication;
import java.util.Map;
import org.junit.Test;
import reactor.test.StepVerifier;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.vault.util.TestWebClientFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link CubbyholeAuthentication} using
* {@link AuthenticationStepsOperator}.
*
* @author Mark Paluch
*/
public class CubbyholeAuthenticationOperatorIntegrationTests extends
CubbyholeAuthenticationIntegrationTestBase {
WebClient webClient = TestWebClientFactory.create(Settings.createSslConfiguration());
@Test
public void authenticationStepsShouldCreateWrappedToken() {
Map<String, String> wrapInfo = prepareWrappedToken();
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of(initialToken)).wrapped().build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
restTemplate);
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
authentication.getAuthenticationSteps(), webClient);
StepVerifier.create(operator.getVaultToken()).consumeNextWith(actual -> {
assertThat(actual).isNotEqualTo(Settings.token().getToken()).isNotNull();
}).verifyComplete();
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.authentication;
import java.util.Map;
import org.junit.Test;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link CubbyholeAuthentication} using
* {@link AuthenticationStepsExecutor}.
*
* @author Mark Paluch
*/
public class CubbyholeAuthenticationStepsIntegrationTests extends
CubbyholeAuthenticationIntegrationTestBase {
@Test
public void authenticationStepsShouldCreateWrappedToken() {
Map<String, String> wrapInfo = prepareWrappedToken();
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of(initialToken)).wrapped().build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.core;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.vault.util.IntegrationTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ReactiveVaultTemplate} using the {@code generic} backend.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
public class ReactiveVaultTemplateGenericIntegrationTests extends IntegrationTestSupport {
@Autowired
private ReactiveVaultOperations vaultOperations;
@Test
public void readShouldReturnAbsentKey() {
StepVerifier.create(vaultOperations.read("secret/absent")).verifyComplete();
}
@Test
public void readShouldReturnExistingKey() {
StepVerifier.create(
vaultOperations.write("secret/mykey",
Collections.singletonMap("hello", "world"))).verifyComplete();
StepVerifier
.create(vaultOperations.read("secret/mykey"))
.consumeNextWith(
actual -> assertThat(actual.getData()).containsEntry("hello",
"world")).verifyComplete();
}
@Test
public void readShouldReturnNestedPropertiesKey() throws IOException {
Map map = new ObjectMapper()
.readValue(
"{ \"hello.array[0]\":\"array-value0\", \"hello.array[1]\":\"array-value1\" }",
Map.class);
StepVerifier.create(vaultOperations.write("secret/mykey", map)).verifyComplete();
StepVerifier
.create(vaultOperations.read("secret/mykey"))
.consumeNextWith(
actual -> {
assertThat(actual.getData()).containsEntry("hello.array[0]",
"array-value0");
assertThat(actual.getData()).containsEntry("hello.array[1]",
"array-value1");
}).verifyComplete();
}
@Test
public void readShouldReturnNestedObjects() throws IOException {
Map map = new ObjectMapper().readValue(
"{ \"array\": [ {\"hello\": \"world\"}, {\"hello1\": \"world1\"} ] }",
Map.class);
StepVerifier.create(vaultOperations.write("secret/mykey", map)).verifyComplete();
List<Map<String, String>> expected = Arrays.asList(
Collections.singletonMap("hello", "world"),
Collections.singletonMap("hello1", "world1"));
StepVerifier.create(vaultOperations.read("secret/mykey"))
.consumeNextWith(actual -> {
assertThat(actual.getData()).containsEntry("array", expected);
}).verifyComplete();
}
@Test
public void readObjectShouldReadDomainClass() {
Map<String, String> data = new HashMap<>();
data.put("firstname", "Walter");
data.put("password", "Secret");
StepVerifier.create(vaultOperations.write("secret/mykey", data)).verifyComplete();
StepVerifier.create(vaultOperations.read("secret/mykey", Person.class))
.consumeNextWith(actual -> {
Person person = actual.getData();
assertThat(person.getFirstname()).isEqualTo("Walter");
assertThat(person.getPassword()).isEqualTo("Secret");
}).verifyComplete();
}
@Test
public void listShouldReturnExistingKey() {
StepVerifier.create(
vaultOperations.write("secret/mykey",
Collections.singletonMap("hello", "world"))).verifyComplete();
StepVerifier.create(vaultOperations.list("secret").collectList())
.consumeNextWith(actual -> assertThat(actual).contains("mykey"))
.verifyComplete();
}
@Test
public void deleteShouldRemoveKey() {
StepVerifier.create(
vaultOperations.write("secret/mykey",
Collections.singletonMap("hello", "world"))).verifyComplete();
StepVerifier.create(vaultOperations.delete("secret/mykey")).verifyComplete();
StepVerifier.create(vaultOperations.read("secret/mykey")).verifyComplete();
}
static class Person {
String firstname;
String password;
public String getFirstname() {
return firstname;
}
public String getPassword() {
return password;
}
}
}

View File

@@ -19,17 +19,17 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.config.AbstractVaultConfiguration;
import org.springframework.vault.config.AbstractReactiveVaultConfiguration;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.util.Settings;
/**
* Test configuration for Vault integration tests.
*
*
* @author Mark Paluch
*/
@Configuration
public class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
public class VaultIntegrationTestConfiguration extends AbstractReactiveVaultConfiguration {
@Override
public VaultEndpoint vaultEndpoint() {

View File

@@ -0,0 +1,74 @@
/*
* 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.util;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
import org.springframework.vault.client.ReactiveVaultClients;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.config.ClientHttpConnectorFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author Mark Paluch
*/
public class TestWebClientFactory {
public static final VaultEndpoint TEST_VAULT_ENDPOINT = new VaultEndpoint();
private static final AtomicReference<ClientHttpConnector> connectorCache = new AtomicReference<ClientHttpConnector>();
/**
* Create a new {@link WebClient} using the {@link SslConfiguration}. The underlying
* {@link WebClient} is cached. See
* {@link ReactiveVaultClients#createWebClient(VaultEndpoint, ClientHttpConnector)} to
* create {@link WebClient} for a given {@link ClientHttpConnector}.
*
* @param sslConfiguration must not be {@literal null}.
* @return
*/
public static WebClient create(SslConfiguration sslConfiguration) {
Assert.notNull(sslConfiguration, "SslConfiguration must not be null!");
try {
initializeClientHttpConnector(sslConfiguration);
return ReactiveVaultClients.createWebClient(TEST_VAULT_ENDPOINT,
ClientHttpConnectorFactory.create(new ClientOptions(),
sslConfiguration));
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static void initializeClientHttpConnector(SslConfiguration sslConfiguration)
throws Exception {
if (connectorCache.get() != null) {
// return;
}
ClientHttpConnector clientHttpConnector = ClientHttpConnectorFactory.create(
new ClientOptions(), sslConfiguration);
// connectorCache.compareAndSet(null, clientHttpConnector);
}
}