Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 175 additions & 4 deletions src/main/java/com/bettercloud/vault/api/Auth.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import com.bettercloud.vault.json.Json;
import com.bettercloud.vault.json.JsonObject;
import com.bettercloud.vault.response.AuthResponse;
import com.bettercloud.vault.response.LogicalResponse;
import com.bettercloud.vault.response.LookupResponse;
import com.bettercloud.vault.rest.RestResponse;
import com.bettercloud.vault.rest.Rest;
import com.bettercloud.vault.rest.RestResponse;
import lombok.Getter;

import java.io.Serializable;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.UUID;
Expand Down Expand Up @@ -1084,17 +1086,17 @@ public AuthResponse renewSelf(final long increment, final String tokenAuthMount)

/**
* <p>Returns information about the current client token.</p>
*
*
* @return The response information returned from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
*/
public LookupResponse lookupSelf() throws VaultException {
return lookupSelf("token");
}

/**
* <p>Returns information about the current client token.</p>
*
*
* @param tokenAuthMount The mount name of the token authentication back end. If null, defaults to "token"
* @return The response information returned from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
Expand Down Expand Up @@ -1142,6 +1144,68 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
}
}

/**
* <p>Returns information about the current client token for a wrapped token, for which the lookup endpoint is
* different at "sys/wrapping/lookup". Example usage:</p>
*
* <blockquote>
* <pre>{@code
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
* final Vault vault = new Vault(config);
* final LogicalResponse response = vault.auth().lookupWarp();
* // Then you can validate "path" for example ...
* final String path = response.getData().get("path");
* }</pre>
* </blockquote>
*
* @return The response information returned from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
*/
public LogicalResponse lookupWrap() throws VaultException {
int retryCount = 0;
while (true) {
try {
// HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/sys/wrapping/lookup")
.header("X-Vault-Token", config.getToken())
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.get();
// Validate restResponse
if (restResponse.getStatus() != 200) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(),
restResponse.getStatus());
}
final String mimeType = restResponse.getMimeType();
if (mimeType == null || !"application/json".equals(mimeType)) {
throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus());
}
return new LogicalResponse(restResponse, retryCount);
} catch (Exception e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop
// again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace(); //NOPMD
}
} else if (e instanceof VaultException) { //NOPMD
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}

/**
* <p>Revokes current client token.</p>
*
Expand Down Expand Up @@ -1196,4 +1260,111 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
}
}

/**
* <p>Returns the original response inside the wrapped auth token. This method is useful if you need to unwrap a
* token without being authenticated. See {@link #unwrap(String)} if you need to do that authenticated.</p>
*
* <p>In the example below, you cannot use twice the {@code VaultConfig}, since
* after the first usage of the {@code wrappingToken}, it is not usable anymore. You need to use the
* {@code unwrappedToken} in a new vault configuration to continue. Example usage:</p>
*
* <blockquote>
* <pre>{@code
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
* final Vault vault = new Vault(config);
* final AuthResponse response = vault.auth().unwrap();
* final String unwrappedToken = response.getAuthClientToken();
* }</pre>
* </blockquote>
*
* @return The response information returned from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
* @see #unwrap(String)
*/
public AuthResponse unwrap() throws VaultException {
return unwrap(null);
}

/**
* <p>Returns the original response inside the given wrapped auth token. This method is useful if you need to unwrap
* a token, while being already authenticated. Do NOT authenticate in vault with your wrapping token, since it will
* both fail authentication and invalidate the wrapping token at the same time. See {@link #unwrap()} if you need to
* do that without being authenticated.</p>
*
* <p>In the example below, {@code authToken} is NOT your wrapped token, and should have unwrapping permissions.
* The unwrapped token in {@code unwrappedToken}. Example usage:</p>
*
* <blockquote>
* <pre>{@code
* final String authToken = "...";
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
* final Vault vault = new Vault(config);
* final AuthResponse response = vault.auth().unwrap(wrappingToken);
* final String unwrappedToken = response.getAuthClientToken();
* }</pre>
* </blockquote>
*
* @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your {@link VaultConfig#token},
* if token is {@code null}, this method will unwrap the auth token in {@link VaultConfig#token}
* @return The response information returned from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
* @see #unwrap()
*/
public AuthResponse unwrap(final String wrappedToken) throws VaultException {
int retryCount = 0;
while (true) {
try {
// Parse parameters to JSON
final JsonObject jsonObject = Json.object();
if (wrappedToken != null) {
jsonObject.add("token", wrappedToken);
}

final String requestJson = jsonObject.toString();
final String url = config.getAddress() + "/v1/sys/wrapping/unwrap";

// HTTP request to Vault
final RestResponse restResponse = new Rest()
.url(url)
.header("X-Vault-Token", config.getToken())
.body(requestJson.getBytes("UTF-8"))
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.post();

// Validate restResponse
if (restResponse.getStatus() != 200) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(),
restResponse.getStatus());
}
final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType();
if (!mimeType.equals("application/json")) {
throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus());
}
return new AuthResponse(restResponse, retryCount);
} catch (final Exception e) {
// If there are retries to perform, then pause for the configured interval and then execute the
// loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}

}
28 changes: 28 additions & 0 deletions src/test/java/com/bettercloud/vault/vault/VaultTestUtils.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.bettercloud.vault.vault;

import com.bettercloud.vault.json.Json;
import com.bettercloud.vault.json.JsonObject;
import com.bettercloud.vault.vault.mock.MockVault;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
Expand All @@ -10,6 +13,15 @@
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

/**
* <p>Utilities used by all of the Vault-related unit test classes under
* <code>src/test/java/com/bettercloud/vault</code>, to setup and shutdown mock Vault server implementations.</p>
Expand Down Expand Up @@ -54,5 +66,21 @@ public static void shutdownMockVault(final Server server) throws Exception {
}
}

public static Optional<JsonObject> readRequestBody(HttpServletRequest request) {
try {
StringBuilder requestBuffer = new StringBuilder();
IOUtils.readLines(request.getReader()).forEach(requestBuffer::append);
String string = requestBuffer.toString();
return string.isEmpty() ? Optional.empty() : Optional.of(Json.parse(string).asObject());
} catch (IOException e) {
return Optional.empty();
}
}

public static Map<String, String> readRequestHeaders(HttpServletRequest request) {
return Collections.list(request.getHeaderNames()).stream()
.collect(toMap(identity(), request::getHeader));
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@
import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.json.Json;
import com.bettercloud.vault.json.JsonObject;
import com.bettercloud.vault.vault.VaultTestUtils;
import com.bettercloud.vault.vault.mock.AuthRequestValidatingMockVault;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;
import java.util.function.Predicate;

import static com.bettercloud.vault.vault.VaultTestUtils.readRequestBody;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

Expand All @@ -23,7 +22,7 @@ public class AuthBackendAwsTests {
public void testLoginByAwsEc2Id() throws Exception {
final Predicate<HttpServletRequest> isValidEc2IdRequest = (request) -> {
try {
JsonObject requestBody = readRequestBody(request);
JsonObject requestBody = readRequestBody(request).orElse(null);
return requestBody != null && request.getRequestURI().endsWith("/auth/aws/login") &&
requestBody.getString("identity", "").equals("identity") &&
requestBody.getString("signature", "").equals("signature");
Expand Down Expand Up @@ -59,7 +58,7 @@ public void testLoginByAwsEc2Id() throws Exception {
public void testLoginByAwsEc2Pkcs7() throws Exception {
final Predicate<HttpServletRequest> isValidEc2pkcs7Request = (request) -> {
try {
JsonObject requestBody = readRequestBody(request);
JsonObject requestBody = readRequestBody(request).orElse(null);
return requestBody != null && request.getRequestURI().endsWith("/auth/aws/login") &&
requestBody.getString("pkcs7", "").equals("pkcs7");
} catch (Exception e) {
Expand Down Expand Up @@ -95,7 +94,7 @@ public void testLoginByAwsEc2Pkcs7() throws Exception {
@Test
public void testLoginByAwsIam() throws Exception {
final Predicate<HttpServletRequest> isValidEc2IamRequest = (request) -> {
JsonObject requestBody = readRequestBody(request);
JsonObject requestBody = readRequestBody(request).orElse(null);
return requestBody != null && request.getRequestURI().endsWith("/auth/aws/login") &&
requestBody.getString("iam_http_request_method", "").equals("POST") &&
requestBody.getString("iam_request_url", "").equals("url") &&
Expand Down Expand Up @@ -123,14 +122,4 @@ public void testLoginByAwsIam() throws Exception {
assertEquals("c9368254-3f21-aded-8a6f-7c818e81b17a", token.trim());
}

private JsonObject readRequestBody(HttpServletRequest request) {
try {
StringBuilder requestBuffer = new StringBuilder();
IOUtils.readLines(request.getReader()).forEach(requestBuffer::append);
return Json.parse(requestBuffer.toString()).asObject();
} catch (Exception e) {
return null;
}
}

}
Loading