Skip to content

Commit c74d3b1

Browse files
Merge pull request #101 from mulesoft-labs/master
support for revoke method, and addition of a useCsrSans property in PKI role object
2 parents c8b1a00 + f76e51f commit c74d3b1

5 files changed

Lines changed: 148 additions & 12 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final S
706706
* Most likely just aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8= (base64-encoding of https://sts.amazonaws.com/) as most requests will
707707
* probably use POST with an empty URI.
708708
* @param iamRequestBody Base64-encoded body of the signed request. Most likely QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ== which is
709-
* the base64 encoding of Action=GetCallerIdentity&Version=2011-06-15.
709+
* the base64 encoding of Action=GetCallerIdentity&Version=2011-06-15.
710710
* @param iamRequestHeaders
711711
* @return The auth token, with additional response metadata
712712
* @throws VaultException If any error occurs, or unexpected response received from Vault

src/main/java/com/bettercloud/vault/api/pki/Pki.java

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import com.bettercloud.vault.rest.Rest;
99
import com.bettercloud.vault.rest.RestResponse;
1010

11+
import java.util.Arrays;
1112
import java.util.List;
12-
import java.util.Optional;
13+
import java.util.stream.Collectors;
14+
1315

1416
/**
1517
* <p>The implementing class for operations on Vault's PKI backend.</p>
@@ -195,6 +197,73 @@ public PkiResponse getRole(final String roleName) throws VaultException {
195197
}
196198
}
197199

200+
/**
201+
* <p>Operation to revike a certificate in the vault using the PKI backend.
202+
* Relies on an authentication token being present in
203+
* the <code>VaultConfig</code> instance.</p>
204+
*
205+
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will be thrown if
206+
* the role does not exist, or if any other problem occurs. Example usage:</p>
207+
*
208+
* <blockquote>
209+
* <pre>{@code
210+
* final VaultConfig config = new VaultConfig.address(...).token(...).build();
211+
* final Vault vault = new Vault(config);
212+
*
213+
* final PkiResponse response = vault.pki().revoke("serialnumber");
214+
* assertEquals(204, response.getRestResponse().getStatus();
215+
* }</pre>
216+
* </blockquote>
217+
*
218+
* @param serialNumber The name of the role to delete
219+
* @return A container for the information returned by Vault
220+
* @throws VaultException If any error occurs or unexpected response is received from Vault
221+
*/
222+
public PkiResponse revoke(final String serialNumber) throws VaultException {
223+
int retryCount = 0;
224+
while (true) {
225+
// Make an HTTP request to Vault
226+
JsonObject jsonObject = new JsonObject();
227+
if (serialNumber != null) {
228+
jsonObject.add("serial_number", serialNumber);
229+
}
230+
final String requestJson = jsonObject.toString();
231+
try {
232+
final RestResponse restResponse = new Rest()//NOPMD
233+
.url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath))
234+
.header("X-Vault-Token", config.getToken())
235+
.connectTimeoutSeconds(config.getOpenTimeout())
236+
.readTimeoutSeconds(config.getReadTimeout())
237+
.body(requestJson.getBytes("UTF-8"))
238+
.sslVerification(config.getSslConfig().isVerify())
239+
.sslContext(config.getSslConfig().getSslContext())
240+
.post();
241+
242+
// Validate response
243+
if (restResponse.getStatus() != 200) {
244+
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
245+
}
246+
return new PkiResponse(restResponse, retryCount);
247+
} catch (Exception e) {
248+
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
249+
if (retryCount < config.getMaxRetries()) {
250+
retryCount++;
251+
try {
252+
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
253+
Thread.sleep(retryIntervalMilliseconds);
254+
} catch (InterruptedException e1) {
255+
e1.printStackTrace();
256+
}
257+
} else if (e instanceof VaultException) {
258+
// ... otherwise, give up.
259+
throw (VaultException) e;
260+
} else {
261+
throw new VaultException(e);
262+
}
263+
}
264+
}
265+
}
266+
198267
/**
199268
* <p>Operation to delete an role using the PKI backend. Relies on an authentication token being present in
200269
* the <code>VaultConfig</code> instance.</p>
@@ -387,7 +456,8 @@ public PkiResponse issue(
387456

388457
// Validate response
389458
if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
390-
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
459+
String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)";
460+
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus());
391461
}
392462
return new PkiResponse(restResponse, retryCount);
393463
} catch (Exception e) {
@@ -418,15 +488,9 @@ private String roleOptionsToJson(final RoleOptions options) {
418488
addJsonFieldIfNotNull(jsonObject, "max_ttl", options.getMaxTtl());
419489
addJsonFieldIfNotNull(jsonObject, "allow_localhost", options.getAllowLocalhost());
420490
if (options.getAllowedDomains() != null && options.getAllowedDomains().size() > 0) {
421-
final StringBuilder allowedDomains = new StringBuilder();
422-
for (int index = 0; index < options.getAllowedDomains().size(); index++) {
423-
allowedDomains.append(options.getAllowedDomains().get(index));
424-
if (index + 1 < options.getAllowedDomains().size()) {
425-
allowedDomains.append(',');
426-
}
427-
}
428-
addJsonFieldIfNotNull(jsonObject, "allowed_domains", allowedDomains.toString());
491+
addJsonFieldIfNotNull(jsonObject, "allowed_domains", options.getAllowedDomains().stream().collect(Collectors.joining(",")));
429492
}
493+
addJsonFieldIfNotNull(jsonObject, "allow_spiffe_name", options.getAllowSpiffename());
430494
addJsonFieldIfNotNull(jsonObject, "allow_bare_domains", options.getAllowBareDomains());
431495
addJsonFieldIfNotNull(jsonObject, "allow_subdomains", options.getAllowSubdomains());
432496
addJsonFieldIfNotNull(jsonObject, "allow_any_name", options.getAllowAnyName());
@@ -439,6 +503,10 @@ private String roleOptionsToJson(final RoleOptions options) {
439503
addJsonFieldIfNotNull(jsonObject, "key_type", options.getKeyType());
440504
addJsonFieldIfNotNull(jsonObject, "key_bits", options.getKeyBits());
441505
addJsonFieldIfNotNull(jsonObject, "use_csr_common_name", options.getUseCsrCommonName());
506+
addJsonFieldIfNotNull(jsonObject, "use_csr_sans", options.getUseCsrSans());
507+
if (options.getKeyUsage() != null && options.getKeyUsage().size() > 0) {
508+
addJsonFieldIfNotNull(jsonObject, "key_usage", options.getKeyUsage().stream().collect(Collectors.joining(",")));
509+
}
442510
}
443511
return jsonObject.toString();
444512
}

src/main/java/com/bettercloud/vault/api/pki/RoleOptions.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ public class RoleOptions implements Serializable {
3535
private String keyType;
3636
private Long keyBits;
3737
private Boolean useCsrCommonName;
38+
private Boolean allowSpiffename;
39+
private Boolean useCsrSans;
40+
private List<String> keyUsage;
41+
3842

3943
/**
4044
* @param ttl (optional) The Time To Live value provided as a string duration with time suffix. Hour is the largest suffix. If not set, uses the system default value or the value of max_ttl, whichever is shorter.
@@ -98,7 +102,15 @@ public RoleOptions allowSubdomains(final Boolean allowSubdomains) {
98102
this.allowSubdomains = allowSubdomains;
99103
return this;
100104
}
101-
105+
/**
106+
* @param allowSpiffename (optional)
107+
*
108+
* @return This object, with AllowSpiffename populated, ready for other builder methods or immediate use.
109+
*/
110+
public RoleOptions allowSpiffeName(final Boolean allowSpiffename) {
111+
this.allowSpiffename = allowSpiffename ;
112+
return this;
113+
}
102114
/**
103115
* @param allowAnyName (optional) If set, clients can request any CN. Useful in some circumstances, but make sure you understand whether it is appropriate for your installation before enabling it. Defaults to false.
104116
*
@@ -198,6 +210,15 @@ public RoleOptions useCsrCommonName(final Boolean useCsrCommonName) {
198210
this.useCsrCommonName = useCsrCommonName;
199211
return this;
200212
}
213+
/**
214+
* @param useCsrSans (optional) If set, when used with the CSR signing endpoint, the common name in the CSR will be used instead of taken from the JSON data. This does not include any requested SANs in the CSR. Defaults to false.
215+
*
216+
* @return This object, with useCsrCommonName populated, ready for other builder methods or immediate use.
217+
*/
218+
public RoleOptions useCsrSans(final Boolean useCsrSans) {
219+
this.useCsrSans = useCsrSans;
220+
return this;
221+
}
201222

202223
public String getTtl() {
203224
return ttl;
@@ -268,5 +289,16 @@ public Long getKeyBits() {
268289
public Boolean getUseCsrCommonName() {
269290
return useCsrCommonName;
270291
}
292+
public Boolean getUseCsrSans() { return useCsrSans; }
293+
public Boolean getAllowSpiffename() { return allowSpiffename; }
294+
271295

296+
public RoleOptions keyUsage(List<String> keyUsage) {
297+
this.keyUsage = keyUsage;
298+
return this;
299+
}
300+
301+
public List<String> getKeyUsage() {
302+
return keyUsage;
303+
}
272304
}

src/test-integration/java/com/bettercloud/vault/api/AuthBackendPkiTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,26 @@ public void testIssueCredentialWithCsr() throws VaultException, InterruptedExcep
132132
assertNotNull(issueResponse.getCredential().getSerialNumber());
133133
assertNotNull(issueResponse.getCredential().getIssuingCa());
134134
}
135+
136+
@Test
137+
public void testRevocation() throws VaultException, InterruptedException, NoSuchAlgorithmException {
138+
final Vault vault = container.getRootVault();
139+
140+
// Create a role
141+
final PkiResponse createRoleResponse = vault.pki().createOrUpdateRole("testRole",
142+
new RoleOptions()
143+
.allowedDomains(new ArrayList<String>(){{ add("myvault.com"); }})
144+
.allowSubdomains(true)
145+
.maxTtl("9h")
146+
);
147+
assertEquals(204, createRoleResponse.getRestResponse().getStatus());
148+
Thread.sleep(3000);
149+
// Issue cert
150+
final PkiResponse issueResponse = vault.pki().issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM);
151+
assertNotNull(issueResponse.getCredential().getSerialNumber());
152+
vault.pki().revoke(issueResponse.getCredential().getSerialNumber());
153+
}
154+
135155
@Test
136156
public void testCustomMountPath() throws VaultException {
137157
final Vault vault = container.getRootVault();

src/test-integration/java/com/bettercloud/vault/util/VaultContainer.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,22 @@ public Vault getVault() throws VaultException {
226226
return getVault(config, MAX_RETRIES, RETRY_MILLIS);
227227
}
228228

229+
/**
230+
* Constructs a VaultConfig that can be used to congiure your own tests
231+
*
232+
* @return
233+
* @throws VaultException
234+
*/
235+
236+
public VaultConfig getVaultConfig() throws VaultException {
237+
return new VaultConfig()
238+
.address(getAddress())
239+
.openTimeout(5)
240+
.readTimeout(30)
241+
.sslConfig(new SslConfig().pemFile(new File(CERT_PEMFILE)).build())
242+
.build();
243+
}
244+
229245
/**
230246
* Constructs an instance of the Vault driver with sensible defaults, configured to use the supplied token
231247
* for authentication.

0 commit comments

Comments
 (0)