From ac9c18ba289a6a1f5c89e6e665c203730f095f02 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sun, 14 Dec 2025 00:26:13 +0100 Subject: [PATCH 1/8] Added enumerator value --- .../github/jopenlibs/vault/api/Logical.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java index 6fe3e06f..ec101317 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java @@ -33,7 +33,7 @@ public class Logical extends OperationsBase { private String nameSpace; - public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, readV1, readV2, writeV1, writeV2, unDelete, mount} + public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, listSubKeys, readV1, readV2, writeV1, writeV2, unDelete, mount} public Logical(final VaultConfig config) { super(config); @@ -338,7 +338,25 @@ public LogicalResponse list(final String path) throws VaultException { } } - private LogicalResponse list(final String path, final logicalOperations operation) + /** + *

Retrieve a list of keys corresponding to key/value pairs at a given Vault path.

+ * + *

Key values ending with a trailing-slash characters are sub-paths. Running a subsequent + * list() + * call, using the original path appended with this key, will retrieve all secret keys stored at + * that sub-path.

+ * + *

This method returns only the secret keys, not values. To retrieve the actual stored + * value for a key, use read() with the key appended onto the original base + * path.

+ * + * @param path The Vault key value at which to look for secrets (e.g. secret) + * @param operation The Vault operation involved to retrieve list + * @return A list of keys corresponding to key/value pairs at a given Vault path, or an empty + * list if there are none + * @throws VaultException If any errors occur, or unexpected response received from Vault + */ + public LogicalResponse list(final String path, final logicalOperations operation) throws VaultException { LogicalResponse response = null; try { From be75f69eca64801c598c60f1b8da81a3652d75b5 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sun, 14 Dec 2025 00:35:57 +0100 Subject: [PATCH 2/8] Added tests --- .../github/jopenlibs/vault/api/LogicalIT.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java index 718c4014..e333b92f 100644 --- a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java +++ b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java @@ -3,6 +3,7 @@ import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultConfig; import io.github.jopenlibs.vault.VaultException; +import io.github.jopenlibs.vault.api.Logical.logicalOperations; import io.github.jopenlibs.vault.response.AuthResponse; import io.github.jopenlibs.vault.response.DataMetadata; import io.github.jopenlibs.vault.response.LogicalResponse; @@ -306,6 +307,43 @@ public void testList() throws VaultException { assertTrue(keys.contains("hello")); } + /** + * Write a secret, and then verify that its key shows up in the list, returning their subkeys + * when we use KV Engine version 2. + * + * @throws VaultException On error. + */ + @Test + public void testListSubKeys() throws VaultException { + final Vault vault = container.getRootVault(); + final Map testMap = Map.of("value", "world", "test", "done"); + + vault.logical().write("secret/hello", testMap); + final List keys = vault.logical() + .list("secret/hello", logicalOperations.listSubKeys).getListSubkeys(); + assertTrue(keys.contains("test")); + assertTrue(keys.contains("value")); + } + + /** + * Write a secret, and then verify that its key shows up in the list, returning their subkeys + * when we use KV Engine version 2. + * + * @throws VaultException On error. + */ + @Test + public void testListSubKeysV1() throws VaultException { + final Vault vault = container.getRootVaultWithCustomVaultConfig( + new VaultConfig().engineVersion(1)); + final Map testMap = Map.of("value", "world"); + + vault.logical().write("secret/hello", testMap); + final List keys = vault.logical() + .list("secret/hello", logicalOperations.listSubKeys).getListSubkeys(); + assertTrue(keys.contains("value")); + } + + /** * Write a secret, and then verify that its key shows up in the list, using KV Engine version * 1. From 413ae169a33a67f0c005bd3f49c5fd2119c364c5 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sun, 14 Dec 2025 00:37:02 +0100 Subject: [PATCH 3/8] Retrieved subkeys --- .../vault/response/LogicalResponse.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java b/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java index 678e18d2..054910a5 100644 --- a/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java +++ b/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java @@ -1,6 +1,7 @@ package io.github.jopenlibs.vault.response; import io.github.jopenlibs.vault.api.Logical; +import io.github.jopenlibs.vault.api.Logical.logicalOperations; import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonArray; import io.github.jopenlibs.vault.json.JsonObject; @@ -26,6 +27,7 @@ public class LogicalResponse extends VaultResponse { private Boolean renewable; private Long leaseDuration; private final Map dataMetadata = new HashMap<>(); + private List subkeys; /** * @param restResponse The raw HTTP response from Vault. @@ -71,6 +73,10 @@ public DataMetadata getDataMetadata() { return new DataMetadata(dataMetadata); } + public List getListSubkeys() { + return subkeys; + } + private void parseMetadataFields() { try { final String jsonString = new String(getRestResponse().getBody(), @@ -105,10 +111,7 @@ private void parseResponseData(final Logical.logicalOperations operation) { // For list operations convert the array of keys to a list of values if (operation.equals(Logical.logicalOperations.listV1) || operation.equals( Logical.logicalOperations.listV2)) { - if ( - getRestResponse().getStatus() != 404 - && data.get("keys") != null - ) { + if (getRestResponse().getStatus() != 404 && data.get("keys") != null) { final JsonArray keys = Json.parse(data.get("keys")).asArray(); for (int index = 0; index < keys.size(); index++) { @@ -117,6 +120,13 @@ private void parseResponseData(final Logical.logicalOperations operation) { } } + + if (operation.equals(logicalOperations.listSubKeys)) { + if (data.containsKey("subkeys")) { + final var keys = Json.parse(data.get("subkeys")).asObject(); + this.subkeys = keys.names(); + } + } } catch (Exception ignored) { } } From 0b64e2201fa7891b319b7b934cf5b54ab294c9df Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sun, 14 Dec 2025 00:39:38 +0100 Subject: [PATCH 4/8] Adjusted path based to subkey enum --- .../jopenlibs/vault/api/LogicalUtilities.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java b/src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java index adc3af28..77af15a6 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java +++ b/src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java @@ -102,17 +102,24 @@ 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, prefixPathDepth, "metadata")); - if (path.endsWith("/")) { - adjustedPath.append("/"); - } - } else { - // Version 1 - adjustedPath.append(path); + switch (operation) { + case listV1: + // Version 1 + adjustedPath.append(path).append("?list=true"); + break; + case listV2: + // Version 2 + adjustedPath.append(addQualifierToPath(pathSegments, prefixPathDepth, "metadata")); + if (path.endsWith("/")) { + adjustedPath.append("/"); + } + adjustedPath.append("?list=true"); + break; + case listSubKeys: + // Subkeys in version 2 + adjustedPath.append(addQualifierToPath(pathSegments, prefixPathDepth, "subkeys")); + break; } - adjustedPath.append("?list=true"); return adjustedPath.toString(); } From ac5aee6fa24dd40f66655bdaf44c226052db3de5 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sun, 14 Dec 2025 15:33:12 +0100 Subject: [PATCH 5/8] Make final subkeys variable ad listData variable --- .../jopenlibs/vault/response/LogicalResponse.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java b/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java index 054910a5..e0de38a2 100644 --- a/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java +++ b/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java @@ -19,15 +19,15 @@ */ public class LogicalResponse extends VaultResponse { - private Map data = new HashMap<>(); - private List listData = new ArrayList<>(); + private final Map data = new HashMap<>(); + private final List listData = new ArrayList<>(); + private final List subkeys = new ArrayList<>(); + private final Map dataMetadata = new HashMap<>(); private JsonObject dataObject = null; private String leaseId; private WrapResponse wrapResponse; private Boolean renewable; private Long leaseDuration; - private final Map dataMetadata = new HashMap<>(); - private List subkeys; /** * @param restResponse The raw HTTP response from Vault. @@ -104,7 +104,7 @@ private void parseResponseData(final Logical.logicalOperations operation) { parseJsonIntoMap(metadataValue.asObject(), dataMetadata); } } - data = new HashMap<>(); + dataObject = jsonObject.get("data").asObject(); parseJsonIntoMap(dataObject, data); @@ -124,7 +124,7 @@ private void parseResponseData(final Logical.logicalOperations operation) { if (operation.equals(logicalOperations.listSubKeys)) { if (data.containsKey("subkeys")) { final var keys = Json.parse(data.get("subkeys")).asObject(); - this.subkeys = keys.names(); + this.subkeys.addAll(keys.names()); } } } catch (Exception ignored) { From 044ca3cbece17ba69df30b4a8bc7f48ca3b0759e Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sun, 14 Dec 2025 15:34:22 +0100 Subject: [PATCH 6/8] Renamed variable --- .../io/github/jopenlibs/vault/response/LogicalResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java b/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java index e0de38a2..f9f58a76 100644 --- a/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java +++ b/src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java @@ -21,7 +21,7 @@ public class LogicalResponse extends VaultResponse { private final Map data = new HashMap<>(); private final List listData = new ArrayList<>(); - private final List subkeys = new ArrayList<>(); + private final List listSubkeys = new ArrayList<>(); private final Map dataMetadata = new HashMap<>(); private JsonObject dataObject = null; private String leaseId; @@ -74,7 +74,7 @@ public DataMetadata getDataMetadata() { } public List getListSubkeys() { - return subkeys; + return listSubkeys; } private void parseMetadataFields() { @@ -124,7 +124,7 @@ private void parseResponseData(final Logical.logicalOperations operation) { if (operation.equals(logicalOperations.listSubKeys)) { if (data.containsKey("subkeys")) { final var keys = Json.parse(data.get("subkeys")).asObject(); - this.subkeys.addAll(keys.names()); + this.listSubkeys.addAll(keys.names()); } } } catch (Exception ignored) { From 97122c19d28d2fa6b01df2887d35a5a9faa7a801 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Mon, 15 Dec 2025 00:17:10 +0100 Subject: [PATCH 7/8] Removed unprocessable test --- .../github/jopenlibs/vault/api/LogicalIT.java | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java index e333b92f..d79c5acb 100644 --- a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java +++ b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java @@ -9,6 +9,7 @@ import io.github.jopenlibs.vault.response.LogicalResponse; import io.github.jopenlibs.vault.response.WrapResponse; import io.github.jopenlibs.vault.util.VaultContainer; +import io.github.jopenlibs.vault.util.VaultVersion; import java.io.IOException; import java.util.HashMap; import java.util.List; @@ -27,6 +28,7 @@ import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNotSame; import static junit.framework.TestCase.assertTrue; +import static org.junit.Assume.assumeTrue; /** * Integration tests for the basic (i.e. "logical") Vault API operations. @@ -325,25 +327,6 @@ public void testListSubKeys() throws VaultException { assertTrue(keys.contains("value")); } - /** - * Write a secret, and then verify that its key shows up in the list, returning their subkeys - * when we use KV Engine version 2. - * - * @throws VaultException On error. - */ - @Test - public void testListSubKeysV1() throws VaultException { - final Vault vault = container.getRootVaultWithCustomVaultConfig( - new VaultConfig().engineVersion(1)); - final Map testMap = Map.of("value", "world"); - - vault.logical().write("secret/hello", testMap); - final List keys = vault.logical() - .list("secret/hello", logicalOperations.listSubKeys).getListSubkeys(); - assertTrue(keys.contains("value")); - } - - /** * Write a secret, and then verify that its key shows up in the list, using KV Engine version * 1. From 2349be2dd0d479877c403ca4e59094e2eff77876 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Mon, 15 Dec 2025 00:18:24 +0100 Subject: [PATCH 8/8] Checked version with test works --- src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java index d79c5acb..c90b7775 100644 --- a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java +++ b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java @@ -312,11 +312,14 @@ public void testList() throws VaultException { /** * Write a secret, and then verify that its key shows up in the list, returning their subkeys * when we use KV Engine version 2. + * This test works from Vault 1.10.0 and onward * * @throws VaultException On error. */ @Test public void testListSubKeys() throws VaultException { + assumeTrue(VaultVersion.greatThan("1.9.10")); + final Vault vault = container.getRootVault(); final Map testMap = Map.of("value", "world", "test", "done");