Skip to content

Commit 27d81a6

Browse files
authored
VJD-3 Fix parse Auth.unwrap response without auth data, add Auth.wrap method (#4)
1 parent 5a27c54 commit 27d81a6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3070
-120
lines changed

.travis.yml

-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,3 @@ script:
2525

2626
notifications:
2727
email: false
28-
29-
after-script:
30-
curl -s curl -s https://raw.githubusercontent.com/monperrus/gmtft/master/give-me-the-failing-tests.py | python

build.gradle

+13-21
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ task compileModuleInfoJava(type: JavaCompile) {
6767

6868
compileModuleInfoJava.dependsOn compileJava
6969
classes.dependsOn compileModuleInfoJava
70-
7170
// End of Java 9 compatibility config
7271

7372

@@ -137,15 +136,6 @@ if (!hasProperty('ossrhUsername')) {
137136
if (!hasProperty('ossrhPassword')) {
138137
ext.ossrhPassword = ''
139138
}
140-
if (!hasProperty('nexusUrl')) {
141-
ext.nexusUrl = ''
142-
}
143-
if (!hasProperty('nexusUsername')) {
144-
ext.nexusUsername = ''
145-
}
146-
if (!hasProperty('nexusPassword')) {
147-
ext.nexusPassword = ''
148-
}
149139

150140
artifacts {
151141
archives javadocJar, sourcesJar
@@ -161,34 +151,31 @@ uploadArchives {
161151
mavenDeployer {
162152
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
163153

164-
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
154+
repository(url: "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") {
165155
authentication(userName: ossrhUsername, password: ossrhPassword)
166156
}
167157

168-
// snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
169-
// authentication(userName: ossrhUsername, password: ossrhPassword)
170-
// }
171-
snapshotRepository(url: nexusUrl) {
172-
authentication(userName: nexusUsername, password: nexusPassword)
158+
snapshotRepository(url: "https://s01.oss.sonatype.org/content/repositories/snapshots/") {
159+
authentication(userName: ossrhUsername, password: ossrhPassword)
173160
}
174161

175162
pom.project {
176163
name 'vault-java-driver'
177164
packaging 'jar'
178165
// optionally artifactId can be defined here
179166
description 'Zero-dependency Java client for HashiCorp\'s Vault'
180-
url 'https://github.com/BetterCloud/vault-java-driver'
167+
url 'https://github.com/jopenlibs/vault-java-driver'
181168

182169
scm {
183-
connection 'https://github.com/BetterCloud/vault-java-driver.git'
184-
developerConnection 'https://github.com/BetterCloud/vault-java-driver.git'
185-
url 'https://github.com/BetterCloud/vault-java-driver'
170+
connection 'https://github.com/jopenlibs/vault-java-driver.git'
171+
developerConnection 'https://github.com/jopenlibs/vault-java-driver.git'
172+
url 'https://github.com/jopenlibs/vault-java-driver'
186173
}
187174

188175
licenses {
189176
license {
190177
name 'MIT'
191-
url 'https://github.com/BetterCloud/vault-java-driver/blob/master/README.md'
178+
url 'https://github.com/jopenlibs/vault-java-driver/blob/master/README.md'
192179
}
193180
}
194181

@@ -207,6 +194,11 @@ uploadArchives {
207194
id 'jarrodcodes'
208195
name 'Jarrod Young'
209196
197+
},
198+
developer {
199+
id 'tledkov'
200+
name 'Taras Ledkov'
201+
210202
}
211203
]}
212204
}

src/main/java/io/github/jopenlibs/vault/api/Auth.java

+167-30
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
import io.github.jopenlibs.vault.response.AuthResponse;
99
import io.github.jopenlibs.vault.response.LogicalResponse;
1010
import io.github.jopenlibs.vault.response.LookupResponse;
11+
import io.github.jopenlibs.vault.response.UnwrapResponse;
12+
import io.github.jopenlibs.vault.response.WrapResponse;
1113
import io.github.jopenlibs.vault.rest.Rest;
1214
import io.github.jopenlibs.vault.rest.RestResponse;
1315
import java.io.Serializable;
1416
import java.nio.charset.StandardCharsets;
1517
import java.util.List;
1618
import java.util.Map;
19+
import java.util.Objects;
1720
import 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+
+ "\nResponse 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
}

src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88

99
public class LogicalUtilities {
10-
10+
/**
11+
* Prevent creation an instance of a utility class.
12+
*/
13+
private LogicalUtilities() {
14+
// No-op.
15+
}
1116
/**
1217
* Convenience method to split a Vault path into its path segments.
1318
*

src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions o
124124
.post();
125125

126126
// Validate restResponse
127-
if (restResponse.getStatus() != 204) {
127+
// TODO: handle warnings
128+
if (restResponse.getStatus() != 204 && restResponse.getStatus() != 200) {
128129
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
129130
}
131+
130132
return new PkiResponse(restResponse, retryCount);
131133
} catch (Exception e) {
132134
// If there are retries to perform, then pause for the configured interval and then execute the loop again...

0 commit comments

Comments
 (0)