88import io .github .jopenlibs .vault .response .AuthResponse ;
99import io .github .jopenlibs .vault .response .LogicalResponse ;
1010import io .github .jopenlibs .vault .response .LookupResponse ;
11+ import io .github .jopenlibs .vault .response .UnwrapResponse ;
12+ import io .github .jopenlibs .vault .response .WrapResponse ;
1113import io .github .jopenlibs .vault .rest .Rest ;
1214import io .github .jopenlibs .vault .rest .RestResponse ;
1315import java .io .Serializable ;
1416import java .nio .charset .StandardCharsets ;
1517import java .util .List ;
1618import java .util .Map ;
19+ import java .util .Objects ;
1720import java .util .UUID ;
1821
1922
@@ -308,36 +311,39 @@ public AuthResponse createToken(final TokenRequest tokenRequest, final String to
308311 // Parse parameters to JSON
309312 final JsonObject jsonObject = Json .object ();
310313
311- if (tokenRequest .id != null ) jsonObject .add ("id" , tokenRequest .id .toString ());
312- if (tokenRequest .polices != null && !tokenRequest .polices .isEmpty ()) {
313- jsonObject .add ("policies" , Json .array (tokenRequest .polices .toArray (new String [tokenRequest .polices .size ()])));//NOPMD
314+ if (tokenRequest .getId () != null ) jsonObject .add ("id" , tokenRequest .getId ().toString ());
315+ if (tokenRequest .getPolices () != null && !tokenRequest .getPolices ().isEmpty ()) {
316+ jsonObject .add (
317+ "policies" ,
318+ Json .array (tokenRequest .getPolices ().toArray (new String [0 ]))
319+ ); //NOPMD
314320 }
315- if (tokenRequest .meta != null && !tokenRequest .meta .isEmpty ()) {
321+ if (tokenRequest .getMeta () != null && !tokenRequest .getMeta () .isEmpty ()) {
316322 final JsonObject metaMap = Json .object ();
317- for (final Map .Entry <String , String > entry : tokenRequest .meta .entrySet ()) {
323+ for (final Map .Entry <String , String > entry : tokenRequest .getMeta () .entrySet ()) {
318324 metaMap .add (entry .getKey (), entry .getValue ());
319325 }
320326 jsonObject .add ("meta" , metaMap );
321327 }
322- if (tokenRequest .noParent != null ) jsonObject .add ("no_parent" , tokenRequest .noParent );
323- if (tokenRequest .noDefaultPolicy != null )
324- jsonObject .add ("no_default_policy" , tokenRequest .noDefaultPolicy );
325- if (tokenRequest .ttl != null ) jsonObject .add ("ttl" , tokenRequest .ttl );
326- if (tokenRequest .displayName != null ) jsonObject .add ("display_name" , tokenRequest .displayName );
327- if (tokenRequest .numUses != null ) jsonObject .add ("num_uses" , tokenRequest .numUses );
328- if (tokenRequest .renewable != null ) jsonObject .add ("renewable" , tokenRequest .renewable );
329- if (tokenRequest .type != null ) jsonObject .add ("type" , tokenRequest .type );
330- if (tokenRequest .explicitMaxTtl != null ) jsonObject .add ("explicit_max_ttl" , tokenRequest .explicitMaxTtl );
331- if (tokenRequest .period != null ) jsonObject .add ("period" , tokenRequest .period );
332- if (tokenRequest .entityAlias != null ) jsonObject .add ("entity_alias" , tokenRequest .entityAlias );
328+ if (tokenRequest .getNoParent () != null ) jsonObject .add ("no_parent" , tokenRequest .getNoParent () );
329+ if (tokenRequest .getNoDefaultPolicy () != null )
330+ jsonObject .add ("no_default_policy" , tokenRequest .getNoDefaultPolicy () );
331+ if (tokenRequest .getTtl () != null ) jsonObject .add ("ttl" , tokenRequest .getTtl () );
332+ if (tokenRequest .getDisplayName () != null ) jsonObject .add ("display_name" , tokenRequest .getDisplayName () );
333+ if (tokenRequest .getNumUses () != null ) jsonObject .add ("num_uses" , tokenRequest .getNumUses () );
334+ if (tokenRequest .getRenewable () != null ) jsonObject .add ("renewable" , tokenRequest .getRenewable () );
335+ if (tokenRequest .getType () != null ) jsonObject .add ("type" , tokenRequest .getType () );
336+ if (tokenRequest .getExplicitMaxTtl () != null ) jsonObject .add ("explicit_max_ttl" , tokenRequest .getExplicitMaxTtl () );
337+ if (tokenRequest .getPeriod () != null ) jsonObject .add ("period" , tokenRequest .getPeriod () );
338+ if (tokenRequest .getEntityAlias () != null ) jsonObject .add ("entity_alias" , tokenRequest .getEntityAlias () );
333339 final String requestJson = jsonObject .toString ();
334340
335341 final StringBuilder urlBuilder = new StringBuilder (config .getAddress ())//NOPMD
336342 .append ("/v1/auth/" )
337343 .append (mount )
338344 .append ("/create" );
339- if (tokenRequest .role != null ) {
340- urlBuilder .append ("/" ).append (tokenRequest .role );
345+ if (tokenRequest .getRole () != null ) {
346+ urlBuilder .append ("/" ).append (tokenRequest .getRole () );
341347 }
342348 final String url = urlBuilder .toString ();
343349
@@ -1502,37 +1508,58 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
15021508 * @throws VaultException If any error occurs, or unexpected response received from Vault
15031509 * @see #unwrap(String)
15041510 */
1505- public AuthResponse unwrap () throws VaultException {
1511+ public UnwrapResponse unwrap () throws VaultException {
15061512 return unwrap (null );
15071513 }
15081514
15091515 /**
1510- * <p>Returns the original response inside the given wrapped auth token. This method is useful if you need to unwrap
1511- * a token, while being already authenticated. Do NOT authenticate in vault with your wrapping token, since it will
1512- * both fail authentication and invalidate the wrapping token at the same time. See {@link #unwrap()} if you need to
1513- * do that without being authenticated.</p>
1516+ * <p>Provide access to the {@code /sys/wrapping/unwrap} endpoint.</p>
1517+ *
1518+ * <p>Returns the original response inside the given wrapping token. Unlike simply reading
1519+ * {@code cubbyhole/response} (which is deprecated), this endpoint provides additional
1520+ * validation checks on the token, returns the original value on the wire rather than
1521+ * a JSON string representation of it, and ensures that the response is properly audit-logged.</p>
1522+ *
1523+ * <p> This endpoint can be used by using a wrapping token as the client token in the API call,
1524+ * in which case the token parameter is not required; or, a different token with permissions
1525+ * to access this endpoint can make the call and pass in the wrapping token in
1526+ * the token parameter. Do not use the wrapping token in both locations;
1527+ * this will cause the wrapping token to be revoked but the value to be unable to be looked up,
1528+ * as it will basically be a double-use of the token!</p>
15141529 *
15151530 * <p>In the example below, {@code authToken} is NOT your wrapped token, and should have unwrapping permissions.
1516- * The unwrapped token in {@code unwrappedToken }. Example usage:</p>
1531+ * The unwrapped data in {@link UnwrapResponse#getData() }. Example usage:</p>
15171532 *
15181533 * <blockquote>
15191534 * <pre>{@code
15201535 * final String authToken = "...";
15211536 * final String wrappingToken = "...";
15221537 * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
15231538 * final Vault vault = new Vault(config);
1524- * final AuthResponse response = vault.auth().unwrap(wrappingToken);
1525- * final String unwrappedToken = response.getAuthClientToken();
1539+ *
1540+ * final WrapResponse wrapResponse = vault.auth().wrap(
1541+ * // Data to wrap
1542+ * new JsonObject()
1543+ * .add("foo", "bar")
1544+ * .add("zoo", "zar"),
1545+ *
1546+ * // TTL of the response-wrapping token
1547+ * 60
1548+ * );
1549+ *
1550+ * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken());
1551+ * final JsonObject unwrappedData = response.getData(); // original data
15261552 * }</pre>
15271553 * </blockquote>
15281554 *
1529- * @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your {@link VaultConfig#token },
1530- * if token is {@code null}, this method will unwrap the auth token in {@link VaultConfig#token }
1555+ * @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your {@link VaultConfig#getToken() },
1556+ * if token is {@code null}, this method will unwrap the auth token in {@link VaultConfig#getToken() }
15311557 * @return The response information returned from Vault
15321558 * @throws VaultException If any error occurs, or unexpected response received from Vault
1559+ * @see #wrap(JsonObject, int)
15331560 * @see #unwrap()
15341561 */
1535- public AuthResponse unwrap (final String wrappedToken ) throws VaultException {
1562+ public UnwrapResponse unwrap (final String wrappedToken ) throws VaultException {
15361563 int retryCount = 0 ;
15371564 while (true ) {
15381565 try {
@@ -1567,7 +1594,7 @@ public AuthResponse unwrap(final String wrappedToken) throws VaultException {
15671594 if (!mimeType .equals ("application/json" )) {
15681595 throw new VaultException ("Vault responded with MIME type: " + mimeType , restResponse .getStatus ());
15691596 }
1570- return new AuthResponse (restResponse , retryCount );
1597+ return new UnwrapResponse (restResponse , retryCount );
15711598 } catch (final Exception e ) {
15721599 // If there are retries to perform, then pause for the configured interval and then execute the
15731600 // loop again...
@@ -1589,4 +1616,114 @@ public AuthResponse unwrap(final String wrappedToken) throws VaultException {
15891616 }
15901617 }
15911618
1619+ /**
1620+ * <p>Provide access to the {@code /sys/wrapping/wrap} endpoint.</p>
1621+ *
1622+ * <p>This provides a powerful mechanism for information sharing in many environments.
1623+ * In the types of scenarios, often the best practical option is to provide cover
1624+ * for the secret information, be able to detect malfeasance (interception, tampering),
1625+ * and limit lifetime of the secret's exposure.
1626+ * Response wrapping performs all three of these duties:</p>
1627+ *
1628+ * <ul>
1629+ * <li>It provides cover by ensuring that the value being transmitted across the wire is
1630+ * not the actual secret but a reference to such a secret, namely the response-wrapping token.
1631+ * Information stored in logs or captured along the way do not directly see the sensitive information.
1632+ * </li>
1633+ * <li>It provides malfeasance detection by ensuring that only a single party can ever
1634+ * unwrap the token and see what's inside. A client receiving a token that cannot be unwrapped
1635+ * can trigger an immediate security incident. In addition, a client can inspect
1636+ * a given token before unwrapping to ensure that its origin is from the expected
1637+ * location in Vault.
1638+ * </li>
1639+ * <li>It limits the lifetime of secret exposure because the response-wrapping token has
1640+ * a lifetime that is separate from the wrapped secret (and often can be much shorter),
1641+ * so if a client fails to come up and unwrap the token, the token can expire very quickly.
1642+ * </li>
1643+ * </ul>
1644+ *
1645+ * <blockquote>
1646+ * <pre>{@code
1647+ * final String authToken = "...";
1648+ * final String wrappingToken = "...";
1649+ * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
1650+ * final Vault vault = new Vault(config);
1651+ *
1652+ * final WrapResponse wrapResponse = vault.auth().wrap(
1653+ * // Data to wrap
1654+ * new JsonObject()
1655+ * .add("foo", "bar")
1656+ * .add("zoo", "zar"),
1657+ *
1658+ * // TTL of the response-wrapping token
1659+ * 60
1660+ * );
1661+ *
1662+ * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken());
1663+ * final JsonObject unwrappedData = response.getData(); // original data
1664+ * }</pre>
1665+ * </blockquote>
1666+ *
1667+ * @param jsonObject User data to wrap.
1668+ * @param ttlInSec Wrap TTL in seconds
1669+ * @return The response information returned from Vault
1670+ * @throws VaultException If any error occurs, or unexpected response received from Vault
1671+ * @see #unwrap(String)
1672+ */
1673+ public WrapResponse wrap (final JsonObject jsonObject , int ttlInSec ) throws VaultException {
1674+ Objects .requireNonNull (jsonObject );
1675+
1676+ int retryCount = 0 ;
1677+ while (true ) {
1678+ try {
1679+ // Parse parameters to JSON
1680+ final String requestJson = jsonObject .toString ();
1681+ final String url = config .getAddress () + "/v1/sys/wrapping/wrap" ;
1682+
1683+ // HTTP request to Vault
1684+ final RestResponse restResponse = new Rest ()
1685+ .url (url )
1686+ .header ("X-Vault-Token" , config .getToken ())
1687+ .header ("X-Vault-Wrap-TTL" , Integer .toString (ttlInSec ))
1688+ .header ("X-Vault-Namespace" , this .nameSpace )
1689+ .body (requestJson .getBytes (StandardCharsets .UTF_8 ))
1690+ .connectTimeoutSeconds (config .getOpenTimeout ())
1691+ .readTimeoutSeconds (config .getReadTimeout ())
1692+ .sslVerification (config .getSslConfig ().isVerify ())
1693+ .sslContext (config .getSslConfig ().getSslContext ())
1694+ .post ();
1695+
1696+ // Validate restResponse
1697+ if (restResponse .getStatus () != 200 ) {
1698+ throw new VaultException ("Vault responded with HTTP status code: " + restResponse .getStatus ()
1699+ + "\n Response body: " + new String (restResponse .getBody (), StandardCharsets .UTF_8 ),
1700+ restResponse .getStatus ());
1701+ }
1702+
1703+ final String mimeType = restResponse .getMimeType () == null ? "null" : restResponse .getMimeType ();
1704+ if (!mimeType .equals ("application/json" )) {
1705+ throw new VaultException ("Vault responded with MIME type: " + mimeType , restResponse .getStatus ());
1706+ }
1707+
1708+ return new WrapResponse (restResponse , retryCount );
1709+ } catch (final Exception e ) {
1710+ // If there are retries to perform, then pause for the configured interval and then execute the
1711+ // loop again...
1712+ if (retryCount < config .getMaxRetries ()) {
1713+ retryCount ++;
1714+ try {
1715+ final int retryIntervalMilliseconds = config .getRetryIntervalMilliseconds ();
1716+ Thread .sleep (retryIntervalMilliseconds );
1717+ } catch (InterruptedException e1 ) {
1718+ e1 .printStackTrace ();
1719+ }
1720+ } else if (e instanceof VaultException ) {
1721+ // ... otherwise, give up.
1722+ throw (VaultException ) e ;
1723+ } else {
1724+ throw new VaultException (e );
1725+ }
1726+ }
1727+ }
1728+ }
15921729}
0 commit comments