diff --git a/README.md b/README.md index a517c9ea..8e98111f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,20 @@ alike, without causing conflicts with any other dependency. NOTE: Although the binary artifact produced by the project is backwards-compatible with Java 8, you do need JDK 9 or higher to modify or build the source code of this library itself. +This Change +----------- +This change generalizes the vault Java driver to allow prefix paths to +contain multiple path elements. That is, instead of restricting v2 paths +to be **v1**/*something*/**data**/*anything*/*else* (e.g., for a read or write), +paths can be **v1**/*my*/*long*/*prefix*/*path*/**data**/*anything*/*else*. +The length of the prefix path in path elements, or the prefix path itself +(from which the length in path elements can be derived) is passed in the +VaultConfig build sequence. This allows Vault administrators greater +flexibility in configuring the system. + +The default is a prefix path length of one, which makes the library's +behavior backwards-compatible with v5.0.0. + Table of Contents ----------------- * [Installing the Driver](#installing-the-driver) diff --git a/src/main/java/com/bettercloud/vault/VaultConfig.java b/src/main/java/com/bettercloud/vault/VaultConfig.java index 85ed7034..29cf4b27 100644 --- a/src/main/java/com/bettercloud/vault/VaultConfig.java +++ b/src/main/java/com/bettercloud/vault/VaultConfig.java @@ -36,6 +36,7 @@ public class VaultConfig implements Serializable { private SslConfig sslConfig; private Integer openTimeout; private Integer readTimeout; + private int prefixPathDepth = 1; private int maxRetries; private int retryIntervalMilliseconds; private Integer globalEngineVersion; @@ -207,6 +208,57 @@ public VaultConfig readTimeout(final Integer readTimeout) { return this; } + /** + *

Set the "path depth" of the prefix path. Normally this is just + * 1, to correspond to one path element in the prefix path. To use + * a longer prefix path, set this value + * + * @param prefixPathDepth integer number of path elements in the prefix path + */ + public VaultConfig prefixPathDepth(int pathLength) { + if (pathLength < 1) { + throw new IllegalArgumentException("pathLength must be > 1"); + } + + this.prefixPathDepth = pathLength; + return this; + } + + + /** + *

Set the "path depth" of the prefix path, by explicitly specifying + * the prefix path, e.g., "foo/bar/blah" would set the prefix path depth + * to 3. + * + * @param prefixPath string prefix path, with or without initial or + * final forward slashes + */ + public VaultConfig prefixPath(String prefixPath) { + int orig = 0; + int pos; + int countElements = 0; + int pathLen = prefixPath.length(); + + if (pathLen == 0) { + throw new IllegalArgumentException("can't use an empty path"); + } + + while ((orig < pathLen) && + ((pos = prefixPath.indexOf('/',orig)) >= 0)) { + countElements++; + orig = pos+1; + } + + if (prefixPath.charAt(0) == '/') { + countElements--; + } + if (prefixPath.charAt(pathLen-1) == '/') { + countElements--; + } + + return prefixPathDepth(countElements+1); + } + /** *

Sets the maximum number of times that an API operation will retry upon failure.

* @@ -245,6 +297,8 @@ void setEngineVersion(final Integer engineVersion) { this.globalEngineVersion = engineVersion; } + + /** *

This is the terminating method in the builder pattern. The method that validates all of the fields that * has been set already, uses environment variables when available to populate any unset fields, and returns @@ -330,5 +384,8 @@ public String getNameSpace() { return nameSpace; } + public int getPrefixPathDepth() { + return prefixPathDepth; + } } diff --git a/src/main/java/com/bettercloud/vault/api/Logical.java b/src/main/java/com/bettercloud/vault/api/Logical.java index 1edb3937..fb19f020 100644 --- a/src/main/java/com/bettercloud/vault/api/Logical.java +++ b/src/main/java/com/bettercloud/vault/api/Logical.java @@ -87,7 +87,7 @@ private LogicalResponse read(final String path, Boolean shouldRetry, final logic try { // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, operation)) + .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, config.getPrefixPathDepth(), operation)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -155,7 +155,7 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege try { // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, logicalOperations.readV2)) + .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, config.getPrefixPathDepth(), logicalOperations.readV2)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) .parameter("version", version.toString()) @@ -254,7 +254,7 @@ private LogicalResponse write(final String path, final Map nameV } // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, operation)) + .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, config.getPrefixPathDepth(), operation)) .body(jsonObjectToWriteFromEngineVersion(operation, requestJson).toString().getBytes(StandardCharsets.UTF_8)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) @@ -314,7 +314,7 @@ public LogicalResponse list(final String path) throws VaultException { private LogicalResponse list(final String path, final logicalOperations operation) throws VaultException { LogicalResponse response = null; try { - response = read(adjustPathForList(path, operation), true, operation); + response = read(adjustPathForList(path, config.getPrefixPathDepth(), operation), true, operation); } catch (final VaultException e) { if (e.getHttpStatusCode() != 404) { throw e; @@ -346,7 +346,7 @@ private LogicalResponse delete(final String path, final Logical.logicalOperation try { // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForDelete(path, operation)) + .url(config.getAddress() + "/v1/" + adjustPathForDelete(path, config.getPrefixPathDepth(), operation)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -406,7 +406,7 @@ public LogicalResponse delete(final String path, final int[] versions) throws Va // Make an HTTP request to Vault JsonObject versionsToDelete = new JsonObject().add("versions", versions); final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path)) + .url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path,config.getPrefixPathDepth())) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -477,7 +477,7 @@ public LogicalResponse unDelete(final String path, final int[] versions) throws // Make an HTTP request to Vault JsonObject versionsToUnDelete = new JsonObject().add("versions", versions); final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path)) + .url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path,config.getPrefixPathDepth())) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -536,7 +536,7 @@ public LogicalResponse destroy(final String path, final int[] versions) throws V // Make an HTTP request to Vault JsonObject versionsToDestroy = new JsonObject().add("versions", versions); final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path)) + .url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path,config.getPrefixPathDepth())) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/LogicalUtilities.java b/src/main/java/com/bettercloud/vault/api/LogicalUtilities.java index a1746fbc..c446032f 100644 --- a/src/main/java/com/bettercloud/vault/api/LogicalUtilities.java +++ b/src/main/java/com/bettercloud/vault/api/LogicalUtilities.java @@ -28,16 +28,25 @@ private static List getPathSegments(final String path) { * path to be converted for use with a Version 2 secret engine. * * @param segments The Vault path split into segments. + * @param prefixPathDepth number of path elements in the prefix part of + * the path (the part before the qualifier) * @param qualifier The String to add to the path, based on the operation. * @return The final path with the needed qualifier. */ - public static String addQualifierToPath(final List segments, final String qualifier) { - final StringBuilder adjustedPath = new StringBuilder(segments.get(0)).append('/').append(qualifier).append('/'); - for (int index = 1; index < segments.size(); index++) { - adjustedPath.append(segments.get(index)); - if (index + 1 < segments.size()) { - adjustedPath.append('/'); - } + public static String addQualifierToPath(final List segments, final int prefixPathDepth, final String qualifier) { + final StringBuilder adjustedPath = new StringBuilder(); + int index; + + for (index=0;index < prefixPathDepth;index++) { + adjustedPath.append(segments.get(index)) + .append('/'); + } + + adjustedPath.append(qualifier); + + for (;index < segments.size(); index++) { + adjustedPath.append('/') + .append(segments.get(index)); } return adjustedPath.toString(); } @@ -51,11 +60,11 @@ public static String addQualifierToPath(final List segments, final Strin * @param operation The operation being performed, e.g. readV2 or writeV1. * @return The Vault path mutated based on the operation. */ - public static String adjustPathForReadOrWrite(final String path, final Logical.logicalOperations operation) { + public static String adjustPathForReadOrWrite(final String path, final int prefixPathLength,final Logical.logicalOperations operation) { final List pathSegments = getPathSegments(path); if (operation.equals(Logical.logicalOperations.readV2) || operation.equals(Logical.logicalOperations.writeV2)) { // Version 2 - final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, "data")); + final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, prefixPathLength, "data")); if (path.endsWith("/")) { adjustedPath.append("/"); } @@ -75,12 +84,12 @@ public static String adjustPathForReadOrWrite(final String path, final Logical.l * @param operation The operation being performed, e.g. readV2 or writeV1. * @return The Vault path mutated based on the operation. */ - public static String adjustPathForList(final String path, final Logical.logicalOperations operation) { + public static String adjustPathForList(final String path, int prefixPathDepth, final Logical.logicalOperations operation) { final List pathSegments = getPathSegments(path); final StringBuilder adjustedPath = new StringBuilder(); if (operation.equals(Logical.logicalOperations.listV2)) { // Version 2 - adjustedPath.append(addQualifierToPath(pathSegments, "metadata")); + adjustedPath.append(addQualifierToPath(pathSegments, prefixPathDepth, "metadata")); if (path.endsWith("/")) { adjustedPath.append("/"); } @@ -102,10 +111,10 @@ public static String adjustPathForList(final String path, final Logical.logicalO * * @return The modified path */ - public static String adjustPathForDelete(final String path, final Logical.logicalOperations operation) { + public static String adjustPathForDelete(final String path, final int prefixPathDepth, final Logical.logicalOperations operation) { final List pathSegments = getPathSegments(path); if (operation.equals(Logical.logicalOperations.deleteV2)) { - final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, "metadata")); + final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, prefixPathDepth, "metadata")); if (path.endsWith("/")) { adjustedPath.append("/"); } @@ -122,9 +131,9 @@ public static String adjustPathForDelete(final String path, final Logical.logica * * @return The modified path */ - public static String adjustPathForVersionDelete(final String path) { + public static String adjustPathForVersionDelete(final String path,final int prefixPathDepth) { final List pathSegments = getPathSegments(path); - final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, "delete")); + final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, prefixPathDepth, "delete")); if (path.endsWith("/")) { adjustedPath.append("/"); } @@ -137,9 +146,9 @@ public static String adjustPathForVersionDelete(final String path) { * @param path The Vault path to check or mutate, based on the operation. * @return The path mutated depending on the operation. */ - public static String adjustPathForVersionUnDelete(final String path) { + public static String adjustPathForVersionUnDelete(final String path, final int prefixPathDepth) { final List pathSegments = getPathSegments(path); - final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, "undelete")); + final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, prefixPathDepth, "undelete")); if (path.endsWith("/")) { adjustedPath.append("/"); } @@ -152,9 +161,9 @@ public static String adjustPathForVersionUnDelete(final String path) { * @param path The Vault path to check or mutate, based on the operation. * @return The path mutated depending on the operation. */ - public static String adjustPathForVersionDestroy(final String path) { + public static String adjustPathForVersionDestroy(final String path,final int prefixPathDepth) { final List pathSegments = getPathSegments(path); - final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, "destroy")); + final StringBuilder adjustedPath = new StringBuilder(addQualifierToPath(pathSegments, prefixPathDepth, "destroy")); if (path.endsWith("/")) { adjustedPath.append("/"); }