com.bettercloud.vault.api.Logical Maven / Gradle / Ivy
Show all versions of vault-java-driver Show documentation
package com.bettercloud.vault.api;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.json.Json;
import com.bettercloud.vault.json.JsonArray;
import com.bettercloud.vault.json.JsonObject;
import com.bettercloud.vault.json.JsonValue;
import com.bettercloud.vault.response.LogicalResponse;
import com.bettercloud.vault.rest.Rest;
import com.bettercloud.vault.rest.RestException;
import com.bettercloud.vault.rest.RestResponse;
/**
* The implementing class for Vault's core/logical operations (e.g. read, write).
*
* This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault
* in a DSL-style builder pattern. See the Javadoc comments of each public
method for usage examples.
*/
public class Logical {
private final VaultConfig config;
public Logical(final VaultConfig config) {
this.config = config;
}
/**
* Basic read operation to retrieve a secret. A single secret key can map to multiple name-value pairs,
* which can be retrieved from the response object. E.g.:
*
*
* {@code
* final LogicalResponse response = vault.logical().read("secret/hello");
*
* final String value = response.getData().get("value");
* final String otherValue = response.getData().get("other_value");
* }
*
*
* @param path The Vault key value from which to read (e.g. secret/hello
)
* @return
* @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, invalid JSON payload, etc), and the maximum number of retries is exceeded.
*/
public LogicalResponse read(final String path) throws VaultException {
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + path)
.header("X-Vault-Token", config.getToken())
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslPemUTF8(config.getSslPemUTF8())
.sslVerification(config.isSslVerify() != null ? config.isSslVerify() : null)
.get();
// Validate response
if (restResponse.getStatus() != 200) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus());
}
final Map data = parseResponseData(restResponse);
return new LogicalResponse(restResponse, retryCount, data);
} catch (RuntimeException | VaultException | RestException e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else {
// ... otherwise, give up.
throw new VaultException(e);
}
}
}
}
/**
* Basic operation to store secrets. Multiple name value pairs can be stored under the same secret key.
* E.g.:
*
*
* {@code
* final Map nameValuePairs = new HashMap();
* nameValuePairs.put("value", "foo");
* nameValuePairs.put("other_value", "bar");
*
* final LogicalResponse response = vault.logical().write("secret/hello", nameValuePairs);
* }
*
*
* @param path The Vault key value to which to write (e.g. secret/hello
)
* @param nameValuePairs Secret name and value pairs to store under this Vault key
* @throws VaultException
*/
public LogicalResponse write(final String path, final Map nameValuePairs) throws VaultException {
int retryCount = 0;
while (true) {
try {
JsonObject requestJson = Json.object();
for (final Map.Entry pair : nameValuePairs.entrySet()) {
requestJson = requestJson.add(pair.getKey(), pair.getValue());
}
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + path)
.body(requestJson.toString().getBytes("UTF-8"))
.header("X-Vault-Token", config.getToken())
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslPemUTF8(config.getSslPemUTF8())
.sslVerification(config.isSslVerify() != null ? config.isSslVerify() : null)
.post();
// HTTP Status should be either 200 (with content - e.g. PKI write) or 204 (no content)
final int restStatus = restResponse.getStatus();
if (restStatus == 204) {
return new LogicalResponse(restResponse, retryCount);
} else if (restStatus == 200) {
final Map data = parseResponseData(restResponse);
return new LogicalResponse(restResponse, retryCount, data);
} else {
throw new VaultException("Expecting HTTP status 204 or 200, but instead receiving " + restStatus);
}
} catch (Exception e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else {
// ... otherwise, give up.
throw new VaultException(e);
}
}
}
}
/**
/**
* 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
)
* @return
* @throws VaultException
*/
public List list(final String path) throws VaultException {
final String fullPath = path == null ? "list=true" : path + "?list=true";
final LogicalResponse response = read(fullPath);
final List returnValues = new ArrayList<>();
if (response.getData() != null && response.getData().get("keys") != null) {
final JsonArray keys = Json.parse(response.getData().get("keys")).asArray();
for (int index = 0; index < keys.size(); index++) {
returnValues.add(keys.get(index).asString());
}
}
return returnValues;
}
/**
* Deletes the key/value pair located at the provided path.
*
* If the path represents a sub-path, then all of its contents must be deleted prior to deleting the empty
* sub-path itself.
*
* @param path The Vault key value to delete (e.g. secret/hello
).
* @return
* @throws VaultException
*/
public LogicalResponse delete(final String path) throws VaultException {
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + path)
.header("X-Vault-Token", config.getToken())
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslPemUTF8(config.getSslPemUTF8())
.sslVerification(config.isSslVerify() != null ? config.isSslVerify() : null)
.delete();
// Validate response
if (restResponse.getStatus() != 204) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus());
}
return new LogicalResponse(restResponse, retryCount);
} catch (RuntimeException | VaultException | RestException e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else {
// ... otherwise, give up.
throw new VaultException(e);
}
}
}
}
/**
* This logic will move into the LogicalResponse constructor
.
*
* @param restResponse
* @return
* @throws VaultException
*/
@Deprecated
private Map parseResponseData(final RestResponse restResponse) throws VaultException {
final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType();
if (!mimeType.equals("application/json")) {
throw new VaultException("Vault responded with MIME type: " + mimeType);
}
String jsonString;
try {
jsonString = new String(restResponse.getBody(), "UTF-8");//NOPMD
} catch (UnsupportedEncodingException e) {
throw new VaultException(e);
}
// Parse JSON
final Map data = new HashMap();//NOPMD
for (final JsonObject.Member member : Json.parse(jsonString).asObject().get("data").asObject()) {
final JsonValue jsonValue = member.getValue();
if (jsonValue == null || jsonValue.isNull()) {
continue;
} else if (jsonValue.isString()) {
data.put(member.getName(), jsonValue.asString());
} else {
data.put(member.getName(), jsonValue.toString());
}
}
return data;
}
}