Add authentication flow DSL.

We now support customizable authentication flows via AuthenticationSteps. Authentication allow composition of the particular steps involved in the authentication flow until a VaultToken is created.

AuthenticationSteps.fromSupplier(() -> getAppRoleLogin(options.getRoleId(), options.getSecretId()))
    .login("auth/{mount}/login", options.getPath());

AuthenticationSteps.just(VaultToken.of(…));

Closes gh-107.
This commit is contained in:
Mark Paluch
2017-07-04 17:11:21 +02:00
parent 017350bb42
commit 2c46866776
16 changed files with 1238 additions and 81 deletions

View File

@@ -40,7 +40,8 @@ import org.springframework.web.client.RestOperations;
* @see <a href="https://www.vaultproject.io/docs/auth/app-id.html">Auth Backend: App
* ID</a>
*/
public class AppIdAuthentication implements ClientAuthentication {
public class AppIdAuthentication implements ClientAuthentication,
AuthenticationStepsFactory {
private static final Log logger = LogFactory.getLog(AppIdAuthentication.class);
@@ -89,6 +90,14 @@ public class AppIdAuthentication implements ClientAuthentication {
}
}
public AuthenticationSteps getAuthenticationSteps() {
return AuthenticationSteps.fromSupplier(
() -> getAppIdLogin(options.getAppId(), options.getUserIdMechanism()
.createUserId())) //
.login("auth/{mount}/login", options.getPath());
}
private Map<String, String> getAppIdLogin(String appId, String userId) {
Map<String, String> login = new HashMap<>();

View File

@@ -42,7 +42,8 @@ import org.springframework.web.client.RestOperations;
* @see <a href="https://www.vaultproject.io/docs/auth/approle.html">Auth Backend:
* AppRole</a>
*/
public class AppRoleAuthentication implements ClientAuthentication {
public class AppRoleAuthentication implements ClientAuthentication,
AuthenticationStepsFactory {
private static final Log logger = LogFactory.getLog(AppRoleAuthentication.class);
@@ -91,6 +92,14 @@ public class AppRoleAuthentication implements ClientAuthentication {
}
}
@Override
public AuthenticationSteps getAuthenticationSteps() {
return AuthenticationSteps.fromSupplier(
() -> getAppRoleLogin(options.getRoleId(), options.getSecretId())) //
.login("auth/{mount}/login", options.getPath());
}
private Map<String, String> getAppRoleLogin(String roleId, String secretId) {
Map<String, String> login = new HashMap<>();

View File

@@ -0,0 +1,504 @@
/*
* 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.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.FieldDefaults;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
/**
* Authentication DSL allowing flow composition to create a {@link VaultToken}.
* <p>
* Static generators are the main entry point to start with a flow composition. An example
* authentication using AWS-EC2 authentication:
*
* <pre class="code">
* String nonce = &quot;&quot;;
* return AuthenticationSteps
* .fromHttpRequest(
* HttpRequestBuilder.get(options.getIdentityDocumentUri().toString()) //
* .as(String.class)) //
* .map(pkcs7 -&gt; pkcs7.replaceAll(&quot;\\r&quot;, &quot;&quot;)) //
* .map(pkcs7 -&gt; {
*
* Map&lt;String, String&gt; login = new HashMap&lt;&gt;();
*
* login.put(&quot;nonce&quot;, new String(nonce));
* login.put(&quot;pkcs7&quot;, pkcs7);
*
* return login;
* }).login(&quot;auth/{mount}/login&quot;, &quot;aws&quot;);
* </pre>
*
* <p>
* To perform a computation, authentication steps are composed into a <em>pipeline</em>. A
* pipeline consists of a source (which might be an object, a supplier function, a HTTP
* request, etc), zero or more <em>intermediate operations</em> (which transform the
* authentication state object into another object, such as {@link Node#map(Function)}),
* and a <em>terminal operation</em> which finishes authentication composition. An
* authentication flow operates on the authentication state object which is created for
* each authentication. Step produce an object and some steps can accept the current state
* object for further transformation.
*
* <p>
* {@link AuthenticationSteps} describes the authentication flow. Computation on the
* source data is only performed when the flow definition is interpreted by an executor.
*
*
* @author Mark Paluch
* @since 2.0
* @see AuthenticationStepsFactory
*/
public class AuthenticationSteps {
private static final Node<Object> HEAD = new Node<>();
final List<Node<?>> steps = new ArrayList<>();
/**
* Create a flow definition using a provided {@link VaultToken}.
*
* @param token the token to be used from this {@link AuthenticationSteps}, must not
* be {@literal null}.
* @return the {@link AuthenticationSteps}.
*/
public static AuthenticationSteps just(VaultToken token) {
Assert.notNull(token, "Vault token must not be null");
return new AuthenticationSteps(new SupplierStep<>(() -> token,
AuthenticationSteps.HEAD));
}
/**
* Create a flow definition from a {@link HttpRequest} returning a
* {@link VaultResponse}.
* @param request the HTTP request definition, must not be {@literal null}.
* @return the {@link AuthenticationSteps}.
*/
public static AuthenticationSteps just(HttpRequest<VaultResponse> request) {
Assert.notNull(request, "HttpRequest must not be null");
return new AuthenticationSteps(new HttpRequestNode<>(request,
AuthenticationSteps.HEAD));
}
/**
* Start flow composition from a {@link Supplier}.
*
* @param supplier supplier function that will produce the flow value, must not be
* {@literal null}.
* @return the first {@link Node}.
*/
public static <T> Node<T> fromSupplier(Supplier<T> supplier) {
Assert.notNull(supplier, "Supplier must not be null");
return new SupplierStep<>(supplier, AuthenticationSteps.HEAD);
}
/**
* Start flow composition from a {@link HttpRequest}.
*
* @param request the HTTP request definition, must not be {@literal null}.
* @return the first {@link Node}.
*/
public static <T> Node<T> fromHttpRequest(HttpRequest<T> request) {
Assert.notNull(request, "HttpRequest must not be null");
return new HttpRequestNode<>(request, AuthenticationSteps.HEAD);
}
AuthenticationSteps(PathAware pathAware) {
PathAware current = pathAware;
do {
if (current instanceof Node<?>) {
steps.add((Node<?>) current);
}
if (current.getPrevious() instanceof PathAware) {
current = (PathAware) current.getPrevious();
}
else {
break;
}
}
while (!Objects.equals(current, AuthenticationSteps.HEAD));
Collections.reverse(steps);
}
/**
* Intermediate authentication step with authentication flow operators represented as
* node.
*
* @param <T> authentication state object type produced by this node.
*/
public static class Node<T> {
/**
* Transform the state object into a different object.
*
* @param mappingFunction mapping function to be applied to the state object, must
* not be {@literal null}.
* @param <R> resulting object type
* @return the next {@link Node}.
*/
public <R> Node<R> map(Function<? super T, ? extends R> mappingFunction) {
Assert.notNull(mappingFunction, "Mapping function must not be null");
return new MapStep<>(mappingFunction, this);
}
/**
* Callback with the current state object.
*
* @param consumerFunction consumer function to be called with the state object,
* must not be {@literal null}.
* @return the next {@link Node}.
*/
public Node<T> onNext(Consumer<? super T> consumerFunction) {
Assert.notNull(consumerFunction, "Consumer function must not be null");
return new OnNextStep<>(consumerFunction, this);
}
/**
* Request data using a {@link HttpRequest}.
*
* @param request the HTTP request definition, must not be {@literal null}.
* @return the next {@link Node}.
*/
public <R> Node<R> request(HttpRequest<R> request) {
Assert.notNull(request, "HttpRequest must not be null");
return new HttpRequestNode<>(request, this);
}
/**
* Terminal operation requesting a {@link VaultToken token} from Vault by posting
* the current state to Vaults {@code uriTemplate}.
*
* @param uriTemplate Vault authentication endpoint, must not be {@literal null}
* or empty.
* @param uriVariables URI variables for URI template expansion.
* @return the {@link AuthenticationSteps}.
*/
public AuthenticationSteps login(String uriTemplate, String... uriVariables) {
Assert.hasText(uriTemplate, "URI template must not be null or empty");
return login(HttpRequestBuilder.post(uriTemplate, uriVariables).as(
VaultResponse.class));
}
/**
* Terminal operation requesting a {@link VaultToken token} from Vault by issuing
* a HTTP request with the current state to Vaults {@code uriTemplate}.
*
* @param request HTTP request definition.
* @return the {@link AuthenticationSteps}.
*/
public AuthenticationSteps login(HttpRequest<VaultResponse> request) {
Assert.notNull(request, "HttpRequest must not be null");
return new AuthenticationSteps(new HttpRequestNode<>(request, this));
}
/**
* Terminal operation resulting in a {@link VaultToken token} by applying a
* mapping {@link Function} to the current state object.
*
* @param mappingFunction mapping function to be applied to the state object, must
* not be {@literal null}.
* @return the {@link AuthenticationSteps}.
*/
public AuthenticationSteps login(
Function<? super T, ? extends VaultToken> mappingFunction) {
Assert.notNull(mappingFunction, "Mapping function must not be null");
return new AuthenticationSteps(new MapStep<>(mappingFunction, this));
}
}
/**
* Builder for {@link HttpRequest}.
*/
public static class HttpRequestBuilder {
HttpMethod method;
URI uri;
String uriTemplate;
String[] urlVariables;
HttpEntity<?> entity;
/**
* Builder entry point to {@code GET} from {@code uriTemplate}.
*
* @param uriTemplate must not be {@literal null} or empty.
* @param uriVariables the variables to expand the template.
* @return a new {@link HttpRequestBuilder}.
*/
public static HttpRequestBuilder get(String uriTemplate, String... uriVariables) {
return new HttpRequestBuilder(HttpMethod.GET, uriTemplate, uriVariables);
}
/**
* Builder entry point to {@code GET} from {@code uri}.
*
* @param uri must not be {@literal null}.
* @return a new {@link HttpRequestBuilder}.
*/
public static HttpRequestBuilder get(URI uri) {
return new HttpRequestBuilder(HttpMethod.GET, uri);
}
/**
* Builder entry point to {@code POST} to {@code uriTemplate}.
*
* @param uriTemplate must not be {@literal null} or empty.
* @param uriVariables the variables to expand the template.
* @return a new {@link HttpRequestBuilder}.
*/
public static HttpRequestBuilder post(String uriTemplate, String... uriVariables) {
return new HttpRequestBuilder(HttpMethod.POST, uriTemplate, uriVariables);
}
/**
* Builder entry point to {@code POST} to {@code uri}.
*
* @param uri must not be {@literal null}.
* @return a new {@link HttpRequestBuilder}.
*/
public static HttpRequestBuilder post(URI uri) {
return new HttpRequestBuilder(HttpMethod.POST, uri);
}
private HttpRequestBuilder(HttpMethod method, URI uri) {
this.method = method;
this.uri = uri;
}
private HttpRequestBuilder(HttpMethod method, String uriTemplate,
String[] urlVariables) {
this.method = method;
this.uriTemplate = uriTemplate;
this.urlVariables = urlVariables;
}
private HttpRequestBuilder(HttpMethod method, URI uri, String uriTemplate,
String[] urlVariables, HttpEntity<?> entity) {
this.method = method;
this.uri = uri;
this.uriTemplate = uriTemplate;
this.urlVariables = urlVariables;
this.entity = entity;
}
/**
* Configure a request {@link HttpEntity entity}.
*
* @param httpEntity must not be {@literal null}.
* @return a new {@link HttpRequestBuilder}.
*/
public HttpRequestBuilder with(HttpEntity<?> httpEntity) {
Assert.notNull(httpEntity, "HttpEntity must not be null");
return new HttpRequestBuilder(method, uri, uriTemplate, urlVariables,
httpEntity);
}
/**
* Configure a request {@link HttpHeaders headers}.
*
* @param headers must not be {@literal null}.
* @return a new {@link HttpRequestBuilder}.
*/
public HttpRequestBuilder with(HttpHeaders headers) {
Assert.notNull(headers, "HttpHeaders must not be null");
return new HttpRequestBuilder(method, uri, uriTemplate, urlVariables,
new HttpEntity<>(headers));
}
/**
* Configure the result type and build the {@link HttpRequest} object.
* @param type must not be {@literal null}.
* @return the {@link HttpRequest} definition.
*/
public <T> HttpRequest<T> as(Class<T> type) {
Assert.notNull(type, "Result type must not be null");
return new HttpRequest<>(this, type);
}
}
/**
* Value object representing a HTTP request.
*
* @param <T> authentication state object type produced by this request.
*/
@FieldDefaults(makeFinal = true, level = AccessLevel.PACKAGE)
@Getter(AccessLevel.PACKAGE)
public static class HttpRequest<T> {
HttpMethod method;
URI uri;
String uriTemplate;
String[] urlVariables;
HttpEntity<?> entity;
Class<T> responseType;
HttpRequest(HttpRequestBuilder builder, Class<T> responseType) {
this.method = builder.method;
this.uri = builder.uri;
this.uriTemplate = builder.uriTemplate;
this.urlVariables = builder.urlVariables;
this.entity = builder.entity;
this.responseType = responseType;
}
@Override
public String toString() {
return String.format("%s %s AS %s", getMethod(), getUri() != null ? getUri()
: getUriTemplate(), getResponseType());
}
}
@Value
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
static class HttpRequestNode<T> extends Node<T> implements PathAware {
@NonNull
HttpRequest<T> definition;
@NonNull
Node<?> previous;
@Override
public String toString() {
return definition.toString();
}
}
@Value
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
static class MapStep<I, O> extends Node<O> implements PathAware {
@NonNull
Function<? super I, ? extends O> mapper;
@NonNull
Node<?> previous;
O apply(I in) {
return mapper.apply(in);
}
@Override
public String toString() {
return "Map: " + mapper.toString();
}
}
@Value
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
static class OnNextStep<T> extends Node<T> implements PathAware {
@NonNull
Consumer<? super T> consumer;
@NonNull
Node<?> previous;
T apply(T in) {
consumer.accept(in);
return in;
}
@Override
public String toString() {
return "Consumer: " + consumer.toString();
}
}
@Value
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
static class SupplierStep<T> extends Node<T> implements PathAware {
@NonNull
Supplier<T> supplier;
@NonNull
Node<?> previous;
public T get() {
return supplier.get();
}
@Override
public String toString() {
return "Supplier: " + supplier.toString();
}
}
interface PathAware {
Node<?> getPrevious();
}
}

View File

@@ -0,0 +1,178 @@
/*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
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.client.VaultResponses;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
/**
* Synchronous executor for {@link AuthenticationSteps} using {@link RestOperations} to
* login using authentication flows.
*
* @author Mark Paluch
* @since 2.0
*/
public class AuthenticationStepsExecutor implements ClientAuthentication {
private static final Log logger = LogFactory.getLog(AppIdAuthentication.class);
private final AuthenticationSteps chain;
private final RestOperations restOperations;
/**
* Create a new {@link AuthenticationStepsExecutor} given {@link AuthenticationSteps}
* and {@link RestOperations}.
*
* @param steps must not be {@literal null}.
* @param restOperations must not be {@literal null}.
*/
public AuthenticationStepsExecutor(AuthenticationSteps steps,
RestOperations restOperations) {
Assert.notNull(steps, "AuthenticationSteps must not be null");
Assert.notNull(restOperations, "RestOperations must not be null");
this.chain = steps;
this.restOperations = restOperations;
}
@Override
@SuppressWarnings("unchecked")
public VaultToken login() throws VaultException {
Object state = null;
for (Node<?> o : chain.steps) {
if (logger.isDebugEnabled()) {
logger.debug(String
.format("Executing %s with current state %s", o, state));
}
try {
if (o instanceof HttpRequestNode) {
state = doHttpRequest((HttpRequestNode<Object>) o, state);
}
if (o instanceof AuthenticationSteps.MapStep) {
state = doMapStep((MapStep<Object, Object>) o, state);
}
if (o instanceof OnNextStep) {
state = doOnNext((OnNextStep<Object>) o, state);
}
if (o instanceof AuthenticationSteps.SupplierStep<?>) {
state = doSupplierStep((SupplierStep<Object>) o);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Executed %s with current state %s", o,
state));
}
}
catch (HttpStatusCodeException e) {
throw new VaultException(String.format(
"HTTP request %s in state %s failed with Status %s and body %s",
o, state, e.getStatusCode(),
VaultResponses.getError(e.getResponseBodyAsString())));
}
catch (RuntimeException e) {
throw new VaultException(String.format(
"Authentication execution failed in %s", o), e);
}
}
if (state instanceof VaultToken) {
return (VaultToken) state;
}
if (state instanceof VaultResponse) {
VaultResponse response = (VaultResponse) state;
return LoginTokenUtil.from(response.getAuth());
}
throw new IllegalStateException(String.format(
"Cannot retrieve VaultToken from authentication chain. Got instead %s",
state));
}
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 Object doHttpRequest(HttpRequestNode<Object> step, Object state) {
HttpRequest<Object> definition = step.getDefinition();
if (definition.getUri() == null) {
ResponseEntity<?> exchange = restOperations
.exchange(definition.getUriTemplate(), definition.getMethod(),
getEntity(definition.getEntity(), state),
definition.getResponseType(),
(Object[]) definition.getUrlVariables());
return exchange.getBody();
}
ResponseEntity<?> exchange = restOperations.exchange(definition.getUri(),
definition.getMethod(), getEntity(definition.getEntity(), state),
definition.getResponseType());
return exchange.getBody();
}
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;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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;
/**
* Factory interface for components that create {@link AuthenticationSteps}.
* <p>
* Implementing objects are required to construct {@link AuthenticationSteps} by their
* needs and invoked once upon {@link AuthenticationSteps} retrieval.
*
* @author Mark Paluch
* @since 2.0
* @see AuthenticationSteps
*/
public interface AuthenticationStepsFactory {
/**
* Get the {@link AuthenticationSteps} describing an authentication flow.
*
* @return the {@link AuthenticationSteps} describing an authentication flow.
*/
AuthenticationSteps getAuthenticationSteps();
}

View File

@@ -17,6 +17,7 @@ package org.springframework.vault.authentication;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
@@ -25,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder;
import org.springframework.vault.client.VaultResponses;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
@@ -45,7 +47,8 @@ import org.springframework.web.client.RestOperations;
* @see <a href="https://www.vaultproject.io/docs/auth/aws-ec2.html">Auth Backend:
* aws-ec2</a>
*/
public class AwsEc2Authentication implements ClientAuthentication {
public class AwsEc2Authentication implements ClientAuthentication,
AuthenticationStepsFactory {
private static final Log logger = LogFactory.getLog(AwsEc2Authentication.class);
@@ -79,8 +82,7 @@ public class AwsEc2Authentication implements ClientAuthentication {
* @param awsMetadataRestOperations must not be {@literal null}.
*/
public AwsEc2Authentication(AwsEc2AuthenticationOptions options,
RestOperations vaultRestOperations,
RestOperations awsMetadataRestOperations) {
RestOperations vaultRestOperations, RestOperations awsMetadataRestOperations) {
Assert.notNull(options, "AwsEc2AuthenticationOptions must not be null");
Assert.notNull(vaultRestOperations, "Vault RestOperations must not be null");
@@ -112,9 +114,10 @@ public class AwsEc2Authentication implements ClientAuthentication {
if (response.getAuth().get("metadata") instanceof Map) {
Map<Object, Object> metadata = (Map<Object, Object>) response
.getAuth().get("metadata");
logger.debug(String.format(
"Login successful using AWS-EC2 authentication for instance %s, AMI %s",
metadata.get("instance_id"), metadata.get("instance_id")));
logger.debug(String
.format("Login successful using AWS-EC2 authentication for instance %s, AMI %s",
metadata.get("instance_id"),
metadata.get("instance_id")));
}
else {
logger.debug("Login successful using AWS-EC2 authentication");
@@ -129,6 +132,33 @@ public class AwsEc2Authentication implements ClientAuthentication {
}
}
public AuthenticationSteps getAuthenticationSteps() {
return AuthenticationSteps
.fromHttpRequest(
HttpRequestBuilder.get(
options.getIdentityDocumentUri().toString()).as(
String.class))
.map(pkcs7 -> pkcs7.replaceAll("\\r", "").replace("\\n", ""))
.map(pkcs7 -> {
Map<String, String> login = new HashMap<>();
if (StringUtils.hasText(options.getRole())) {
login.put("role", options.getRole());
}
if (Objects.equals(this.nonce.get(), EMPTY)) {
this.nonce.compareAndSet(EMPTY, createNonce());
}
login.put("nonce", new String(this.nonce.get()));
login.put("pkcs7", pkcs7);
return login;
}).login("auth/{mount}/login", options.getPath());
}
protected Map<String, String> getEc2Login() {
Map<String, String> login = new HashMap<>();
@@ -144,8 +174,8 @@ public class AwsEc2Authentication implements ClientAuthentication {
login.put("nonce", new String(this.nonce.get()));
try {
String pkcs7 = awsMetadataRestOperations
.getForObject(options.getIdentityDocumentUri(), String.class);
String pkcs7 = awsMetadataRestOperations.getForObject(
options.getIdentityDocumentUri(), String.class);
if (StringUtils.hasText(pkcs7)) {
login.put("pkcs7", pkcs7.replaceAll("\\r", "").replace("\\n", ""));
}
@@ -153,10 +183,9 @@ public class AwsEc2Authentication implements ClientAuthentication {
return login;
}
catch (RestClientException e) {
throw new VaultException(
String.format("Cannot obtain Identity Document from %s",
options.getIdentityDocumentUri()),
e);
throw new VaultException(String.format(
"Cannot obtain Identity Document from %s",
options.getIdentityDocumentUri()), e);
}
}

View File

@@ -28,12 +28,15 @@ import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
/**
* TLS Client Certificate {@link ClientAuthentication}.
*
* @author Mark Paluch
*/
public class ClientCertificateAuthentication implements ClientAuthentication {
public class ClientCertificateAuthentication implements ClientAuthentication,
AuthenticationStepsFactory {
private static final Log logger = LogFactory
.getLog(ClientCertificateAuthentication.class);
@@ -73,4 +76,9 @@ public class ClientCertificateAuthentication implements ClientAuthentication {
VaultResponses.getError(e.getResponseBodyAsString())));
}
}
@Override
public AuthenticationSteps getAuthenticationSteps() {
return AuthenticationSteps.just(post("auth/cert/login").as(VaultResponse.class));
}
}

View File

@@ -25,13 +25,17 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.AuthenticationSteps.HttpRequest;
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.vault.support.VaultToken;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.get;
/**
* Cubbyhole {@link ClientAuthentication} implementation.
* <p>
@@ -132,7 +136,8 @@ import org.springframework.web.client.RestOperations;
* "https://www.vaultproject.io/docs/concepts/response-wrapping.html">Response
* Wrapping</a>
*/
public class CubbyholeAuthentication implements ClientAuthentication {
public class CubbyholeAuthentication implements ClientAuthentication,
AuthenticationStepsFactory {
private static final Log logger = LogFactory.getLog(CubbyholeAuthentication.class);
@@ -175,6 +180,17 @@ public class CubbyholeAuthentication implements ClientAuthentication {
return tokenToUse;
}
public AuthenticationSteps getAuthenticationSteps() {
HttpRequest<VaultResponse> initialRequest = get(options.getPath()) //
.with(VaultHttpHeaders.from(options.getInitialToken())) //
.as(VaultResponse.class);
return AuthenticationSteps.fromHttpRequest(initialRequest) //
.map(VaultResponseSupport::getData) //
.login(this::getToken);
}
private Map<String, Object> lookupToken() {
try {

View File

@@ -25,7 +25,8 @@ import org.springframework.vault.support.VaultToken;
* @see VaultToken
* @see <a href="https://www.vaultproject.io/docs/auth/token.html">Auth Backend: Token</a>
*/
public class TokenAuthentication implements ClientAuthentication {
public class TokenAuthentication implements ClientAuthentication,
AuthenticationStepsFactory {
private final VaultToken token;
@@ -57,4 +58,9 @@ public class TokenAuthentication implements ClientAuthentication {
public VaultToken login() {
return token;
}
@Override
public AuthenticationSteps getAuthenticationSteps() {
return AuthenticationSteps.just(token);
}
}

View File

@@ -84,16 +84,15 @@ public class VaultClients {
*/
public static RestTemplate createRestTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(
3);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(3);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
RestTemplate restTemplate = new RestTemplate(messageConverters);
restTemplate.getInterceptors()
.add((request, body, execution) -> execution.execute(request, body));
restTemplate.getInterceptors().add(
(request, body, execution) -> execution.execute(request, body));
return restTemplate;
}
@@ -122,8 +121,8 @@ public class VaultClients {
}
/**
* Strip/add leading slashes from {@code uriTemplate} depending on whetner the
* base url has a trailing slash.
* Strip/add leading slashes from {@code uriTemplate} depending on wheter the base
* url has a trailing slash.
*
* @param uriTemplate
* @return
@@ -142,6 +141,16 @@ public class VaultClients {
return uriTemplate;
}
try {
URI uri = URI.create(uriTemplate);
if (uri.getHost() != null) {
return uriTemplate;
}
}
catch (IllegalArgumentException e) {
}
if (!uriTemplate.startsWith("/")) {
return "/" + uriTemplate;
}

View File

@@ -38,36 +38,35 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport {
@Before
public void before() throws Exception {
public void before() {
if (!prepare().hasAuth("app-id")) {
prepare().mountAuth("app-id");
}
prepare().getVaultOperations().doWithSession(
restOperations -> {
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");
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);
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");
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);
restOperations.postForEntity(
"auth/app-id/map/user-id/static-userid-value", userIdData,
Map.class);
return null;
});
return null;
});
}
@Test
public void shouldLoginSuccessfully() throws Exception {
public void shouldLoginSuccessfully() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("myapp") //
@@ -85,7 +84,7 @@ public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport
}
@Test(expected = VaultException.class)
public void loginShouldFail() throws Exception {
public void loginShouldFail() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("wrong") //
@@ -96,5 +95,47 @@ public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport
.createSslConfiguration());
new AppIdAuthentication(options, restTemplate).login();
}
@Test
public void authenticationStepsShouldLoginSuccessfully() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("myapp") //
.userIdMechanism(new StaticUserId("static-userid-value")) //
.build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AppIdAuthentication authentication = new AppIdAuthentication(options,
restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).isNotEmpty();
}
@Test(expected = VaultException.class)
public void authenticationStepsLoginShouldFail() {
AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
.appId("wrong") //
.userIdMechanism(new StaticUserId("wrong")) //
.build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AuthenticationSteps authenticationChain = new AppIdAuthentication(options,
restTemplate).getAuthenticationSteps();
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authenticationChain, restTemplate);
executor.login();
}
}

View File

@@ -58,22 +58,22 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor
Map<String, String> withSecretId = new HashMap<String, String>();
withSecretId.put("policies", "dummy"); // policy
withSecretId.put("bound_cidr_list", "0.0.0.0/0");
withSecretId.put("bind_secret_id", "true");
withSecretId.put("bound_cidr_list", "0.0.0.0/0");
withSecretId.put("bind_secret_id", "true");
restOperations.postForEntity("auth/approle/role/with-secret-id", withSecretId,
Map.class);
restOperations.postForEntity("auth/approle/role/with-secret-id",
withSecretId, Map.class);
Map<String, String> noSecretIdRole = new HashMap<String, String>();
noSecretIdRole.put("policies", "dummy"); // policy
noSecretIdRole.put("bound_cidr_list", "0.0.0.0/0");
noSecretIdRole.put("bind_secret_id", "false");
Map<String, String> noSecretIdRole = new HashMap<String, String>();
noSecretIdRole.put("policies", "dummy"); // policy
noSecretIdRole.put("bound_cidr_list", "0.0.0.0/0");
noSecretIdRole.put("bind_secret_id", "false");
restOperations.postForEntity("auth/approle/role/no-secret-id", noSecretIdRole,
Map.class);
restOperations.postForEntity("auth/approle/role/no-secret-id",
noSecretIdRole, Map.class);
return null;
});
return null;
});
}
@Test
@@ -152,6 +152,47 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor
customSecretIdResponse.getData());
}
@Test(expected = VaultException.class)
public void authenticationStepsShouldAuthenticatePullModeFailsWithWrongSecretId() {
String roleId = getRoleId("with-secret-id");
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.roleId(roleId).secretId("this-is-a-wrong-secret-id").build();
AppRoleAuthentication authentication = new AppRoleAuthentication(options,
prepare().getRestTemplate());
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), prepare().getRestTemplate());
assertThat(executor.login()).isNotNull();
}
@Test
public void authenticationStepsShouldAuthenticatePushModeWithProvidedSecretId() {
String roleId = getRoleId("with-secret-id");
final String secretId = "hello_world_two";
final VaultResponse customSecretIdResponse = getVaultOperations().write(
"auth/approle/role/with-secret-id/custom-secret-id",
Collections.singletonMap("secret_id", secretId));
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.roleId(roleId).secretId(secretId).build();
AppRoleAuthentication authentication = new AppRoleAuthentication(options,
prepare().getRestTemplate());
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), prepare().getRestTemplate());
assertThat(executor.login()).isNotNull();
getVaultOperations().write(
"auth/approle/role/with-secret-id/secret-id-accessor/destroy",
customSecretIdResponse.getData());
}
private VaultOperations getVaultOperations() {
return prepare().getVaultOperations();
}

View File

@@ -0,0 +1,171 @@
/*
* 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.net.URI;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.vault.VaultException;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultClients.PrefixAwareUriTemplateHandler;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.get;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
/**
* Unit tests for {@link AuthenticationStepsExecutor}.
*
* @author Mark Paluch
*/
public class AuthenticationStepsExecutorUnitTests {
private RestTemplate restTemplate;
private MockRestServiceServer mockRest;
@Before
public void before() throws Exception {
RestTemplate restTemplate = VaultClients.createRestTemplate();
restTemplate.setUriTemplateHandler(new PrefixAwareUriTemplateHandler());
this.mockRest = MockRestServiceServer.createServer(restTemplate);
this.restTemplate = restTemplate;
}
@Test
public void justTokenShouldLogin() {
AuthenticationSteps steps = AuthenticationSteps.just(VaultToken.of("my-token"));
assertThat(login(steps)).isEqualTo(VaultToken.of("my-token"));
}
@Test
public void supplierOfStringShouldLoginWithMap() {
AuthenticationSteps steps = AuthenticationSteps.fromSupplier(() -> "my-token")
.login(VaultToken::of);
assertThat(login(steps)).isEqualTo(VaultToken.of("my-token"));
}
@Test
public void justLoginRequestShouldLogin() {
mockRest.expect(requestTo("/auth/cert/login"))
.andExpect(method(HttpMethod.POST))
.andRespond(
withSuccess()
.contentType(MediaType.APPLICATION_JSON)
.body("{"
+ "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}"
+ "}"));
AuthenticationSteps steps = AuthenticationSteps.just(post("/auth/{path}/login",
"cert").as(VaultResponse.class));
assertThat(login(steps)).isEqualTo(VaultToken.of("my-token"));
}
@Test
public void justLoginShouldFail() {
mockRest.expect(requestTo("/auth/cert/login")).andExpect(method(HttpMethod.POST))
.andRespond(withBadRequest().body("foo"));
AuthenticationSteps steps = AuthenticationSteps.just(post("/auth/{path}/login",
"cert").as(VaultResponse.class));
assertThatExceptionOfType(VaultException.class)
.isThrownBy(() -> login(steps))
.withMessage(
"HTTP request POST /auth/{path}/login AS class org.springframework.vault.support.VaultResponse "
+ "in state null failed with Status 400 and body foo");
}
@Test
public void initialRequestWithMapShouldLogin() {
mockRest.expect(requestTo("somewhere/else")).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess().contentType(MediaType.TEXT_PLAIN).body("foo"));
mockRest.expect(requestTo("/auth/cert/login"))
.andExpect(method(HttpMethod.POST))
.andExpect(content().string("foo-token"))
.andRespond(
withSuccess()
.contentType(MediaType.APPLICATION_JSON)
.body("{"
+ "\"auth\":{\"client_token\":\"foo-token\", \"renewable\": true, \"lease_duration\": 10}"
+ "}"));
AuthenticationSteps steps = AuthenticationSteps
.fromHttpRequest(get(URI.create("somewhere/else")).as(String.class))
.onNext(System.out::println) //
.map(s -> s.concat("-token")) //
.login("/auth/cert/login");
assertThat(login(steps)).isEqualTo(VaultToken.of("foo-token"));
}
@Test
public void requestWithHeadersShouldLogin() {
mockRest.expect(requestTo("somewhere/else")) //
.andExpect(header("foo", "bar")) //
.andExpect(method(HttpMethod.GET)) //
.andRespond(withSuccess().contentType(MediaType.TEXT_PLAIN).body("foo"));
mockRest.expect(requestTo("/auth/cert/login"))
.andExpect(content().string("foo"))
.andRespond(
withSuccess()
.contentType(MediaType.APPLICATION_JSON)
.body("{"
+ "\"auth\":{\"client_token\":\"foo-token\", \"renewable\": true, \"lease_duration\": 10}"
+ "}"));
HttpHeaders headers = new HttpHeaders();
headers.add("foo", "bar");
AuthenticationSteps steps = AuthenticationSteps.fromHttpRequest(
get(URI.create("somewhere/else")).with(headers).as(String.class)) //
.login("/auth/cert/login");
assertThat(login(steps)).isEqualTo(VaultToken.of("foo-token"));
}
private VaultToken login(AuthenticationSteps steps) {
return new AuthenticationStepsExecutor(steps, restTemplate).login();
}
}

View File

@@ -129,6 +129,44 @@ public class AwsEc2AuthenticationUnitTests {
assertThat(((LoginToken) login).isRenewable()).isFalse();
}
@Test
public void authenticationChainShouldLogin() throws Exception {
Nonce nonce = Nonce.provided("foo".toCharArray());
AwsEc2AuthenticationOptions authenticationOptions = AwsEc2AuthenticationOptions
.builder().nonce(nonce).build();
mockRest.expect(
requestTo("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")) //
.andExpect(method(HttpMethod.GET)) //
.andRespond(withSuccess().body("value"));
mockRest.expect(requestTo("/auth/aws-ec2/login"))
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath("$.pkcs7").value("value"))
.andExpect(jsonPath("$.nonce").value("foo"))
.andRespond(
withSuccess()
.contentType(MediaType.APPLICATION_JSON)
.body("{"
+ "\"auth\":{\"client_token\":\"my-token\", \"lease_duration\":20}"
+ "}"));
AwsEc2Authentication authentication = new AwsEc2Authentication(
authenticationOptions, restTemplate, restTemplate);
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
authentication.getAuthenticationSteps(), restTemplate);
VaultToken login = executor.login();
assertThat(login).isInstanceOf(LoginToken.class);
assertThat(login.getToken()).isEqualTo("my-token");
assertThat(((LoginToken) login).getLeaseDuration()).isEqualTo(
Duration.ofSeconds(20));
assertThat(((LoginToken) login).isRenewable()).isFalse();
}
@Test(expected = VaultException.class)
public void loginShouldFailWhileObtainingIdentityDocument() throws Exception {

View File

@@ -48,23 +48,22 @@ import static org.springframework.vault.util.Settings.findWorkDir;
*
* @author Mark Paluch
*/
public class ClientCertificateAuthenticationIntegrationTests
extends IntegrationTestSupport {
public class ClientCertificateAuthenticationIntegrationTests extends
IntegrationTestSupport {
@Before
public void before() throws Exception {
public void before() {
if (!prepare().hasAuth("cert")) {
prepare().mountAuth("cert");
}
prepare().getVaultOperations()
.doWithSession((RestOperationsCallback<Object>) restOperations -> {
prepare().getVaultOperations().doWithSession(
(RestOperationsCallback<Object>) restOperations -> {
File workDir = findWorkDir();
String certificate = Files.contentOf(
new File(workDir, "ca/certs/client.cert.pem"),
StandardCharsets.US_ASCII);
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),
@@ -73,7 +72,7 @@ public class ClientCertificateAuthenticationIntegrationTests
}
@Test
public void shouldLoginSuccessfully() throws Exception {
public void shouldLoginSuccessfully() {
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), prepareCertAuthenticationMethod());
@@ -90,7 +89,7 @@ public class ClientCertificateAuthenticationIntegrationTests
// 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 loginShouldFail() throws Exception {
public void loginShouldFail() {
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
.create(new ClientOptions(), Settings.createSslConfiguration());
@@ -100,6 +99,41 @@ public class ClientCertificateAuthenticationIntegrationTests
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();

View File

@@ -43,24 +43,9 @@ import static org.junit.Assume.assumeNotNull;
public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupport {
@Test
public void shouldCreateWrappedToken() throws Exception {
public void shouldCreateWrappedToken() {
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);
Map<String, String> wrapInfo = prepareWrappedToken();
String initialToken = wrapInfo.get("token");
@@ -76,7 +61,29 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
}
@Test
public void loginShouldFail() throws Exception {
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() {
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("Hello")).wrapped().build();
@@ -95,4 +102,25 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
.hasMessageContaining("permission denied");
}
}
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;
}
}