Skip to content

Commit 36a4b18

Browse files
Merge pull request #105 from dubreuia/master
Add support for response wrapping #78
2 parents c97d805 + 862ea73 commit 36a4b18

7 files changed

Lines changed: 442 additions & 26 deletions

File tree

src/main/java/com/bettercloud/vault/api/Auth.java

Lines changed: 175 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import com.bettercloud.vault.json.Json;
77
import com.bettercloud.vault.json.JsonObject;
88
import com.bettercloud.vault.response.AuthResponse;
9+
import com.bettercloud.vault.response.LogicalResponse;
910
import com.bettercloud.vault.response.LookupResponse;
10-
import com.bettercloud.vault.rest.RestResponse;
1111
import com.bettercloud.vault.rest.Rest;
12+
import com.bettercloud.vault.rest.RestResponse;
1213
import lombok.Getter;
1314

1415
import java.io.Serializable;
16+
import java.net.URI;
1517
import java.util.List;
1618
import java.util.Map;
1719
import java.util.UUID;
@@ -1084,17 +1086,17 @@ public AuthResponse renewSelf(final long increment, final String tokenAuthMount)
10841086

10851087
/**
10861088
* <p>Returns information about the current client token.</p>
1087-
*
1089+
*
10881090
* @return The response information returned from Vault
10891091
* @throws VaultException If any error occurs, or unexpected response received from Vault
10901092
*/
10911093
public LookupResponse lookupSelf() throws VaultException {
10921094
return lookupSelf("token");
10931095
}
1094-
1096+
10951097
/**
10961098
* <p>Returns information about the current client token.</p>
1097-
*
1099+
*
10981100
* @param tokenAuthMount The mount name of the token authentication back end. If null, defaults to "token"
10991101
* @return The response information returned from Vault
11001102
* @throws VaultException If any error occurs, or unexpected response received from Vault
@@ -1142,6 +1144,68 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
11421144
}
11431145
}
11441146

1147+
/**
1148+
* <p>Returns information about the current client token for a wrapped token, for which the lookup endpoint is
1149+
* different at "sys/wrapping/lookup". Example usage:</p>
1150+
*
1151+
* <blockquote>
1152+
* <pre>{@code
1153+
* final String wrappingToken = "...";
1154+
* final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
1155+
* final Vault vault = new Vault(config);
1156+
* final LogicalResponse response = vault.auth().lookupWarp();
1157+
* // Then you can validate "path" for example ...
1158+
* final String path = response.getData().get("path");
1159+
* }</pre>
1160+
* </blockquote>
1161+
*
1162+
* @return The response information returned from Vault
1163+
* @throws VaultException If any error occurs, or unexpected response received from Vault
1164+
*/
1165+
public LogicalResponse lookupWrap() throws VaultException {
1166+
int retryCount = 0;
1167+
while (true) {
1168+
try {
1169+
// HTTP request to Vault
1170+
final RestResponse restResponse = new Rest()//NOPMD
1171+
.url(config.getAddress() + "/v1/sys/wrapping/lookup")
1172+
.header("X-Vault-Token", config.getToken())
1173+
.connectTimeoutSeconds(config.getOpenTimeout())
1174+
.readTimeoutSeconds(config.getReadTimeout())
1175+
.sslVerification(config.getSslConfig().isVerify())
1176+
.sslContext(config.getSslConfig().getSslContext())
1177+
.get();
1178+
// Validate restResponse
1179+
if (restResponse.getStatus() != 200) {
1180+
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(),
1181+
restResponse.getStatus());
1182+
}
1183+
final String mimeType = restResponse.getMimeType();
1184+
if (mimeType == null || !"application/json".equals(mimeType)) {
1185+
throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus());
1186+
}
1187+
return new LogicalResponse(restResponse, retryCount);
1188+
} catch (Exception e) {
1189+
// If there are retries to perform, then pause for the configured interval and then execute the loop
1190+
// again...
1191+
if (retryCount < config.getMaxRetries()) {
1192+
retryCount++;
1193+
try {
1194+
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
1195+
Thread.sleep(retryIntervalMilliseconds);
1196+
} catch (InterruptedException e1) {
1197+
e1.printStackTrace(); //NOPMD
1198+
}
1199+
} else if (e instanceof VaultException) { //NOPMD
1200+
// ... otherwise, give up.
1201+
throw (VaultException) e;
1202+
} else {
1203+
throw new VaultException(e);
1204+
}
1205+
}
1206+
}
1207+
}
1208+
11451209
/**
11461210
* <p>Revokes current client token.</p>
11471211
*
@@ -1196,4 +1260,111 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
11961260
}
11971261
}
11981262

1263+
/**
1264+
* <p>Returns the original response inside the wrapped auth token. This method is useful if you need to unwrap a
1265+
* token without being authenticated. See {@link #unwrap(String)} if you need to do that authenticated.</p>
1266+
*
1267+
* <p>In the example below, you cannot use twice the {@code VaultConfig}, since
1268+
* after the first usage of the {@code wrappingToken}, it is not usable anymore. You need to use the
1269+
* {@code unwrappedToken} in a new vault configuration to continue. Example usage:</p>
1270+
*
1271+
* <blockquote>
1272+
* <pre>{@code
1273+
* final String wrappingToken = "...";
1274+
* final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
1275+
* final Vault vault = new Vault(config);
1276+
* final AuthResponse response = vault.auth().unwrap();
1277+
* final String unwrappedToken = response.getAuthClientToken();
1278+
* }</pre>
1279+
* </blockquote>
1280+
*
1281+
* @return The response information returned from Vault
1282+
* @throws VaultException If any error occurs, or unexpected response received from Vault
1283+
* @see #unwrap(String)
1284+
*/
1285+
public AuthResponse unwrap() throws VaultException {
1286+
return unwrap(null);
1287+
}
1288+
1289+
/**
1290+
* <p>Returns the original response inside the given wrapped auth token. This method is useful if you need to unwrap
1291+
* a token, while being already authenticated. Do NOT authenticate in vault with your wrapping token, since it will
1292+
* both fail authentication and invalidate the wrapping token at the same time. See {@link #unwrap()} if you need to
1293+
* do that without being authenticated.</p>
1294+
*
1295+
* <p>In the example below, {@code authToken} is NOT your wrapped token, and should have unwrapping permissions.
1296+
* The unwrapped token in {@code unwrappedToken}. Example usage:</p>
1297+
*
1298+
* <blockquote>
1299+
* <pre>{@code
1300+
* final String authToken = "...";
1301+
* final String wrappingToken = "...";
1302+
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
1303+
* final Vault vault = new Vault(config);
1304+
* final AuthResponse response = vault.auth().unwrap(wrappingToken);
1305+
* final String unwrappedToken = response.getAuthClientToken();
1306+
* }</pre>
1307+
* </blockquote>
1308+
*
1309+
* @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your {@link VaultConfig#token},
1310+
* if token is {@code null}, this method will unwrap the auth token in {@link VaultConfig#token}
1311+
* @return The response information returned from Vault
1312+
* @throws VaultException If any error occurs, or unexpected response received from Vault
1313+
* @see #unwrap()
1314+
*/
1315+
public AuthResponse unwrap(final String wrappedToken) throws VaultException {
1316+
int retryCount = 0;
1317+
while (true) {
1318+
try {
1319+
// Parse parameters to JSON
1320+
final JsonObject jsonObject = Json.object();
1321+
if (wrappedToken != null) {
1322+
jsonObject.add("token", wrappedToken);
1323+
}
1324+
1325+
final String requestJson = jsonObject.toString();
1326+
final String url = config.getAddress() + "/v1/sys/wrapping/unwrap";
1327+
1328+
// HTTP request to Vault
1329+
final RestResponse restResponse = new Rest()
1330+
.url(url)
1331+
.header("X-Vault-Token", config.getToken())
1332+
.body(requestJson.getBytes("UTF-8"))
1333+
.connectTimeoutSeconds(config.getOpenTimeout())
1334+
.readTimeoutSeconds(config.getReadTimeout())
1335+
.sslVerification(config.getSslConfig().isVerify())
1336+
.sslContext(config.getSslConfig().getSslContext())
1337+
.post();
1338+
1339+
// Validate restResponse
1340+
if (restResponse.getStatus() != 200) {
1341+
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(),
1342+
restResponse.getStatus());
1343+
}
1344+
final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType();
1345+
if (!mimeType.equals("application/json")) {
1346+
throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus());
1347+
}
1348+
return new AuthResponse(restResponse, retryCount);
1349+
} catch (final Exception e) {
1350+
// If there are retries to perform, then pause for the configured interval and then execute the
1351+
// loop again...
1352+
if (retryCount < config.getMaxRetries()) {
1353+
retryCount++;
1354+
try {
1355+
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
1356+
Thread.sleep(retryIntervalMilliseconds);
1357+
} catch (InterruptedException e1) {
1358+
e1.printStackTrace();
1359+
}
1360+
} else if (e instanceof VaultException) {
1361+
// ... otherwise, give up.
1362+
throw (VaultException) e;
1363+
} else {
1364+
throw new VaultException(e);
1365+
}
1366+
}
1367+
}
1368+
}
1369+
11991370
}

src/test/java/com/bettercloud/vault/vault/VaultTestUtils.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.bettercloud.vault.vault;
22

3+
import com.bettercloud.vault.json.Json;
4+
import com.bettercloud.vault.json.JsonObject;
35
import com.bettercloud.vault.vault.mock.MockVault;
6+
import org.apache.commons.io.IOUtils;
47
import org.eclipse.jetty.server.Connector;
58
import org.eclipse.jetty.server.HttpConfiguration;
69
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -10,6 +13,15 @@
1013
import org.eclipse.jetty.server.SslConnectionFactory;
1114
import org.eclipse.jetty.util.ssl.SslContextFactory;
1215

16+
import javax.servlet.http.HttpServletRequest;
17+
import java.io.IOException;
18+
import java.util.Collections;
19+
import java.util.Map;
20+
import java.util.Optional;
21+
22+
import static java.util.function.Function.identity;
23+
import static java.util.stream.Collectors.toMap;
24+
1325
/**
1426
* <p>Utilities used by all of the Vault-related unit test classes under
1527
* <code>src/test/java/com/bettercloud/vault</code>, to setup and shutdown mock Vault server implementations.</p>
@@ -54,5 +66,21 @@ public static void shutdownMockVault(final Server server) throws Exception {
5466
}
5567
}
5668

69+
public static Optional<JsonObject> readRequestBody(HttpServletRequest request) {
70+
try {
71+
StringBuilder requestBuffer = new StringBuilder();
72+
IOUtils.readLines(request.getReader()).forEach(requestBuffer::append);
73+
String string = requestBuffer.toString();
74+
return string.isEmpty() ? Optional.empty() : Optional.of(Json.parse(string).asObject());
75+
} catch (IOException e) {
76+
return Optional.empty();
77+
}
78+
}
79+
80+
public static Map<String, String> readRequestHeaders(HttpServletRequest request) {
81+
return Collections.list(request.getHeaderNames()).stream()
82+
.collect(toMap(identity(), request::getHeader));
83+
}
84+
5785
}
5886

src/test/java/com/bettercloud/vault/vault/api/AuthBackendAwsTests.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
import com.bettercloud.vault.Vault;
44
import com.bettercloud.vault.VaultConfig;
55
import com.bettercloud.vault.VaultException;
6-
import com.bettercloud.vault.json.Json;
76
import com.bettercloud.vault.json.JsonObject;
87
import com.bettercloud.vault.vault.VaultTestUtils;
98
import com.bettercloud.vault.vault.mock.AuthRequestValidatingMockVault;
10-
import org.apache.commons.io.IOUtils;
119
import org.eclipse.jetty.server.Server;
1210
import org.junit.Test;
1311

1412
import javax.servlet.http.HttpServletRequest;
1513
import java.util.function.Predicate;
1614

15+
import static com.bettercloud.vault.vault.VaultTestUtils.readRequestBody;
1716
import static org.junit.Assert.assertEquals;
1817
import static org.junit.Assert.assertNotNull;
1918

@@ -23,7 +22,7 @@ public class AuthBackendAwsTests {
2322
public void testLoginByAwsEc2Id() throws Exception {
2423
final Predicate<HttpServletRequest> isValidEc2IdRequest = (request) -> {
2524
try {
26-
JsonObject requestBody = readRequestBody(request);
25+
JsonObject requestBody = readRequestBody(request).orElse(null);
2726
return requestBody != null && request.getRequestURI().endsWith("/auth/aws/login") &&
2827
requestBody.getString("identity", "").equals("identity") &&
2928
requestBody.getString("signature", "").equals("signature");
@@ -59,7 +58,7 @@ public void testLoginByAwsEc2Id() throws Exception {
5958
public void testLoginByAwsEc2Pkcs7() throws Exception {
6059
final Predicate<HttpServletRequest> isValidEc2pkcs7Request = (request) -> {
6160
try {
62-
JsonObject requestBody = readRequestBody(request);
61+
JsonObject requestBody = readRequestBody(request).orElse(null);
6362
return requestBody != null && request.getRequestURI().endsWith("/auth/aws/login") &&
6463
requestBody.getString("pkcs7", "").equals("pkcs7");
6564
} catch (Exception e) {
@@ -95,7 +94,7 @@ public void testLoginByAwsEc2Pkcs7() throws Exception {
9594
@Test
9695
public void testLoginByAwsIam() throws Exception {
9796
final Predicate<HttpServletRequest> isValidEc2IamRequest = (request) -> {
98-
JsonObject requestBody = readRequestBody(request);
97+
JsonObject requestBody = readRequestBody(request).orElse(null);
9998
return requestBody != null && request.getRequestURI().endsWith("/auth/aws/login") &&
10099
requestBody.getString("iam_http_request_method", "").equals("POST") &&
101100
requestBody.getString("iam_request_url", "").equals("url") &&
@@ -123,14 +122,4 @@ public void testLoginByAwsIam() throws Exception {
123122
assertEquals("c9368254-3f21-aded-8a6f-7c818e81b17a", token.trim());
124123
}
125124

126-
private JsonObject readRequestBody(HttpServletRequest request) {
127-
try {
128-
StringBuilder requestBuffer = new StringBuilder();
129-
IOUtils.readLines(request.getReader()).forEach(requestBuffer::append);
130-
return Json.parse(requestBuffer.toString()).asObject();
131-
} catch (Exception e) {
132-
return null;
133-
}
134-
}
135-
136125
}

0 commit comments

Comments
 (0)