Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/main/java/io/github/jopenlibs/vault/api/Logical.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -338,7 +338,25 @@ public LogicalResponse list(final String path) throws VaultException {
}
}

private LogicalResponse list(final String path, final logicalOperations operation)
/**
* <p>Retrieve a list of keys corresponding to key/value pairs at a given Vault path.</p>
*
* <p>Key values ending with a trailing-slash characters are sub-paths. Running a subsequent
* <code>list()</code>
* call, using the original path appended with this key, will retrieve all secret keys stored at
* that sub-path.</p>
*
* <p>This method returns only the secret keys, not values. To retrieve the actual stored
* value for a key, use <code>read()</code> with the key appended onto the original base
* path.</p>
*
* @param path The Vault key value at which to look for secrets (e.g. <code>secret</code>)
* @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 {
Expand Down
27 changes: 17 additions & 10 deletions src/main/java/io/github/jopenlibs/vault/api/LogicalUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,24 @@ public static String adjustPathForList(final String path, int prefixPathDepth,
final Logical.logicalOperations operation) {
final List<String> 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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,14 +19,15 @@
*/
public class LogicalResponse extends VaultResponse {

private Map<String, String> data = new HashMap<>();
private List<String> listData = new ArrayList<>();
private final Map<String, String> data = new HashMap<>();
private final List<String> listData = new ArrayList<>();
private final List<String> listSubkeys = new ArrayList<>();
private final Map<String, String> dataMetadata = new HashMap<>();
private JsonObject dataObject = null;
private String leaseId;
private WrapResponse wrapResponse;
private Boolean renewable;
private Long leaseDuration;
private final Map<String, String> dataMetadata = new HashMap<>();

/**
* @param restResponse The raw HTTP response from Vault.
Expand Down Expand Up @@ -71,6 +73,10 @@ public DataMetadata getDataMetadata() {
return new DataMetadata(dataMetadata);
}

public List<String> getListSubkeys() {
return listSubkeys;
}

private void parseMetadataFields() {
try {
final String jsonString = new String(getRestResponse().getBody(),
Expand Down Expand Up @@ -98,17 +104,14 @@ private void parseResponseData(final Logical.logicalOperations operation) {
parseJsonIntoMap(metadataValue.asObject(), dataMetadata);
}
}
data = new HashMap<>();

dataObject = jsonObject.get("data").asObject();
parseJsonIntoMap(dataObject, data);

// 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++) {
Expand All @@ -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.listSubkeys.addAll(keys.names());
}
}
} catch (Exception ignored) {
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
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;
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;
Expand All @@ -26,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.
Expand Down Expand Up @@ -306,6 +309,27 @@ 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.
* 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<String, Object> testMap = Map.of("value", "world", "test", "done");

vault.logical().write("secret/hello", testMap);
final List<String> 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, using KV Engine version
* 1.
Expand Down
Loading