@@ -1282,7 +1282,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
12821282
12831283 /**
12841284 * <p>Returns information about the current client token for a wrapped token, for which the
1285- * lookup endpoint is different at "sys/wrapping/lookup". Example usage:</p>
1285+ * lookup endpoint is at "sys/wrapping/lookup". Example usage:</p>
12861286 *
12871287 * <blockquote>
12881288 * <pre>{@code
@@ -1299,23 +1299,85 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
12991299 * @throws VaultException If any error occurs, or unexpected response received from Vault
13001300 */
13011301 public LogicalResponse lookupWrap () throws VaultException {
1302+ return lookupWrap (config .getToken (), false );
1303+ }
1304+
1305+ /**
1306+ * <p>Returns information about the a wrapped token when authorization is needed for lookup,
1307+ * for which the lookup endpoint is at "sys/wrapping/lookup". Example usage:</p>
1308+ *
1309+ * <blockquote>
1310+ * <pre>{@code
1311+ * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
1312+ * final Vault vault = new Vault(config);
1313+ * ...
1314+ * final String wrappingToken = "...";
1315+ * final LogicalResponse response = vault.auth().lookupWarp(wrappingToken);
1316+ * // Then you can validate "path" for example ...
1317+ * final String path = response.getData().get("path");
1318+ * }</pre>
1319+ * </blockquote>
1320+ *
1321+ * @param wrappedToken Wrapped token.
1322+ * @return The response information returned from Vault
1323+ * @throws VaultException If any error occurs, or unexpected response received from Vault
1324+ */
1325+ public LogicalResponse lookupWrap (final String wrappedToken ) throws VaultException {
1326+ return lookupWrap (wrappedToken , true );
1327+ }
1328+
1329+ /**
1330+ * <p>Returns information about the a wrapped token,
1331+ * for which the lookup endpoint is at "sys/wrapping/lookup". Example usage:</p>
1332+ *
1333+ * <blockquote>
1334+ * <pre>{@code
1335+ * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
1336+ * final Vault vault = new Vault(config);
1337+ * ...
1338+ * final String wrappingToken = "...";
1339+ * final LogicalResponse response = vault.auth().lookupWarp(wrappingToken);
1340+ * // Then you can validate "path" for example ...
1341+ * final String path = response.getData().get("path");
1342+ * }</pre>
1343+ * </blockquote>
1344+ *
1345+ * @param wrappedToken Wrapped token.
1346+ * @param inBody When {@code true} the token value placed in the body request: {@code {"token": "$wrappedToken"}},
1347+ * otherwise, set the token into header: {@code "X-Vault-Token: $wrappedToken"}.
1348+ * @return The response information returned from Vault
1349+ * @throws VaultException If any error occurs, or unexpected response received from Vault
1350+ */
1351+ public LogicalResponse lookupWrap (final String wrappedToken , boolean inBody ) throws VaultException {
1352+ final String requestJson = inBody ? Json .object ().add ("token" , wrappedToken ).toString () : null ;
1353+
13021354 return retry (attempt -> {
13031355 // HTTP request to Vault
1304- final RestResponse restResponse = new Rest ()//NOPMD
1356+ Rest rest = new Rest ()//NOPMD
13051357 .url (config .getAddress () + "/v1/sys/wrapping/lookup" )
1306- .header ("X-Vault-Token" , config .getToken ())
13071358 .header ("X-Vault-Namespace" , this .nameSpace )
13081359 .header ("X-Vault-Request" , "true" )
13091360 .connectTimeoutSeconds (config .getOpenTimeout ())
13101361 .readTimeoutSeconds (config .getReadTimeout ())
13111362 .sslVerification (config .getSslConfig ().isVerify ())
1312- .sslContext (config .getSslConfig ().getSslContext ())
1313- .get ();
1363+ .sslContext (config .getSslConfig ().getSslContext ());
1364+
1365+ if (inBody ) {
1366+ rest = rest
1367+ .header ("X-Vault-Token" , config .getToken ())
1368+ .body (requestJson .getBytes (StandardCharsets .UTF_8 ));
1369+ } else {
1370+ rest = rest .header ("X-Vault-Token" , wrappedToken );
1371+ }
1372+
1373+ final RestResponse restResponse = rest .post ();
13141374
13151375 // Validate restResponse
13161376 if (restResponse .getStatus () != 200 ) {
13171377 throw new VaultException (
1318- "Vault responded with HTTP status code: " + restResponse .getStatus (),
1378+ "Vault responded with HTTP status code: " + restResponse .getStatus () +
1379+ "\n Response body: " + new String (restResponse .getBody (),
1380+ StandardCharsets .UTF_8 ),
13191381 restResponse .getStatus ());
13201382 }
13211383
@@ -1399,7 +1461,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
13991461 * @see #unwrap(String)
14001462 */
14011463 public UnwrapResponse unwrap () throws VaultException {
1402- return unwrap (null );
1464+ return unwrap (config . getToken (), false );
14031465 }
14041466
14051467 /**
@@ -1452,28 +1514,87 @@ public UnwrapResponse unwrap() throws VaultException {
14521514 * @see #unwrap()
14531515 */
14541516 public UnwrapResponse unwrap (final String wrappedToken ) throws VaultException {
1455- return retry (attempt -> {
1456- // Parse parameters to JSON
1457- final JsonObject jsonObject = Json .object ();
1458- if (wrappedToken != null ) {
1459- jsonObject .add ("token" , wrappedToken );
1460- }
1517+ return unwrap (wrappedToken , true );
1518+ }
14611519
1462- final String requestJson = jsonObject .toString ();
1520+ /**
1521+ * <p>Provide access to the {@code /sys/wrapping/unwrap} endpoint.</p>
1522+ *
1523+ * <p>Returns the original response inside the given wrapping token. Unlike simply reading
1524+ * {@code cubbyhole/response} (which is deprecated), this endpoint provides additional
1525+ * validation checks on the token, returns the original value on the wire rather than a JSON
1526+ * string representation of it, and ensures that the response is properly audit-logged.</p>
1527+ *
1528+ * <p> This endpoint can be used by using a wrapping token as the client token in the API call,
1529+ * in which case the token parameter is not required; or, a different token with permissions to
1530+ * access this endpoint can make the call and pass in the wrapping token in the token parameter.
1531+ * Do not use the wrapping token in both locations; this will cause the wrapping token to be
1532+ * revoked but the value to be unable to be looked up, as it will basically be a double-use of
1533+ * the token!</p>
1534+ *
1535+ * <p>In the example below, {@code authToken} is NOT your wrapped token, and should have
1536+ * unwrapping permissions. The unwrapped data in {@link UnwrapResponse#getData()}. Example
1537+ * usage:</p>
1538+ *
1539+ * <blockquote>
1540+ * <pre>{@code
1541+ * final String authToken = "...";
1542+ * final String wrappingToken = "...";
1543+ * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
1544+ * final Vault vault = new Vault(config);
1545+ *
1546+ * final WrapResponse wrapResponse = vault.auth().wrap(
1547+ * // Data to wrap
1548+ * new JsonObject()
1549+ * .add("foo", "bar")
1550+ * .add("zoo", "zar"),
1551+ *
1552+ * // TTL of the response-wrapping token
1553+ * 60
1554+ * );
1555+ *
1556+ * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken(), true);
1557+ * final JsonObject unwrappedData = response.getData(); // original data
1558+ * }</pre>
1559+ * </blockquote>
1560+ *
1561+ * @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your
1562+ * {@link VaultConfig#getToken()}, if token is {@code null}, this method will unwrap the auth
1563+ * token in {@link VaultConfig#getToken()}
1564+ * @param inBody When {@code true} the token value placed in the body request: {@code {"token": "$wrappedToken"}},
1565+ * otherwise, set the token into header: {@code "X-Vault-Token: $wrappedToken"}.
1566+ * @return The response information returned from Vault
1567+ * @throws VaultException If any error occurs, or unexpected response received from Vault
1568+ * @see #wrap(JsonObject, int)
1569+ * @see #unwrap()
1570+ */
1571+ public UnwrapResponse unwrap (final String wrappedToken , boolean inBody ) throws VaultException {
1572+ Objects .requireNonNull (wrappedToken , "Wrapped token is null" );
1573+
1574+ return retry (attempt -> {
14631575 final String url = config .getAddress () + "/v1/sys/wrapping/unwrap" ;
14641576
14651577 // HTTP request to Vault
1466- final RestResponse restResponse = new Rest ()
1578+ Rest rest = new Rest ()
14671579 .url (url )
1468- .header ("X-Vault-Token" , config .getToken ())
14691580 .header ("X-Vault-Namespace" , this .nameSpace )
14701581 .header ("X-Vault-Request" , "true" )
1471- .body (requestJson .getBytes (StandardCharsets .UTF_8 ))
14721582 .connectTimeoutSeconds (config .getOpenTimeout ())
14731583 .readTimeoutSeconds (config .getReadTimeout ())
14741584 .sslVerification (config .getSslConfig ().isVerify ())
1475- .sslContext (config .getSslConfig ().getSslContext ())
1476- .post ();
1585+ .sslContext (config .getSslConfig ().getSslContext ());
1586+
1587+ if (inBody ) {
1588+ final String requestJson = Json .object ().add ("token" , wrappedToken ).toString ();
1589+ rest = rest
1590+ .header ("X-Vault-Token" , config .getToken ())
1591+ .body (requestJson .getBytes (StandardCharsets .UTF_8 ));
1592+ } else {
1593+ rest = rest
1594+ .header ("X-Vault-Token" , wrappedToken );
1595+ }
1596+
1597+ RestResponse restResponse = rest .post ();
14771598
14781599 // Validate restResponse
14791600 if (restResponse .getStatus () != 200 ) {
@@ -1560,7 +1681,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
15601681 // HTTP request to Vault
15611682 final RestResponse restResponse = new Rest ()
15621683 .url (url )
1563- .header ("X-Vault-Token" , config .getToken ())
1684+ .header ("X-Vault-Token" , config .getToken ())
15641685 .header ("X-Vault-Wrap-TTL" , Integer .toString (ttlInSec ))
15651686 .header ("X-Vault-Namespace" , this .nameSpace )
15661687 .header ("X-Vault-Request" , "true" )
@@ -1590,4 +1711,82 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
15901711 return new WrapResponse (restResponse , attempt );
15911712 });
15921713 }
1714+
1715+ /**
1716+ * <p>Provide access to the {@code /sys/wrapping/rewrap} endpoint. This endpoint rewraps a
1717+ * response-wrapped token. The new token will use the same creation TTL as the original token
1718+ * and contain the same response. The old token will be invalidated. This can be used for
1719+ * long-term storage of a secret in a response-wrapped token when rotation is a
1720+ * requirement.</p>
1721+ *
1722+ * <blockquote>
1723+ * <pre>{@code
1724+ * final String authToken = "...";
1725+ * final String wrappingToken = "...";
1726+ * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
1727+ * final Vault vault = new Vault(config);
1728+ *
1729+ * final WrapResponse wrapResponse = vault.auth().wrap(
1730+ * // Data to wrap
1731+ * new JsonObject()
1732+ * .add("foo", "bar")
1733+ * .add("zoo", "zar"),
1734+ *
1735+ * // TTL of the response-wrapping token
1736+ * 60
1737+ * );
1738+ * ...
1739+ * final WrapResponse wrapResponse2 = vault.auth().rewrap(wrapResponse.getToken());
1740+ *
1741+ * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse2.getToken());
1742+ * final JsonObject unwrappedData = response.getData(); // original data
1743+ * }</pre>
1744+ * </blockquote>
1745+ *
1746+ * @param wrappedToken Wrapped token ID to re-wrap.
1747+ * @return The response information returned from Vault
1748+ * @throws VaultException If any error occurs, or unexpected response received from Vault
1749+ * @see #wrap(JsonObject, int)
1750+ */
1751+ public WrapResponse rewrap (final String wrappedToken ) throws VaultException {
1752+ Objects .requireNonNull (wrappedToken );
1753+
1754+ return retry (attempt -> {
1755+ // Parse parameters to JSON
1756+ final String requestJson = Json .object ().add ("token" , wrappedToken ).toString ();
1757+ final String url = config .getAddress () + "/v1/sys/wrapping/rewrap" ;
1758+
1759+ // HTTP request to Vault
1760+ final RestResponse restResponse = new Rest ()
1761+ .url (url )
1762+ // .header("X-Vault-Token", wrappedToken)
1763+ .header ("X-Vault-Token" , config .getToken ())
1764+ .header ("X-Vault-Namespace" , this .nameSpace )
1765+ .header ("X-Vault-Request" , "true" )
1766+ .body (requestJson .getBytes (StandardCharsets .UTF_8 ))
1767+ .connectTimeoutSeconds (config .getOpenTimeout ())
1768+ .readTimeoutSeconds (config .getReadTimeout ())
1769+ .sslVerification (config .getSslConfig ().isVerify ())
1770+ .sslContext (config .getSslConfig ().getSslContext ())
1771+ .post ();
1772+
1773+ // Validate restResponse
1774+ if (restResponse .getStatus () != 200 ) {
1775+ throw new VaultException (
1776+ "Vault responded with HTTP status code: " + restResponse .getStatus ()
1777+ + "\n Response body: " + new String (restResponse .getBody (),
1778+ StandardCharsets .UTF_8 ),
1779+ restResponse .getStatus ());
1780+ }
1781+
1782+ final String mimeType =
1783+ restResponse .getMimeType () == null ? "null" : restResponse .getMimeType ();
1784+ if (!mimeType .equals ("application/json" )) {
1785+ throw new VaultException ("Vault responded with MIME type: " + mimeType ,
1786+ restResponse .getStatus ());
1787+ }
1788+
1789+ return new WrapResponse (restResponse , attempt );
1790+ });
1791+ }
15931792}
0 commit comments