diff --git a/build.gradle b/build.gradle index 2dc5c64f..f78d638a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,12 +5,12 @@ apply plugin: 'checkstyle' group 'com.bettercloud' archivesBaseName = 'vault-java-driver' -version '5.1.0' +version '5.3.0' ext.isReleaseVersion = !version.endsWith('SNAPSHOT') // This project is actually limited to Java 8 compatibility. See below. -sourceCompatibility = 9 -targetCompatibility = 9 +sourceCompatibility = 11 +targetCompatibility = 11 repositories { mavenCentral() diff --git a/src/main/java/com/bettercloud/vault/Vault.java b/src/main/java/com/bettercloud/vault/Vault.java index 479bb09a..93023c9c 100644 --- a/src/main/java/com/bettercloud/vault/Vault.java +++ b/src/main/java/com/bettercloud/vault/Vault.java @@ -8,6 +8,7 @@ import com.bettercloud.vault.api.database.Database; import com.bettercloud.vault.api.mounts.Mounts; import com.bettercloud.vault.api.pki.Pki; +import com.bettercloud.vault.api.transit.Transit; import com.bettercloud.vault.json.Json; import com.bettercloud.vault.json.JsonObject; import com.bettercloud.vault.json.JsonValue; @@ -198,6 +199,10 @@ public Pki pki(final String mountPath) { public Database database(final String mountPath) { return new Database(vaultConfig, mountPath); } + + public Transit transit() { return new Transit(vaultConfig); } + public Transit transit(final String mountPath) { return new Transit(vaultConfig, mountPath); } + /** * Returns the implementing class for Vault's lease operations (e.g. revoke, revoke-prefix). * diff --git a/src/main/java/com/bettercloud/vault/api/database/Database.java b/src/main/java/com/bettercloud/vault/api/database/Database.java index cf842ca2..e22e89d6 100644 --- a/src/main/java/com/bettercloud/vault/api/database/Database.java +++ b/src/main/java/com/bettercloud/vault/api/database/Database.java @@ -118,6 +118,74 @@ public DatabaseResponse createOrUpdateRole(final String roleName, final Database } } + /** + *
Operation to create or update an static role using the Database Secret engine.
+ * Relies on an authentication token being present in the VaultConfig instance.
This version of the method accepts a DatabaseStaticRoleOptions parameter, containing optional settings
+ * for the role creation operation. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final DatabaseStaticRoleOptions options = new DatabaseStaticRoleOptions()
+ * .dbName("test")
+ * .rotationPeriod("9h");
+ * final DatabaseResponse response = vault.database().createOrUpdateStaticRole("testRole", options);
+ *
+ * assertEquals(204, response.getRestResponse().getStatus());
+ * }
+ *
+ *
+ * @param roleName A name for the role to be created or updated
+ * @param options Optional settings for the role to be created or updated (e.g. db_name, ttl, etc)
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public DatabaseResponse createOrUpdateStaticRole(final String roleName, final DatabaseStaticRoleOptions options) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ try {
+ final String requestJson = staticRoleOptionsToJson(options);
+
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/static-roles/%s", 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 restResponse
+ if (restResponse.getStatus() != 204) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new DatabaseResponse(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);
+ }
+ }
+ }
+ }
+
/**
* Operation to retrieve an role using the Database backend. Relies on an authentication token being present in
* the VaultConfig instance.
Operation to generate a new set of credentials using the Database backend. + * + *
A successful operation will return a 204 HTTP status. A VaultException will be thrown if
+ * the role does not exist, or if any other problem occurs. Credential information will be populated in the
+ * credential field of the DatabaseResponse return value. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final DatabaseResponse response = vault.database().creds("testRole");
+ * assertEquals(204, response.getRestResponse().getStatus();
+ * }
+ *
+ *
+ * @param roleName The role for which to retrieve credentials
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public DatabaseResponse staticCreds(final String roleName) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ try {
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/static-creds/%s", config.getAddress(), this.mountPath, roleName))
+ .header("X-Vault-Token", config.getToken())
+ .header("X-Vault-Namespace", this.nameSpace)
+ .connectTimeoutSeconds(config.getOpenTimeout())
+ .readTimeoutSeconds(config.getReadTimeout())
+ .sslVerification(config.getSslConfig().isVerify())
+ .sslContext(config.getSslConfig().getSslContext())
+ .get();
+
+ // 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 DatabaseResponse(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 DatabaseRoleOptions options) {
final JsonObject jsonObject = Json.object();
@@ -386,6 +516,19 @@ private String roleOptionsToJson(final DatabaseRoleOptions options) {
return jsonObject.toString();
}
+ private String staticRoleOptionsToJson(final DatabaseStaticRoleOptions options) {
+ final JsonObject jsonObject = Json.object();
+
+ if (options != null) {
+ addJsonFieldIfNotNull(jsonObject, "db_name", options.getDbName());
+ addJsonFieldIfNotNull(jsonObject, "username", options.getUsername());
+ addJsonFieldIfNotNull(jsonObject, "rotation_period", options.getRotationPeriod());
+ addJsonFieldIfNotNull(jsonObject, "rotation_statements", joinList(options.getRotationStatements()));
+ }
+
+ return jsonObject.toString();
+ }
+
private String joinList(ListOperation to generate a new set of credentials or sign the embedded CSR, in the PKI backend. If CSR is passed the + * sign function of the vault will be called if not, issue will be used. + * The issuing CA certificate is returned as well, so that only the root CA need be in a + * client's trust store.
+ * + *A successful operation will return a 204 HTTP status. A VaultException will be thrown if
+ * the role does not exist, or if any other problem occurs. Credential information will be populated in the
+ * credential field of the PkiResponse return value. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final PkiResponse response = vault.pki().deleteRole("testRole");
+ * assertEquals(204, response.getRestResponse().getStatus();
+ * }
+ *
+ *
+ * @param roleName The role on which the credentials will be based.
+ * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, it will be issued.
+ * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. These can be host names or email addresses; they will be parsed into their respective fields. If any requested names do not match role policy, the entire request will be denied.
+ * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. Only valid if the role allows IP SANs (which is the default).
+ * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl value. If not provided, the role's ttl value will be used. Note that the role values default to system values if not explicitly set.
+ * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will contain the private key, certificate, and issuing CA, concatenated.
+ * @param privateKeyFormat (optional) Format for the returned private key. Generally the default will be controlled by the "format" parameter as either base64-encoded DER or PEM-encoded DER. However, this can be set to "pkcs8" to have the returned private key contain base64-encoded pkcs8 or PEM-encode pkcs8 instead. Defaults to "der".
+ * @param csr (optional) PEM Encoded CSR
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
public PkiResponse issue(
final String roleName,
final String commonName,
@@ -416,6 +457,7 @@ public PkiResponse issue(
final ListPossible format options for private key issued by the PKI backend
+ * + *See: {@link Pki#issue(String, String, List, List, String, CredentialFormat, PrivateKeyFormat, String)}
+ */ +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/main/java/com/bettercloud/vault/api/transit/CryptData.java b/src/main/java/com/bettercloud/vault/api/transit/CryptData.java new file mode 100644 index 00000000..fe1c83c0 --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/transit/CryptData.java @@ -0,0 +1,37 @@ +package com.bettercloud.vault.api.transit; + +public class CryptData { + + private String ciphertext; + + private String plaintext; + + private Integer keyVersion; + + public String getCiphertext() { + return ciphertext; + } + + public String getPlaintext() { + return plaintext; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public CryptData ciphertext(String ciphertext){ + this.ciphertext = ciphertext; + return this; + } + + public CryptData plaintext(String plaintext){ + this.plaintext = plaintext; + return this; + } + + public CryptData keyVersion(Integer keyVersion){ + this.keyVersion = keyVersion; + return this; + } +} diff --git a/src/main/java/com/bettercloud/vault/api/transit/DataKeyOptions.java b/src/main/java/com/bettercloud/vault/api/transit/DataKeyOptions.java new file mode 100644 index 00000000..9cf5acdd --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/transit/DataKeyOptions.java @@ -0,0 +1,41 @@ +package com.bettercloud.vault.api.transit; + +public class DataKeyOptions { + + private String context; + + + private String nonce; + + private Integer bits; + + + + + public String getContext() { + return context; + } + + public String getNonce() { + return nonce; + } + + public Integer getBits() { + return bits; + } + + public DataKeyOptions context(String context) { + this.context = context; + return this; + } + + public DataKeyOptions nonce(String nonce) { + this.nonce = nonce; + return this; + } + + public DataKeyOptions bits(int bits) { + this.bits = bits; + return this; + } +} diff --git a/src/main/java/com/bettercloud/vault/api/transit/DecryptOptions.java b/src/main/java/com/bettercloud/vault/api/transit/DecryptOptions.java new file mode 100644 index 00000000..3e176730 --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/transit/DecryptOptions.java @@ -0,0 +1,39 @@ +package com.bettercloud.vault.api.transit; + +public class DecryptOptions { + + private String ciphertext; + + private String context; + + private String nonce; + + + public String getCiphertext() { + return ciphertext; + } + + public String getContext() { + return context; + } + + + public String getNonce() { + return nonce; + } + + public DecryptOptions ciphertext(String ciphertext) { + this.ciphertext = ciphertext; + return this; + } + + public DecryptOptions context(String context) { + this.context = context; + return this; + } + + public DecryptOptions nonce(String nonce) { + this.nonce = nonce; + return this; + } +} diff --git a/src/main/java/com/bettercloud/vault/api/transit/EncryptOptions.java b/src/main/java/com/bettercloud/vault/api/transit/EncryptOptions.java new file mode 100644 index 00000000..deacd83d --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/transit/EncryptOptions.java @@ -0,0 +1,49 @@ +package com.bettercloud.vault.api.transit; + +public class EncryptOptions { + + private byte[] plaintext; + + private String context; + + private Integer keyVersion; + + private String nonce; + + + public byte[] getPlaintext() { + return plaintext; + } + + public String getContext() { + return context; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public String getNonce() { + return nonce; + } + + public EncryptOptions plaintext(byte[] plaintext) { + this.plaintext = plaintext; + return this; + } + + public EncryptOptions context(String context) { + this.context = context; + return this; + } + + public EncryptOptions keyVersion(int keyVersion) { + this.keyVersion = keyVersion; + return this; + } + + public EncryptOptions nonce(String nonce) { + this.nonce = nonce; + return this; + } +} diff --git a/src/main/java/com/bettercloud/vault/api/transit/KeyOptions.java b/src/main/java/com/bettercloud/vault/api/transit/KeyOptions.java new file mode 100644 index 00000000..8526dd32 --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/transit/KeyOptions.java @@ -0,0 +1,136 @@ +package com.bettercloud.vault.api.transit; + +public class KeyOptions { + + private Boolean convergentEncryption; + + private Boolean derived; + + private Boolean exportable; + + private Boolean allowPlaintextBackup; + + private String type; + + private Integer autoRotatePeriod; + + //read only + private Boolean deletionAllowed; + + private String name; + + private Integer minDecryptionVersion; + + private Integer minEncryptionVersion; + + private Boolean supportsEncryption; + + private Boolean supportsDecryption; + + private Boolean supportsDerivation; + + private Boolean supportsSigning; + + public KeyOptions() { + } + + // from response + public KeyOptions(Boolean deletionAllowed, String name, Integer minDecryptionVersion, + Integer minEncryptionVersion, Boolean supportsEncryption, Boolean supportsDecryption, + Boolean supportsDerivation, Boolean supportsSigning) { + this.deletionAllowed = deletionAllowed; + this.name = name; + this.minDecryptionVersion = minDecryptionVersion; + this.minEncryptionVersion = minEncryptionVersion; + this.supportsEncryption = supportsEncryption; + this.supportsDecryption = supportsDecryption; + this.supportsDerivation = supportsDerivation; + this.supportsSigning = supportsSigning; + } + + public Boolean getConvergentEncryption() { + return convergentEncryption; + } + + public Boolean getDerived() { + return derived; + } + + public Boolean getExportable() { + return exportable; + } + + public Boolean getAllowPlaintextBackup() { + return allowPlaintextBackup; + } + + public String getType() { + return type; + } + + public Integer getAutoRotatePeriod() { + return autoRotatePeriod; + } + + public Boolean getDeletionAllowed() { + return deletionAllowed; + } + + public String getName() { + return name; + } + + public Integer getMinDecryptionVersion() { + return minDecryptionVersion; + } + + public Integer getMinEncryptionVersion() { + return minEncryptionVersion; + } + + public Boolean getSupportsEncryption() { + return supportsEncryption; + } + + public Boolean getSupportsDecryption() { + return supportsDecryption; + } + + public Boolean getSupportsDerivation() { + return supportsDerivation; + } + + public Boolean getSupportsSigning() { + return supportsSigning; + } + + public KeyOptions convergentEncryption(Boolean convergentEncryption) { + this.convergentEncryption = convergentEncryption; + return this; + } + + public KeyOptions derived(Boolean derived) { + this.derived = derived; + return this; + } + + public KeyOptions exportable(Boolean exportable) { + this.exportable = exportable; + return this; + } + + public KeyOptions allowPlaintextBackup(Boolean allowPlaintextBackup) { + this.allowPlaintextBackup = allowPlaintextBackup; + return this; + } + + public KeyOptions type(String type) { + this.type = type; + return this; + } + + public KeyOptions autoRotatePeriod(Integer autoRotatePeriod) { + this.autoRotatePeriod = autoRotatePeriod; + return this; + } +} diff --git a/src/main/java/com/bettercloud/vault/api/transit/Transit.java b/src/main/java/com/bettercloud/vault/api/transit/Transit.java new file mode 100644 index 00000000..89d65c2c --- /dev/null +++ b/src/main/java/com/bettercloud/vault/api/transit/Transit.java @@ -0,0 +1,528 @@ +package com.bettercloud.vault.api.transit; + +import com.bettercloud.vault.VaultConfig; +import com.bettercloud.vault.VaultException; +import com.bettercloud.vault.api.database.DatabaseStaticRoleOptions; +import com.bettercloud.vault.json.Json; +import com.bettercloud.vault.json.JsonObject; +import com.bettercloud.vault.response.TransitResponse; +import com.bettercloud.vault.rest.Rest; +import com.bettercloud.vault.rest.RestResponse; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; + +/** + *The implementing class for operations on Vault's database backend.
+ * + *This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault
+ * in a DSL-style builder pattern. See the Javadoc comments of each public method for usage examples.
/v1/transit).
+ *
+ * @param config A container for the configuration settings needed to initialize a Vault driver instance
+ */
+ public Transit(final VaultConfig config) {
+ this(config, "transit");
+ }
+
+ /**
+ * Constructor for use when the Transit backend is mounted on some non-default custom path (e.g. /v1/tr123).
+ *
+ * @param config A container for the configuration settings needed to initialize a Vault driver instance
+ * @param mountPath The path on which your Vault Transit backend is mounted, without the /v1/ prefix (e.g. "root-ca")
+ */
+ public Transit(final VaultConfig config, final String mountPath) {
+ this.config = config;
+ this.mountPath = mountPath;
+ if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
+ this.nameSpace = this.config.getNameSpace();
+ }
+ }
+ public TransitResponse createKey(String keyName) throws VaultException {
+ return createKey(keyName, null);
+ }
+ /**
+ * Operation to create an key using the Transit backend. Relies on an authentication token being present in
+ * the VaultConfig instance.
This version of the method accepts a KeyOptions parameter, containing optional settings
+ * for the key creation operation. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final KeyOptions options = new KeyOptions()
+ * .type("aes128-gcm96")
+ * .exportable(true);
+ * final TransitResponse response = vault.transit().createKey("testKey", options);
+ *
+ * assertEquals(204, response.getRestResponse().getStatus());
+ * }
+ *
+ *
+ * @param keyName A name for the key to be created
+ * @param options Optional settings for the key to be created
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public TransitResponse createKey(String keyName, KeyOptions options) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ try {
+ final String requestJson = keyOptionsToJson(options);
+
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/keys/%s", config.getAddress(), this.mountPath, keyName))
+ .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 restResponse
+ if (restResponse.getStatus() != 204) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new TransitResponse(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);
+ }
+ }
+ }
+ }
+
+ /**
+ * Operation to retrieve an key using the Transit backend. Relies on an authentication token being present in
+ * the VaultConfig instance.
The key information will be populated in the keyOptions field of the TransitResponse
+ * return value. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ * final TransitResponse response = vault.pki().getRole("testRole");
+ *
+ * final KeyOptions details = response.getKeyOptions();
+ * }
+ *
+ *
+ * @param keyName The name of the key to retrieve
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public TransitResponse getKey(String keyName) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ // Make an HTTP request to Vault
+ try {
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/keys/%s", config.getAddress(), this.mountPath, keyName))
+ .header("X-Vault-Token", config.getToken())
+ .header("X-Vault-Namespace", this.nameSpace)
+ .connectTimeoutSeconds(config.getOpenTimeout())
+ .readTimeoutSeconds(config.getReadTimeout())
+ .sslVerification(config.getSslConfig().isVerify())
+ .sslContext(config.getSslConfig().getSslContext())
+ .get();
+
+ // Validate response
+ if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new TransitResponse(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);
+ }
+ }
+ }
+ }
+
+ /**
+ * Operation to delete an key using the Transit backend. Relies on an authentication token being present in
+ * the VaultConfig instance.
A successful operation will return a 204 HTTP status. A VaultException will be thrown if
+ * the role does not exist, or if any other problem occurs. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final TransitResponse response = vault.transit().deleteKey("testKey");
+ * assertEquals(204, response.getRestResponse().getStatus();
+ * }
+ *
+ *
+ * @param keyName The name of the key to delete
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public TransitResponse deleteKey(String keyName) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ // Make an HTTP request to Vault
+ try {
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/keys/%s", config.getAddress(), this.mountPath, keyName))
+ .header("X-Vault-Token", config.getToken())
+ .header("X-Vault-Namespace", this.nameSpace)
+ .connectTimeoutSeconds(config.getOpenTimeout())
+ .readTimeoutSeconds(config.getReadTimeout())
+ .sslVerification(config.getSslConfig().isVerify())
+ .sslContext(config.getSslConfig().getSslContext())
+ .delete();
+
+ // Validate response
+ if (restResponse.getStatus() != 204) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new TransitResponse(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);
+ }
+ }
+ }
+ }
+
+ /**
+ * Operation to encrypt data using the Transit Secret engine.
+ * Relies on an authentication token being present in the VaultConfig instance.
This version of the method accepts a EncryptOptions parameter, containing optional settings
+ * for the encrypt data operation. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final TransitEncryptOptions options = new EncryptOptions()
+ * .plaintext("test"getBytes());
+ * final TransitResponse response = vault.transit().encryptData("encryptKey1", options);
+ *
+ * assertEquals(204, response.getRestResponse().getStatus());
+ * }
+ *
+ *
+ * @param keyName A name for the encrypt key to be used
+ * @param options Data and params to encrypt data
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public TransitResponse encryptData(final String keyName, final EncryptOptions options) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ // Make an HTTP request to Vault
+ try {
+ final String requestJson = encryptOptionsToJson(options);
+
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/encrypt/%s", config.getAddress(), this.mountPath, keyName))
+ .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) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new TransitResponse(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);
+ }
+ }
+ }
+ }
+
+ /**
+ * Operation to decrypt data using the Transit Secret engine.
+ * Relies on an authentication token being present in the VaultConfig instance.
This version of the method accepts a DecryptOptions parameter, containing optional settings
+ * for the encrypt data operation. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final TransitEncryptOptions options = new DecryptOptions()
+ * .ciphertext("test"getBytes());
+ * final TransitResponse response = vault.transit().decryptData("encryptKey1", options);
+ *
+ * assertEquals(204, response.getRestResponse().getStatus());
+ * }
+ *
+ *
+ * @param keyName A name for the encrypt key to be used
+ * @param options Data and params to encrypt data
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public TransitResponse decryptData(final String keyName, final DecryptOptions options) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ // Make an HTTP request to Vault
+ try {
+ final String requestJson = decryptOptionsToJson(options);
+
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/decrypt/%s", config.getAddress(), this.mountPath, keyName))
+ .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) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new TransitResponse(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);
+ }
+ }
+ }
+ }
+
+ public TransitResponse dataKey(final String type, final String keyName) throws VaultException {
+ return dataKey(type, keyName, null);
+ }
+
+ /**
+ * Operation to encrypt data using the Transit Secret engine.
+ * Relies on an authentication token being present in the VaultConfig instance.
This version of the method accepts a EncryptOptions parameter, containing optional settings
+ * for the encrypt data operation. Example usage:
+ * {@code
+ * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+ * final Vault vault = new Vault(config);
+ *
+ * final TransitEncryptOptions options = new EncryptOptions()
+ * .plaintext("test"getBytes());
+ * final TransitResponse response = vault.transit().encryptData("encryptKey1", options);
+ *
+ * assertEquals(204, response.getRestResponse().getStatus());
+ * }
+ *
+ *
+ * @param type A type for response (plaintext, wrapped)
+ * @param keyName A name for the encrypt key to be used
+ * @param options Data and params to encrypt data
+ * @return A container for the information returned by Vault
+ * @throws VaultException If any error occurs or unexpected response is received from Vault
+ */
+ public TransitResponse dataKey(final String type, final String keyName, final DataKeyOptions options) throws VaultException {
+ int retryCount = 0;
+ while (true) {
+ // Make an HTTP request to Vault
+ try {
+ final String requestJson = dataKeyOptionsToJson(options);
+
+ final RestResponse restResponse = new Rest()//NOPMD
+ .url(String.format("%s/v1/%s/datakey/%s/%s", config.getAddress(), this.mountPath, type, keyName))
+ .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) {
+ throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
+ }
+ return new TransitResponse(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 keyOptionsToJson(KeyOptions options){
+ final JsonObject jsonObject = Json.object();
+
+ if (options != null) {
+ addJsonFieldIfNotNull(jsonObject, "convergent_encryption", options.getConvergentEncryption());
+ addJsonFieldIfNotNull(jsonObject, "derived", options.getDerived());
+ addJsonFieldIfNotNull(jsonObject, "exportable", options.getExportable());
+ addJsonFieldIfNotNull(jsonObject, "allow_plaintext_backup", options.getAllowPlaintextBackup());
+ addJsonFieldIfNotNull(jsonObject, "type", options.getType());
+ addJsonFieldIfNotNull(jsonObject, "auto_rotate_period", options.getAutoRotatePeriod());
+ }
+ return jsonObject.toString();
+ }
+
+ private String encryptOptionsToJson(final EncryptOptions options) {
+ final JsonObject jsonObject = Json.object();
+
+ if (options != null) {
+ addJsonFieldIfNotNull(jsonObject, "plaintext", Base64.getEncoder().encodeToString(options.getPlaintext()));
+ addJsonFieldIfNotNull(jsonObject, "context", options.getContext());
+ addJsonFieldIfNotNull(jsonObject, "key_version", options.getKeyVersion());
+ addJsonFieldIfNotNull(jsonObject, "nonce", options.getNonce());
+ }
+
+ return jsonObject.toString();
+ }
+
+ private String decryptOptionsToJson(final DecryptOptions options) {
+ final JsonObject jsonObject = Json.object();
+
+ if (options != null) {
+ addJsonFieldIfNotNull(jsonObject, "ciphertext", options.getCiphertext());
+ addJsonFieldIfNotNull(jsonObject, "context", options.getContext());
+ addJsonFieldIfNotNull(jsonObject, "nonce", options.getNonce());
+ }
+
+ return jsonObject.toString();
+ }
+
+ private String dataKeyOptionsToJson(final DataKeyOptions options) {
+ final JsonObject jsonObject = Json.object();
+
+ if (options != null) {
+ addJsonFieldIfNotNull(jsonObject, "context", options.getContext());
+ addJsonFieldIfNotNull(jsonObject, "nonce", options.getNonce());
+ addJsonFieldIfNotNull(jsonObject, "bits", options.getBits());
+ }
+
+ return jsonObject.toString();
+ }
+
+ private JsonObject addJsonFieldIfNotNull(final JsonObject jsonObject, final String name, final Object value) {
+ if (value == null) {
+ return jsonObject;
+ }
+ if (value instanceof String) {
+ jsonObject.add(name, (String) value);
+ } else if (value instanceof Boolean) {
+ jsonObject.add(name, (Boolean) value);
+ } else if (value instanceof Long) {
+ jsonObject.add(name, (Long) value);
+ } else if (value instanceof Integer) {
+ jsonObject.add(name, (Integer) value);
+ } else if (value instanceof byte[]){
+ jsonObject.add(name, new String((byte[]) value));
+ }
+
+ return jsonObject;
+ }
+
+
+
+}
diff --git a/src/main/java/com/bettercloud/vault/response/TransitResponse.java b/src/main/java/com/bettercloud/vault/response/TransitResponse.java
new file mode 100644
index 00000000..ec8857da
--- /dev/null
+++ b/src/main/java/com/bettercloud/vault/response/TransitResponse.java
@@ -0,0 +1,138 @@
+package com.bettercloud.vault.response;
+
+import com.bettercloud.vault.api.Logical.logicalOperations;
+import com.bettercloud.vault.api.transit.CryptData;
+import com.bettercloud.vault.api.transit.KeyOptions;
+import com.bettercloud.vault.rest.RestResponse;
+import java.util.Map;
+
+public class TransitResponse extends LogicalResponse {
+
+ private KeyOptions keyOptions;
+
+ private CryptData cryptData;
+
+
+ /**
+ * @param restResponse The raw HTTP response from Vault.
+ * @param retries The number of retry attempts that occurred during the API call (can be zero).
+ */
+ public TransitResponse(RestResponse restResponse, int retries) {
+ this(restResponse, retries, logicalOperations.authentication);
+ }
+
+ /**
+ * @param restResponse The raw HTTP response from Vault.
+ * @param retries The number of retry attempts that occurred during the API call (can be zero).
+ * @param operation The operation requested.
+ */
+ public TransitResponse(RestResponse restResponse, int retries, logicalOperations operation) {
+ super(restResponse, retries, operation);
+ keyOptions = buildKeyOptionsFromData(this.getData());
+ cryptData = buildEncryptDataFromData(this.getData());
+ }
+
+ public KeyOptions getKeyOptions() {
+ return keyOptions;
+ }
+
+ public CryptData getCryptData() {
+ return cryptData;
+ }
+
+ /**
+ * Generates a KeyOptions object from the response data returned by Transit
+ * backend REST calls, for those calls which do return role data
+ * (e.g. getKey(String keyName)).
If the response data does not contain key information, then this method will
+ * return null.
"data" object from a Vault JSON response, converted into Java key-value pairs.
+ * @return A container for role options
+ */
+ private KeyOptions buildKeyOptionsFromData(final MapGenerates a EncryptData object from the response data returned by Transit
+ * backend REST calls, for those calls which do return role data
+ * (e.g. encryptData(String keyName, EncryptOptions options)).
"data" object from a Vault JSON response, converted into Java key-value pairs.
+ * @return A container for role options
+ */
+ private CryptData buildEncryptDataFromData(final MapUsed to determine whether a String value contains a "true" or "false" value. The problem
+ * with Boolean.parseBoolean() is that it swallows null values and returns them
+ * as false rather than null.
null
+ * @return A true or false value if the input can be parsed as such, or else null.
+ */
+ private Boolean parseBoolean(final String input) {
+ if (input == null) {
+ return null;
+ } else {
+ return Boolean.parseBoolean(input);
+ }
+ }
+
+ private Integer parseInt(final String input) {
+ if (input == null) {
+ return null;
+ } else {
+ return Integer.parseInt(input);
+ }
+ }
+
+}
diff --git a/src/test-integration/java/com/bettercloud/vault/api/AuthBackendDatabaseTests.java b/src/test-integration/java/com/bettercloud/vault/api/AuthBackendDatabaseTests.java
index d9d41c60..6ef457a1 100644
--- a/src/test-integration/java/com/bettercloud/vault/api/AuthBackendDatabaseTests.java
+++ b/src/test-integration/java/com/bettercloud/vault/api/AuthBackendDatabaseTests.java
@@ -3,6 +3,7 @@
import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.api.database.DatabaseRoleOptions;
+import com.bettercloud.vault.api.database.DatabaseStaticRoleOptions;
import com.bettercloud.vault.response.DatabaseResponse;
import com.bettercloud.vault.util.DbContainer;
import com.bettercloud.vault.util.VaultContainer;
@@ -97,6 +98,22 @@ public void testGetCredentials() throws VaultException {
assertTrue(credsResponse.getCredential().getUsername().contains("new-role"));
}
+ @Test
+ public void testStaticCredentials() throws VaultException {
+ final Vault vault = container.getRootVault();
+
+ List/v1/transit/* REST endpoints.
+ */
+public class AuthBackendTransitTests {
+
+ @ClassRule
+ public static final VaultContainer container = new VaultContainer();
+
+ @BeforeClass
+ public static void setupClass() throws IOException, InterruptedException {
+ container.initAndUnsealVault();
+ container.setupBackendTransit();
+ }
+
+ // @Before
+ public void setup() throws VaultException {
+ final Vault vault = container.getRootVault();
+
+ final TransitResponse defaultResponse = vault.transit().deleteKey("testKey");
+ final RestResponse defaultRestResponse = defaultResponse.getRestResponse();
+ assertEquals(204, defaultRestResponse.getStatus());
+
+ final TransitResponse customResponse = vault.transit("other-transit").deleteKey("testKey");
+ final RestResponse customRestResponse = customResponse.getRestResponse();
+ assertEquals(204, customRestResponse.getStatus());
+ }
+
+ @Test
+ public void testCreateKey_Defaults() throws VaultException {
+ final Vault vault = container.getRootVault();
+
+ vault.transit().createKey("testKey");
+ final TransitResponse response = vault.transit().getKey("testKey");
+ assertTrue(compareKeyOptions(new KeyOptions(), response.getKeyOptions()));
+ }
+
+ @Test
+ public void testCreateRole_WithOptions() throws VaultException {
+ final Vault vault = container.getRootVault();
+
+ final KeyOptions options = new KeyOptions().type("rsa-4096");
+ vault.transit().createKey("testKey", options);
+ final TransitResponse response = vault.transit().getKey("testKey");
+ assertTrue(compareKeyOptions(options, response.getKeyOptions()));
+ }
+
+ @Test
+ public void encryptDecryptTest() throws VaultException {
+ final Vault vault = container.getRootVault();
+
+ testCreateKey_Defaults();
+
+ EncryptOptions options = new EncryptOptions().plaintext("123456789".getBytes());
+ final TransitResponse encryptResponse = vault.transit().encryptData("testKey", options);
+ DecryptOptions decryptOptions = new DecryptOptions().ciphertext(encryptResponse.getCryptData().getCiphertext());
+ final TransitResponse decryptResponse = vault.transit().decryptData("testKey", decryptOptions);
+ assertTrue(Arrays.equals(options.getPlaintext(),
+ Base64.getDecoder().decode(decryptResponse.getCryptData().getPlaintext())));
+ }
+
+ @Test
+ public void dataKeyTest() throws VaultException {
+ final Vault vault = container.getRootVault();
+
+ testCreateKey_Defaults();
+
+ final TransitResponse encryptResponse = vault.transit().dataKey("plaintext", "testKey");
+ DecryptOptions decryptOptions = new DecryptOptions().ciphertext(encryptResponse.getCryptData().getCiphertext());
+ final TransitResponse decryptResponse = vault.transit().decryptData("testKey", decryptOptions);
+ assertTrue(Arrays.equals(Base64.getDecoder().decode(encryptResponse.getCryptData().getPlaintext()),
+ Base64.getDecoder().decode(decryptResponse.getCryptData().getPlaintext())));
+ }
+
+// @Test
+// public void testDeleteKey() throws VaultException {
+// final Vault vault = container.getRootVault();
+//
+// testCreateKey_Defaults();
+// final TransitResponse deleteResponse = vault.transit().deleteKey("testKey");
+// assertEquals(204, deleteResponse.getRestResponse().getStatus());
+// final TransitResponse getResponse = vault.transit().getKey("testKey");
+// assertEquals(404, getResponse.getRestResponse().getStatus());
+// }
+
+ // @Test
+// public void testIssueCredential() throws VaultException, InterruptedException {
+// final Vault vault = container.getRootVault();
+//
+// // Create a role
+// final PkiResponse createRoleResponse = vault.pki().createOrUpdateRole("testRole",
+// new RoleOptions()
+// .allowedDomains(new ArrayList