diff --git a/src/main/java/com/bettercloud/vault/api/pki/IssueOptions.java b/src/main/java/com/bettercloud/vault/api/pki/IssueOptions.java new file mode 100644 index 00000000..493ee39d --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/pki/IssueOptions.java @@ -0,0 +1,96 @@ +package com.bettercloud.vault.api.pki; + +import java.util.List; + +/** + * Value class for options to be passed to the PKI issue API. + */ +public class IssueOptions { + + private List altNames; + private List ipSans; + private List uriSans; + private List otherSans; + private String ttl; + private CredentialFormat credentialFormat; + private PrivateKeyFormat privateKeyFormat; + private Boolean excludeCnFromSans; + private String csr; + + public IssueOptions altNames(List altNames) { + this.altNames = altNames; + return this; + } + + public IssueOptions ipSans(List ipSans) { + this.ipSans = ipSans; + return this; + } + + public IssueOptions uriSans(List uriSans) { + this.uriSans = uriSans; + return this; + } + + public IssueOptions otherSans(List otherSans) { + this.otherSans = otherSans; + return this; + } + + public IssueOptions ttl(String ttl) { + this.ttl = ttl; + return this; + } + + public IssueOptions credentialFormat(CredentialFormat format) { + this.credentialFormat = format; + return this; + } + + public IssueOptions privateKeyFormat(PrivateKeyFormat privateKeyFormat) { + this.privateKeyFormat = privateKeyFormat; + return this; + } + + public IssueOptions excludeCnFromSans(Boolean excludeCnFromSans) { + this.excludeCnFromSans = excludeCnFromSans; + return this; + } + + public IssueOptions csr(String csr) { + this.csr = csr; + return this; + } + + public List getAltNames() { + return altNames; + } + + public List getIpSans() { return ipSans; } + + public List getUriSans() { + return uriSans; + } + + public List getOtherSans() { + return otherSans; + } + + public String getTtl() { + return ttl; + } + + public CredentialFormat getCredentialFormat() { return credentialFormat; } + + public PrivateKeyFormat getPrivateKeyFormat() { + return privateKeyFormat; + } + + public Boolean isExcludeCnFromSans() { + return excludeCnFromSans; + } + + public String getCsr() { + return csr; + } +} diff --git a/src/main/java/com/bettercloud/vault/api/pki/Pki.java b/src/main/java/com/bettercloud/vault/api/pki/Pki.java index 1074c2be..3587d1c7 100644 --- a/src/main/java/com/bettercloud/vault/api/pki/Pki.java +++ b/src/main/java/com/bettercloud/vault/api/pki/Pki.java @@ -496,6 +496,124 @@ public PkiResponse issue( } } + // TODO add method doc + public PkiResponse issue( + final String roleName, + final String commonName, + final IssueOptions issueOptions + ) throws VaultException { + int retryCount = 0; + while (true) { + // Construct a JSON body from inputs + final JsonObject jsonObject = Json.object(); + if (commonName != null) { + jsonObject.add("common_name", commonName); + } + List altNames = issueOptions.getAltNames(); + if (altNames != null && !altNames.isEmpty()) { + final StringBuilder altNamesCsv = new StringBuilder();//NOPMD + for (int index = 0; index < altNames.size(); index++) { + altNamesCsv.append(altNames.get(index)); + if (index + 1 < altNames.size()) { + altNamesCsv.append(','); + } + } + jsonObject.add("alt_names", altNamesCsv.toString()); + } + List ipSans = issueOptions.getIpSans(); + if (ipSans != null && !ipSans.isEmpty()) { + final StringBuilder ipSansCsv = new StringBuilder();//NOPMD + for (int index = 0; index < ipSans.size(); index++) { + ipSansCsv.append(ipSans.get(index)); + if (index + 1 < ipSans.size()) { + ipSansCsv.append(','); + } + } + jsonObject.add("ip_sans", ipSansCsv.toString()); + } + List uriSans = issueOptions.getUriSans(); + if (uriSans != null && !uriSans.isEmpty()) { + final StringBuilder uriSansCsv = new StringBuilder();//NOPMD + for (int index = 0; index < ipSans.size(); index++) { + uriSansCsv.append(uriSans.get(index)); + if (index + 1 < uriSans.size()) { + uriSansCsv.append(','); + } + } + jsonObject.add("uri_sans", uriSansCsv.toString()); + } + List otherSans = issueOptions.getOtherSans(); + if (otherSans != null && !otherSans.isEmpty()) { + final StringBuilder otherSansCsv = new StringBuilder();//NOPMD + for (int index = 0; index < otherSans.size(); index++) { + otherSansCsv.append(otherSans.get(index)); + if (index + 1 < otherSans.size()) { + otherSansCsv.append(','); + } + } + jsonObject.add("other_sans", otherSansCsv.toString()); + } + String ttl = issueOptions.getTtl(); + if (ttl != null) { + jsonObject.add("ttl", ttl); + } + CredentialFormat credentialFormat = issueOptions.getCredentialFormat(); + if (credentialFormat != null) { + jsonObject.add("format", credentialFormat.toString()); + } + PrivateKeyFormat privateKeyFormat = issueOptions.getPrivateKeyFormat(); + if (privateKeyFormat != null) { + jsonObject.add("private_key_format", privateKeyFormat.toString()); + } + Boolean excludeCnFromSans = issueOptions.isExcludeCnFromSans(); + if(excludeCnFromSans != null) { + jsonObject.add("exclude_cn_from_sans ", excludeCnFromSans); + } + String csr = issueOptions.getCsr(); + if (csr != null) { + jsonObject.add("csr", csr); + } + final String requestJson = jsonObject.toString(); + + // Make an HTTP request to Vault + try { + String endpoint = (csr == null || csr.isEmpty()) ? "%s/v1/%s/issue/%s" : "%s/v1/%s/sign/%s"; + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format(endpoint, config.getAddress(), this.mountPath, roleName)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate response + if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) { + String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)"; + throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus()); + } + return new PkiResponse(restResponse, retryCount); + } catch (Exception e) { + // If there are retries to perform, then pause for the configured interval and then execute the loop again... + if (retryCount < config.getMaxRetries()) { + retryCount++; + try { + final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); + Thread.sleep(retryIntervalMilliseconds); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } else if (e instanceof VaultException) { + // ... otherwise, give up. + throw (VaultException) e; + } else { + throw new VaultException(e); + } + } + } + } private String roleOptionsToJson(final RoleOptions options) { final JsonObject jsonObject = Json.object(); diff --git a/src/main/java/com/bettercloud/vault/api/pki/PrivateKeyFormat.java b/src/main/java/com/bettercloud/vault/api/pki/PrivateKeyFormat.java new file mode 100644 index 00000000..2b76ac2b --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/pki/PrivateKeyFormat.java @@ -0,0 +1,24 @@ +package com.bettercloud.vault.api.pki; + +/** + *

Possible format options for private keys issued by the PKI backend.

+ */ +public enum PrivateKeyFormat { + DER, + PEM, + PKCS8; + + public static PrivateKeyFormat fromString(final String text) { + if (text != null) { + for (final PrivateKeyFormat format : PrivateKeyFormat.values()) { + if (text.equalsIgnoreCase(format.toString())) { + return format; + } + } + } + return null; + } + + @Override + public String toString() { return super.toString().toLowerCase(); } +} diff --git a/src/test-integration/java/com/bettercloud/vault/api/AuthBackendTokenTests.java b/src/test-integration/java/com/bettercloud/vault/api/AuthBackendTokenTests.java index a5f9e337..2a3ab548 100644 --- a/src/test-integration/java/com/bettercloud/vault/api/AuthBackendTokenTests.java +++ b/src/test-integration/java/com/bettercloud/vault/api/AuthBackendTokenTests.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.List; import java.util.UUID; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/src/test/java/com/bettercloud/vault/vault/api/TransitApiTest.java b/src/test/java/com/bettercloud/vault/vault/api/TransitApiTest.java index 5f215dfb..05b9ff67 100644 --- a/src/test/java/com/bettercloud/vault/vault/api/TransitApiTest.java +++ b/src/test/java/com/bettercloud/vault/vault/api/TransitApiTest.java @@ -7,14 +7,12 @@ import com.bettercloud.vault.response.LogicalResponse; import com.bettercloud.vault.vault.VaultTestUtils; import com.bettercloud.vault.vault.mock.MockVault; +import java.util.Collections; +import java.util.Optional; import org.eclipse.jetty.server.Server; import org.junit.After; -import org.junit.Before; import org.junit.Test; -import java.util.Collections; -import java.util.Optional; - import static org.junit.Assert.assertEquals; public class TransitApiTest {