From 0ac0fdded4f44886a53c67a3231c34737370e962 Mon Sep 17 00:00:00 2001 From: Brad Kaiser Date: Tue, 27 Aug 2019 12:32:26 -0500 Subject: [PATCH 1/3] Allow path prefixes to be multi-level. --- .../com/bettercloud/vault/VaultConfig.java | 57 +++++++++++++++++++ .../com/bettercloud/vault/api/Logical.java | 16 +++--- .../vault/api/LogicalUtilities.java | 47 ++++++++------- 3 files changed, 93 insertions(+), 27 deletions(-) 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("/"); } From 6a1642c90f489c14869665b9e337f0451c64d6d4 Mon Sep 17 00:00:00 2001 From: Brad Kaiser Date: Tue, 27 Aug 2019 12:42:42 -0500 Subject: [PATCH 2/3] Add a description of the change. --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index a517c9ea..8d254c2e 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 behavior is to use 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) From 46322afe45b61eb28577d84e6ff4bcb0c70649d2 Mon Sep 17 00:00:00 2001 From: Brad Kaiser Date: Tue, 27 Aug 2019 13:43:13 -0500 Subject: [PATCH 3/3] Tweak text formatting slightly. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d254c2e..8e98111f 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,15 @@ 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*. +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 behavior is to use a prefix path length of one, which makes -the library's behavior backwards-compatible with v5.0.0. +The default is a prefix path length of one, which makes the library's +behavior backwards-compatible with v5.0.0. Table of Contents -----------------