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

Implementation for Vault's Database Secret Engine #175

Merged
merged 1 commit into from
Aug 16, 2019
Merged
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
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 @@ -5,6 +5,7 @@
import com.bettercloud.vault.api.Leases;
import com.bettercloud.vault.api.Logical;
import com.bettercloud.vault.api.Seal;
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.json.Json;
Expand Down Expand Up @@ -192,6 +193,10 @@ public Pki pki(final String mountPath) {
return new Pki(vaultConfig, mountPath);
}

public Database database() { return new Database(vaultConfig); }

public Database database(final String mountPath) { return new Database(vaultConfig, mountPath); }

/**
* Returns the implementing class for Vault's lease operations (e.g. revoke, revoke-prefix).
*
Expand Down
414 changes: 414 additions & 0 deletions src/main/java/com/bettercloud/vault/api/database/Database.java

Large diffs are not rendered by default.

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

public class DatabaseCredential {

private String username;
private String password;

/**
* @return Returns the Username associated with the retrieved Credential
*/
public String getUsername() {
return username;
}

/**
* @return Returns the Password associated with the retrieved Credential
*/
public String getPassword() {
return password;
}

public DatabaseCredential username(String username) {
this.username = username;
return this;
}

public DatabaseCredential password(String password) {
this.password = password;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.bettercloud.vault.api.database;

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

public class DatabaseRoleOptions {
private String name;
private String dbName;
private String defaultTtl = "0";
private String maxTtl = "0";
private List<String> creationStatements = new ArrayList<>();
private List<String> revocationStatements = new ArrayList<>();
private List<String> rollbackStatements = new ArrayList<>();
private List<String> renewStatements = new ArrayList<>();

public String getName() {
return name;
}

public String getDbName() {
return dbName;
}

public String getDefaultTtl() {
return defaultTtl;
}

public String getMaxTtl() {
return maxTtl;
}

public List<String> getCreationStatements() {
return creationStatements;
}

public List<String> getRenewStatements() {
return renewStatements;
}

public List<String> getRevocationStatements() {
return revocationStatements;
}

public List<String> getRollbackStatements() {
return rollbackStatements;
}

/**
* @param name (string: <required>) – 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 DatabaseRoleOptions name(final String name) {
this.name = name;
return this;
}

/**
* @param dbName (string: <required>) - 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 DatabaseRoleOptions dbName(final String dbName) {
this.dbName = dbName;
return this;
}

/**
* @param defaultTtl (string/int: 0) - Specifies the TTL for the leases associated with this role. Accepts time suffixed strings ("1h") or an integer number of seconds. Defaults to system/engine default TTL time.
* @return This object, with defaultTtl populated, ready for other builder methods or immediate use.
*/
public DatabaseRoleOptions defaultTtl(final String defaultTtl) {
this.defaultTtl = defaultTtl;
return this;
}

/**
* @param maxTtl (string/int: 0) - Specifies the maximum TTL for the leases associated with this role. Accepts time suffixed strings ("1h") or an integer number of seconds. Defaults to system/mount default TTL time; this value is allowed to be less than the mount max TTL (or, if not set, the system max TTL), but it is not allowed to be longer. See also The TTL General Case.
* @return This object, with maxTtl populated, ready for other builder methods or immediate use.
*/
public DatabaseRoleOptions maxTtl(final String maxTtl) {
this.maxTtl = maxTtl;
return this;
}

/**
* @param creationStatements (list: <required>) – Specifies the database statements executed to create and configure a user. 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 DatabaseRoleOptions creationStatements(final List<String> creationStatements) {
this.creationStatements = creationStatements;
return this;
}

/**
* @param revocationStatements (list: []) – Specifies the database statements to be executed to revoke a user. See the plugin's API page for more information on support and formatting for this parameter.
* @return This object, with revocationStatements populated, ready for other builder methods or immediate use.
*/
public DatabaseRoleOptions revocationStatements(final List<String> revocationStatements) {
this.revocationStatements = revocationStatements;
return this;
}

/**
* @param rollbackStatements (list: []) – Specifies the database statements to be executed rollback a create operation in the event of an error. 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 rollbackStatements populated, ready for other builder methods or immediate use.
*/
public DatabaseRoleOptions rollbackStatements(final List<String> rollbackStatements) {
this.rollbackStatements = rollbackStatements;
return this;
}

/**
* @param renewStatements (list: []) – Specifies the database statements to be executed to renew a 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 renewStatements populated, ready for other builder methods or immediate use.
*/
public DatabaseRoleOptions renewStatements(final List<String> renewStatements) {
this.renewStatements = renewStatements;
return this;
}
}
97 changes: 97 additions & 0 deletions src/main/java/com/bettercloud/vault/response/DatabaseResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.bettercloud.vault.response;

import com.bettercloud.vault.api.Logical;
import com.bettercloud.vault.api.database.DatabaseCredential;
import com.bettercloud.vault.api.database.DatabaseRoleOptions;
import com.bettercloud.vault.json.JsonArray;
import com.bettercloud.vault.json.JsonObject;
import com.bettercloud.vault.rest.RestResponse;

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

public class DatabaseResponse extends LogicalResponse {

private DatabaseCredential credential;

private DatabaseRoleOptions roleOptions;

public DatabaseCredential getCredential() {
return credential;
}

public DatabaseRoleOptions getRoleOptions() {
return roleOptions;
}

/**
* @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 DatabaseResponse(RestResponse restResponse, int retries) {
super(restResponse, retries, Logical.logicalOperations.authentication);
credential = buildCredentialFromData(this.getData());
roleOptions = buildRoleOptionsFromData(this.getData(), this.getDataObject());
}

private DatabaseRoleOptions buildRoleOptionsFromData(final Map<String, String> data, final JsonObject jsonObject) {
if (data == null || data.size() == 0) {
return null;
}

final List<String> creationStatements = extractFromJsonArray(safeGetJsonArray(jsonObject, "creation_statements"));
final List<String> renewStatements = extractFromJsonArray(safeGetJsonArray(jsonObject, "renew_statements"));
final List<String> revocationStatements = extractFromJsonArray(safeGetJsonArray(jsonObject, "revocation_statements"));
final List<String> rollbackStatements = extractFromJsonArray(safeGetJsonArray(jsonObject, "rollback_statements"));

final String dbName = data.get("db_name");
final String defaultTtl = data.get("default_ttl");
final String maxTtl = data.get("default_ttl");

if (dbName == null && defaultTtl == null && maxTtl == null) {
return null;
}

return new DatabaseRoleOptions()
.creationStatements(creationStatements)
.renewStatements(renewStatements)
.revocationStatements(revocationStatements)
.rollbackStatements(rollbackStatements)
.dbName(dbName)
.defaultTtl(defaultTtl)
.maxTtl(maxTtl);
}

private DatabaseCredential buildCredentialFromData(final Map<String, String> data) {
if (data == null) {
return null;
}
final String username = data.get("username");
final String password = data.get("password");

if (username == null && password == null) {
return null;
}

return new DatabaseCredential()
.username(username)
.password(password);
}

private JsonArray safeGetJsonArray(JsonObject source, String key) {
if (source == null || source.get(key) == null || source.get(key) == null || !source.get(key).isArray()) {
return new JsonArray();
}

return source.get(key).asArray();
}

private List<String> extractFromJsonArray(JsonArray array) {
List<String> result = new ArrayList<>();

array.forEach(entry -> result.add(entry.asString()));

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.bettercloud.vault.api;

import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.api.database.DatabaseRoleOptions;
import com.bettercloud.vault.response.DatabaseResponse;
import com.bettercloud.vault.util.DbContainer;
import com.bettercloud.vault.util.VaultContainer;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;

public class AuthBackendDatabaseTests {
@ClassRule
public static final VaultContainer container = new VaultContainer();

@ClassRule
public static final DbContainer dbContainer = new DbContainer();


@BeforeClass
public static void setupClass() throws IOException, InterruptedException {
container.initAndUnsealVault();
container.setupBackendDatabase(dbContainer.getDbContainerIp());
}

@Test
public void testRoleCreation() throws VaultException {
final Vault vault = container.getRootVault();

List<String> creationStatements = new ArrayList<>();
creationStatements.add("CREATE USER \"{{name}}\" WITH PASSWORD '{{password}}'; GRANT ALL PRIVILEGES ON DATABASE \"postgres\" to \"{{name}}\";");

DatabaseRoleOptions roleToCreate = new DatabaseRoleOptions().dbName("postgres").creationStatements(creationStatements);

DatabaseResponse response = vault.database().createOrUpdateRole("test-role", roleToCreate);
assertEquals(204, response.getRestResponse().getStatus());

DatabaseResponse role = vault.database().getRole("test-role");
assertEquals(200, role.getRestResponse().getStatus());

assertTrue(compareRoleOptions(role.getRoleOptions(), roleToCreate));
}

@Test
public void testDeleteRole() throws VaultException {
final Vault vault = container.getRootVault();

List<String> creationStatements = new ArrayList<>();
creationStatements.add("CREATE USER \"{{name}}\" WITH PASSWORD '{{password}}'; GRANT ALL PRIVILEGES ON DATABASE \"postgres\" to \"{{name}}\";");

DatabaseRoleOptions roleToCreate = new DatabaseRoleOptions().dbName("postgres").creationStatements(creationStatements);

DatabaseResponse response = vault.database().createOrUpdateRole("delete-role", roleToCreate);
assertEquals(204, response.getRestResponse().getStatus());

DatabaseResponse deletedRole = vault.database().deleteRole("delete-role");
assertEquals(204, deletedRole.getRestResponse().getStatus());

try {
DatabaseResponse role = vault.database().getRole("delete-role");
} catch (VaultException e) {
assertEquals("This should have failed", 404, e.getHttpStatusCode());
}
}

@Test
public void testRoleNotFound() throws VaultException {
final Vault vault = container.getRootVault();

try {
DatabaseResponse role = vault.database().getRole("i-do-not-exist");
} catch (VaultException e) {
assertEquals("This should have failed", 404, e.getHttpStatusCode());
}
}

@Test
public void testGetCredentials() throws VaultException {
final Vault vault = container.getRootVault();

List<String> creationStatements = new ArrayList<>();
creationStatements.add("CREATE USER \"{{name}}\" WITH PASSWORD '{{password}}'; GRANT ALL PRIVILEGES ON DATABASE \"postgres\" to \"{{name}}\";");

DatabaseResponse response = vault.database().createOrUpdateRole("new-role", new DatabaseRoleOptions().dbName("postgres").creationStatements(creationStatements));
assertEquals(204, response.getRestResponse().getStatus());

DatabaseResponse credsResponse = vault.database().creds("new-role");
assertEquals(200, credsResponse.getRestResponse().getStatus());

assertTrue(credsResponse.getCredential().getUsername().contains("new-role"));
}

private boolean compareRoleOptions(DatabaseRoleOptions expected, DatabaseRoleOptions actual) {
return expected.getCreationStatements().size() == actual.getCreationStatements().size() &&
expected.getRenewStatements().size() == actual.getRenewStatements().size() &&
expected.getRevocationStatements().size() == actual.getRevocationStatements().size() &&
expected.getRollbackStatements().size() == actual.getRollbackStatements().size();
}
}
Loading