Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for privateKeyFormat in pki #248

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/bettercloud/vault/Vault.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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).
*
Expand Down
143 changes: 143 additions & 0 deletions src/main/java/com/bettercloud/vault/api/database/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,74 @@ public DatabaseResponse createOrUpdateRole(final String roleName, final Database
}
}

/**
* <p>Operation to create or update an static role using the Database Secret engine.
* Relies on an authentication token being present in the <code>VaultConfig</code> instance.</p>
*
* <p>This version of the method accepts a <code>DatabaseStaticRoleOptions</code> parameter, containing optional settings
* for the role creation operation. Example usage:</p>
*
* <blockquote>
* <pre>{@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());
* }</pre>
* </blockquote>
*
* @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);
}
}
}
}

/**
* <p>Operation to retrieve an role using the Database backend. Relies on an authentication token being present in
* the <code>VaultConfig</code> instance.</p>
Expand Down Expand Up @@ -370,6 +438,68 @@ public DatabaseResponse creds(final String roleName) throws VaultException {
}
}

/**
* <p>Operation to generate a new set of credentials using the Database backend.
*
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will be thrown if
* the role does not exist, or if any other problem occurs. Credential information will be populated in the
* <code>credential</code> field of the <code>DatabaseResponse</code> return value. Example usage:</p>
*
* <blockquote>
* <pre>{@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();
* }</pre>
* </blockquote>
*
* @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();

Expand All @@ -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(List<String> data) {
String result = "";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.bettercloud.vault.api.database;

import java.util.ArrayList;
import java.util.List;

public class DatabaseStaticRoleOptions {
private String name;
private String dbName;
private String username;
private String rotationPeriod;
private List<String> rotationStatements = new ArrayList<>();

public String getName() {
return name;
}

public String getDbName() {
return dbName;
}

public String getUsername() {
return username;
}

public String getRotationPeriod() {
return rotationPeriod;
}

public List<String> getRotationStatements() {
return rotationStatements;
}

/**
* @param name {@code String} – Specifies the name of the role to create. This is specified as part of the URL.
* @return This object, with name populated, ready for other builder methods or immediate use.
*/
public DatabaseStaticRoleOptions name(final String name) {
this.name = name;
return this;
}

/**
* @param dbName {@code String} - The name of the database connection to use for this role.
* @return This object, with dbName populated, ready for other builder methods or immediate use.
*/
public DatabaseStaticRoleOptions dbName(final String dbName) {
this.dbName = dbName;
return this;
}

/**
* @param username {@code String} - The database usernameto use for this role.
* @return This object, with dbName populated, ready for other builder methods or immediate use.
*/
public DatabaseStaticRoleOptions username(final String username) {
this.username = username;
return this;
}

/**
* @param rotationPeriod (string/int: 0) - Specifies the amount of time Vault should wait before rotating the password. The minimum is 5 seconds.
* @return This object, with defaultTtl populated, ready for other builder methods or immediate use.
*/
public DatabaseStaticRoleOptions rotationPeriod(final String rotationPeriod) {
this.rotationPeriod = rotationPeriod;
return this;
}

/**
* @param rotationStatements {@code List<String>} – Specifies the database statements to be executed to rotate the password for the configured database user. Not every plugin type will support this functionality. See the plugin's API page for more information on support and formatting for this parameter.
* @return This object, with creationStatements populated, ready for other builder methods or immediate use.
*/
public DatabaseStaticRoleOptions rotationStatements(final List<String> rotationStatements) {
this.rotationStatements = rotationStatements;
return this;
}

}
47 changes: 46 additions & 1 deletion src/main/java/com/bettercloud/vault/api/pki/Pki.java
Original file line number Diff line number Diff line change
Expand Up @@ -407,15 +407,57 @@ public PkiResponse issue(
* @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,
final List<String> altNames,
final List<String> ipSans,
final String ttl,
final CredentialFormat format,
final String csr
) throws VaultException {
return issue(roleName, commonName, altNames, ipSans, ttl, format, null, csr);
}


/**
* <p>Operation 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.</p>
*
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will be thrown if
* the role does not exist, or if any other problem occurs. Credential information will be populated in the
* <code>credential</code> field of the <code>PkiResponse</code> return value. Example usage:</p>
*
* <blockquote>
* <pre>{@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();
* }</pre>
* </blockquote>
*
* @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,
final List<String> altNames,
final List<String> ipSans,
final String ttl,
final CredentialFormat format,
final PrivateKeyFormat privateKeyFormat,
final String csr
) throws VaultException {
int retryCount = 0;
Expand Down Expand Up @@ -451,6 +493,9 @@ public PkiResponse issue(
if (format != null) {
jsonObject.add("format", format.toString());
}
if (privateKeyFormat != null) {
jsonObject.add("private_key_format", privateKeyFormat.toString());
}
if (csr != null) {
jsonObject.add("csr", csr);
}
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/bettercloud/vault/api/pki/PrivateKeyFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bettercloud.vault.api.pki;

import java.util.List;

/**
* <p>Possible format options for private key issued by the PKI backend</p>
*
* <p>See: {@link Pki#issue(String, String, List, List, String, CredentialFormat, PrivateKeyFormat, String)}</p>
*/
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();
}
}
Loading