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:
@@ -40,6 +40,7 @@ import org.springframework.web.client.RestOperations;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see AuthenticationSteps
|
||||
*/
|
||||
public class AuthenticationStepsExecutor implements ClientAuthentication {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user