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("/");
}