Skip to content

Commit a1a7d44

Browse files
authored
Read subkeys in KV element (#74)
1 parent 4218f58 commit a1a7d44

4 files changed

Lines changed: 79 additions & 20 deletions

File tree

src/main/java/io/github/jopenlibs/vault/api/Logical.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class Logical extends OperationsBase {
3333

3434
private String nameSpace;
3535

36-
public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, readV1, readV2, writeV1, writeV2, unDelete, mount}
36+
public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, listSubKeys, readV1, readV2, writeV1, writeV2, unDelete, mount}
3737

3838
public Logical(final VaultConfig config) {
3939
super(config);
@@ -338,7 +338,25 @@ public LogicalResponse list(final String path) throws VaultException {
338338
}
339339
}
340340

341-
private LogicalResponse list(final String path, final logicalOperations operation)
341+
/**
342+
* <p>Retrieve a list of keys corresponding to key/value pairs at a given Vault path.</p>
343+
*
344+
* <p>Key values ending with a trailing-slash characters are sub-paths. Running a subsequent
345+
* <code>list()</code>
346+
* call, using the original path appended with this key, will retrieve all secret keys stored at
347+
* that sub-path.</p>
348+
*
349+
* <p>This method returns only the secret keys, not values. To retrieve the actual stored
350+
* value for a key, use <code>read()</code> with the key appended onto the original base
351+
* path.</p>
352+
*
353+
* @param path The Vault key value at which to look for secrets (e.g. <code>secret</code>)
354+
* @param operation The Vault operation involved to retrieve list
355+
* @return A list of keys corresponding to key/value pairs at a given Vault path, or an empty
356+
* list if there are none
357+
* @throws VaultException If any errors occur, or unexpected response received from Vault
358+
*/
359+
public LogicalResponse list(final String path, final logicalOperations operation)
342360
throws VaultException {
343361
LogicalResponse response = null;
344362
try {

src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,24 @@ public static String adjustPathForList(final String path, int prefixPathDepth,
102102
final Logical.logicalOperations operation) {
103103
final List<String> pathSegments = getPathSegments(path);
104104
final StringBuilder adjustedPath = new StringBuilder();
105-
if (operation.equals(Logical.logicalOperations.listV2)) {
106-
// Version 2
107-
adjustedPath.append(addQualifierToPath(pathSegments, prefixPathDepth, "metadata"));
108-
if (path.endsWith("/")) {
109-
adjustedPath.append("/");
110-
}
111-
} else {
112-
// Version 1
113-
adjustedPath.append(path);
105+
switch (operation) {
106+
case listV1:
107+
// Version 1
108+
adjustedPath.append(path).append("?list=true");
109+
break;
110+
case listV2:
111+
// Version 2
112+
adjustedPath.append(addQualifierToPath(pathSegments, prefixPathDepth, "metadata"));
113+
if (path.endsWith("/")) {
114+
adjustedPath.append("/");
115+
}
116+
adjustedPath.append("?list=true");
117+
break;
118+
case listSubKeys:
119+
// Subkeys in version 2
120+
adjustedPath.append(addQualifierToPath(pathSegments, prefixPathDepth, "subkeys"));
121+
break;
114122
}
115-
adjustedPath.append("?list=true");
116123
return adjustedPath.toString();
117124
}
118125

src/main/java/io/github/jopenlibs/vault/response/LogicalResponse.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.jopenlibs.vault.response;
22

33
import io.github.jopenlibs.vault.api.Logical;
4+
import io.github.jopenlibs.vault.api.Logical.logicalOperations;
45
import io.github.jopenlibs.vault.json.Json;
56
import io.github.jopenlibs.vault.json.JsonArray;
67
import io.github.jopenlibs.vault.json.JsonObject;
@@ -18,14 +19,15 @@
1819
*/
1920
public class LogicalResponse extends VaultResponse {
2021

21-
private Map<String, String> data = new HashMap<>();
22-
private List<String> listData = new ArrayList<>();
22+
private final Map<String, String> data = new HashMap<>();
23+
private final List<String> listData = new ArrayList<>();
24+
private final List<String> listSubkeys = new ArrayList<>();
25+
private final Map<String, String> dataMetadata = new HashMap<>();
2326
private JsonObject dataObject = null;
2427
private String leaseId;
2528
private WrapResponse wrapResponse;
2629
private Boolean renewable;
2730
private Long leaseDuration;
28-
private final Map<String, String> dataMetadata = new HashMap<>();
2931

3032
/**
3133
* @param restResponse The raw HTTP response from Vault.
@@ -71,6 +73,10 @@ public DataMetadata getDataMetadata() {
7173
return new DataMetadata(dataMetadata);
7274
}
7375

76+
public List<String> getListSubkeys() {
77+
return listSubkeys;
78+
}
79+
7480
private void parseMetadataFields() {
7581
try {
7682
final String jsonString = new String(getRestResponse().getBody(),
@@ -98,17 +104,14 @@ private void parseResponseData(final Logical.logicalOperations operation) {
98104
parseJsonIntoMap(metadataValue.asObject(), dataMetadata);
99105
}
100106
}
101-
data = new HashMap<>();
107+
102108
dataObject = jsonObject.get("data").asObject();
103109
parseJsonIntoMap(dataObject, data);
104110

105111
// For list operations convert the array of keys to a list of values
106112
if (operation.equals(Logical.logicalOperations.listV1) || operation.equals(
107113
Logical.logicalOperations.listV2)) {
108-
if (
109-
getRestResponse().getStatus() != 404
110-
&& data.get("keys") != null
111-
) {
114+
if (getRestResponse().getStatus() != 404 && data.get("keys") != null) {
112115

113116
final JsonArray keys = Json.parse(data.get("keys")).asArray();
114117
for (int index = 0; index < keys.size(); index++) {
@@ -117,6 +120,13 @@ private void parseResponseData(final Logical.logicalOperations operation) {
117120
}
118121

119122
}
123+
124+
if (operation.equals(logicalOperations.listSubKeys)) {
125+
if (data.containsKey("subkeys")) {
126+
final var keys = Json.parse(data.get("subkeys")).asObject();
127+
this.listSubkeys.addAll(keys.names());
128+
}
129+
}
120130
} catch (Exception ignored) {
121131
}
122132
}

src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import io.github.jopenlibs.vault.Vault;
44
import io.github.jopenlibs.vault.VaultConfig;
55
import io.github.jopenlibs.vault.VaultException;
6+
import io.github.jopenlibs.vault.api.Logical.logicalOperations;
67
import io.github.jopenlibs.vault.response.AuthResponse;
78
import io.github.jopenlibs.vault.response.DataMetadata;
89
import io.github.jopenlibs.vault.response.LogicalResponse;
910
import io.github.jopenlibs.vault.response.WrapResponse;
1011
import io.github.jopenlibs.vault.util.VaultContainer;
12+
import io.github.jopenlibs.vault.util.VaultVersion;
1113
import java.io.IOException;
1214
import java.util.HashMap;
1315
import java.util.List;
@@ -26,6 +28,7 @@
2628
import static junit.framework.TestCase.assertNotNull;
2729
import static junit.framework.TestCase.assertNotSame;
2830
import static junit.framework.TestCase.assertTrue;
31+
import static org.junit.Assume.assumeTrue;
2932

3033
/**
3134
* Integration tests for the basic (i.e. "logical") Vault API operations.
@@ -306,6 +309,27 @@ public void testList() throws VaultException {
306309
assertTrue(keys.contains("hello"));
307310
}
308311

312+
/**
313+
* Write a secret, and then verify that its key shows up in the list, returning their subkeys
314+
* when we use KV Engine version 2.
315+
* This test works from Vault 1.10.0 and onward
316+
*
317+
* @throws VaultException On error.
318+
*/
319+
@Test
320+
public void testListSubKeys() throws VaultException {
321+
assumeTrue(VaultVersion.greatThan("1.9.10"));
322+
323+
final Vault vault = container.getRootVault();
324+
final Map<String, Object> testMap = Map.of("value", "world", "test", "done");
325+
326+
vault.logical().write("secret/hello", testMap);
327+
final List<String> keys = vault.logical()
328+
.list("secret/hello", logicalOperations.listSubKeys).getListSubkeys();
329+
assertTrue(keys.contains("test"));
330+
assertTrue(keys.contains("value"));
331+
}
332+
309333
/**
310334
* Write a secret, and then verify that its key shows up in the list, using KV Engine version
311335
* 1.

0 commit comments

Comments
 (0)