login = new HashMap<>();
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java
new file mode 100644
index 00000000..b2644032
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java
@@ -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}.
+ *
+ * Static generators are the main entry point to start with a flow composition. An example
+ * authentication using AWS-EC2 authentication:
+ *
+ *
+ * String nonce = "";
+ * return AuthenticationSteps
+ * .fromHttpRequest(
+ * HttpRequestBuilder.get(options.getIdentityDocumentUri().toString()) //
+ * .as(String.class)) //
+ * .map(pkcs7 -> pkcs7.replaceAll("\\r", "")) //
+ * .map(pkcs7 -> {
+ *
+ * Map<String, String> login = new HashMap<>();
+ *
+ * login.put("nonce", new String(nonce));
+ * login.put("pkcs7", pkcs7);
+ *
+ * return login;
+ * }).login("auth/{mount}/login", "aws");
+ *
+ *
+ *
+ * To perform a computation, authentication steps are composed into a pipeline . A
+ * pipeline consists of a source (which might be an object, a supplier function, a HTTP
+ * request, etc), zero or more intermediate operations (which transform the
+ * authentication state object into another object, such as {@link Node#map(Function)}),
+ * and a terminal operation 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.
+ *
+ *
+ * {@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 HEAD = new Node<>();
+
+ final List> 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 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 Node fromSupplier(Supplier 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 Node fromHttpRequest(HttpRequest 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 authentication state object type produced by this node.
+ */
+ public static class Node {
+
+ /**
+ * 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 resulting object type
+ * @return the next {@link Node}.
+ */
+ public Node 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 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 Node request(HttpRequest 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 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 HttpRequest as(Class type) {
+
+ Assert.notNull(type, "Result type must not be null");
+
+ return new HttpRequest<>(this, type);
+ }
+ }
+
+ /**
+ * Value object representing a HTTP request.
+ *
+ * @param authentication state object type produced by this request.
+ */
+ @FieldDefaults(makeFinal = true, level = AccessLevel.PACKAGE)
+ @Getter(AccessLevel.PACKAGE)
+ public static class HttpRequest {
+
+ HttpMethod method;
+
+ URI uri;
+
+ String uriTemplate;
+
+ String[] urlVariables;
+
+ HttpEntity> entity;
+
+ Class responseType;
+
+ HttpRequest(HttpRequestBuilder builder, Class 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 extends Node implements PathAware {
+
+ @NonNull
+ HttpRequest definition;
+
+ @NonNull
+ Node> previous;
+
+ @Override
+ public String toString() {
+ return definition.toString();
+ }
+ }
+
+ @Value
+ @EqualsAndHashCode(callSuper = false)
+ @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+ static class MapStep extends Node 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 extends Node 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 extends Node implements PathAware {
+
+ @NonNull
+ Supplier supplier;
+ @NonNull
+ Node> previous;
+
+ public T get() {
+ return supplier.get();
+ }
+
+ @Override
+ public String toString() {
+ return "Supplier: " + supplier.toString();
+ }
+ }
+
+ interface PathAware {
+ Node> getPrevious();
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java
new file mode 100644
index 00000000..fa2e5841
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java
@@ -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) o, state);
+ }
+
+ if (o instanceof AuthenticationSteps.MapStep) {
+ state = doMapStep((MapStep) o, state);
+ }
+
+ if (o instanceof OnNextStep) {
+ state = doOnNext((OnNextStep) o, state);
+ }
+
+ if (o instanceof AuthenticationSteps.SupplierStep>) {
+ state = doSupplierStep((SupplierStep) 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 supplierStep) {
+ return supplierStep.get();
+ }
+
+ private static Object doMapStep(MapStep o, Object state) {
+ return o.apply(state);
+ }
+
+ private static Object doOnNext(OnNextStep o, Object state) {
+ return o.apply(state);
+ }
+
+ private Object doHttpRequest(HttpRequestNode step, Object state) {
+
+ HttpRequest 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;
+ }
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsFactory.java
new file mode 100644
index 00000000..17d84842
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsFactory.java
@@ -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}.
+ *
+ * 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();
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java
index 58df0e21..edb2cc57 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java
@@ -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 Auth Backend:
* aws-ec2
*/
-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 metadata = (Map) 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 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 getEc2Login() {
Map 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);
}
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java
index bce66095..7f4e874c 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java
@@ -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));
+ }
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java
index 44aa0a9a..deb28011 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java
@@ -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.
*
@@ -132,7 +136,8 @@ import org.springframework.web.client.RestOperations;
* "https://www.vaultproject.io/docs/concepts/response-wrapping.html">Response
* Wrapping
*/
-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 initialRequest = get(options.getPath()) //
+ .with(VaultHttpHeaders.from(options.getInitialToken())) //
+ .as(VaultResponse.class);
+
+ return AuthenticationSteps.fromHttpRequest(initialRequest) //
+ .map(VaultResponseSupport::getData) //
+ .login(this::getToken);
+ }
+
private Map lookupToken() {
try {
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java
index 7fe1eeaf..f1f3e632 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java
@@ -25,7 +25,8 @@ import org.springframework.vault.support.VaultToken;
* @see VaultToken
* @see Auth Backend: Token
*/
-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);
+ }
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java
index 1fbac987..f8b4edf6 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java
@@ -84,16 +84,15 @@ public class VaultClients {
*/
public static RestTemplate createRestTemplate() {
- List> messageConverters = new ArrayList<>(
- 3);
+ List> 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;
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java
index 612d027f..c4e99cb3 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java
@@ -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 appIdData = new HashMap();
- appIdData.put("value", "dummy"); // policy
- appIdData.put("display_name", "this is my test application");
+ Map appIdData = new HashMap();
+ 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 userIdData = new HashMap();
- userIdData.put("value", "myapp"); // name of the app-id
- userIdData.put("cidr_block", "0.0.0.0/0");
+ Map userIdData = new HashMap();
+ 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();
}
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java
index 745a7e8e..8b3d09bd 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java
@@ -58,22 +58,22 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor
Map withSecretId = new HashMap();
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 noSecretIdRole = new HashMap();
- noSecretIdRole.put("policies", "dummy"); // policy
- noSecretIdRole.put("bound_cidr_list", "0.0.0.0/0");
- noSecretIdRole.put("bind_secret_id", "false");
+ Map noSecretIdRole = new HashMap();
+ 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();
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AuthenticationStepsExecutorUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AuthenticationStepsExecutorUnitTests.java
new file mode 100644
index 00000000..28b27877
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AuthenticationStepsExecutorUnitTests.java
@@ -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();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java
index 31c198f5..0681a915 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java
@@ -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 {
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
index beef7054..6817e856 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
@@ -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) restOperations -> {
+ prepare().getVaultOperations().doWithSession(
+ (RestOperationsCallback) 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();
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java
index bd99d719..5e4c87e4 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java
@@ -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 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(headers),
- VaultResponse.class);
- });
-
- Map wrapInfo = response.getBody().getWrapInfo();
-
- // Response Wrapping requires Vault 0.6.0+
- assumeNotNull(wrapInfo);
+ Map 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 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 prepareWrappedToken() {
+
+ ResponseEntity 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(headers),
+ VaultResponse.class);
+ });
+
+ Map wrapInfo = response.getBody().getWrapInfo();
+
+ // Response Wrapping requires Vault 0.6.0+
+ assumeNotNull(wrapInfo);
+ return wrapInfo;
+ }
}