From f1bf44ca0b08996d708cd9ddd29762b960b67807 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 22:37:12 +0200
Subject: [PATCH 01/34] Reused HttpClient object
---
.../github/jopenlibs/vault/VaultConfig.java | 34 +++++++++++++++++--
.../io/github/jopenlibs/vault/VaultImpl.java | 4 +--
.../jopenlibs/vault/api/sys/Leases.java | 10 +++---
.../github/jopenlibs/vault/api/sys/Seal.java | 7 ++--
.../jopenlibs/vault/api/sys/Wrapping.java | 9 +++--
.../vault/api/sys/mounts/Mounts.java | 12 +++----
.../io/github/jopenlibs/vault/rest/Rest.java | 20 +++++++++--
.../vault/ConnectionReUsageTest.java | 28 +++++++++++++--
8 files changed, 92 insertions(+), 32 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index a7327d3f..f62d55ea 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault;
+import io.github.jopenlibs.vault.rest.Rest;
import java.io.Serializable;
import java.net.http.HttpClient;
import java.util.Collections;
@@ -46,6 +47,7 @@ public class VaultConfig implements Serializable {
private String nameSpace;
private EnvironmentLoader environmentLoader;
private HttpClient httpClient;
+ private transient volatile HttpClient defaultHttpClient;
/**
* The code used to load environment variables is encapsulated here, so that a mock version
@@ -194,6 +196,7 @@ public VaultConfig putSecretsEngineVersionForPath(String path, String version) {
*/
public VaultConfig sslConfig(final SslConfig sslConfig) {
this.sslConfig = sslConfig;
+ this.defaultHttpClient = null;
return this;
}
@@ -212,6 +215,7 @@ public VaultConfig sslConfig(final SslConfig sslConfig) {
*/
public VaultConfig openTimeout(final Integer openTimeout) {
this.openTimeout = openTimeout;
+ this.defaultHttpClient = null;
return this;
}
@@ -286,8 +290,8 @@ public VaultConfig prefixPath(String prefixPath) {
/**
*
Set a preconfigured HttpClient instance to use by REST API calls. This allows to reuse
- * http resources (connections, worker threads) between calls. If a preconfigured HttpClient is specified, then
- * sslConfig and openTimeout values passed to VaultConfig are ignored.
+ * http resources (connections, worker threads) between calls. If a preconfigured HttpClient is
+ * specified, then sslConfig and openTimeout values passed to VaultConfig are ignored.
*
* @param httpClient preconfigured http client instance
* @return VaultConfig
@@ -390,6 +394,7 @@ public VaultConfig build() throws VaultException {
if (this.sslConfig == null) {
this.sslConfig = new SslConfig().environmentLoader(this.environmentLoader).build();
}
+ this.defaultHttpClient = null;
return this;
}
@@ -437,7 +442,30 @@ public int getPrefixPathDepth() {
return prefixPathDepth;
}
+ /**
+ *
Returns the HttpClient used by REST API calls: the one set with
+ * {@link #httpClient(HttpClient)} if any, otherwise a client built from this config's sslConfig
+ * and openTimeout values. The latter is built once and shared by all calls, so connections and
+ * worker threads are reused.
+ *
+ * @return The HttpClient used by REST API calls
+ */
public HttpClient getHttpClient() {
- return httpClient;
+ if (httpClient != null) {
+ return httpClient;
+ }
+ var client = defaultHttpClient;
+ if (client == null) {
+ synchronized (this) {
+ client = defaultHttpClient;
+ if (client == null) {
+ client = Rest.newHttpClient(openTimeout,
+ sslConfig == null ? null : sslConfig.isVerify(),
+ sslConfig == null ? null : sslConfig.getSslContext());
+ defaultHttpClient = client;
+ }
+ }
+ }
+ return client;
}
}
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 9b824655..eab96b9a 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -10,11 +10,9 @@
import io.github.jopenlibs.vault.api.sys.Sys;
import io.github.jopenlibs.vault.api.sys.mounts.Mounts;
import io.github.jopenlibs.vault.json.Json;
-import io.github.jopenlibs.vault.json.JsonObject;
import io.github.jopenlibs.vault.json.JsonValue;
import io.github.jopenlibs.vault.rest.Rest;
import io.github.jopenlibs.vault.rest.RestException;
-import io.github.jopenlibs.vault.rest.RestResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@@ -307,7 +305,7 @@ public Seal seal() {
*/
private Map collectSecretEngineVersions() {
try {
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = new Rest(vaultConfig.getHttpClient())//NOPMD
.url(vaultConfig.getAddress() + "/v1/sys/mounts")
.token(vaultConfig.getToken())
.header("X-Vault-Namespace", this.vaultConfig.getNameSpace())
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
index 7895becd..8265f964 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
@@ -5,8 +5,6 @@
import io.github.jopenlibs.vault.api.OperationsBase;
import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.response.VaultResponse;
-import io.github.jopenlibs.vault.rest.Rest;
-import io.github.jopenlibs.vault.rest.RestResponse;
import java.nio.charset.StandardCharsets;
@@ -54,7 +52,7 @@ public Leases withNameSpace(final String nameSpace) {
public VaultResponse revoke(final String leaseId) throws VaultException {
return retry(attempt -> {
final String requestJson = Json.object().add("lease_id", leaseId).toString();
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/leases/revoke")
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -95,7 +93,7 @@ public VaultResponse revoke(final String leaseId) throws VaultException {
*/
public VaultResponse revokePrefix(final String prefix) throws VaultException {
return retry(attempt -> {
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/leases/revoke-prefix/" + prefix)
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -137,7 +135,7 @@ public VaultResponse revokePrefix(final String prefix) throws VaultException {
*/
public VaultResponse revokeForce(final String prefix) throws VaultException {
return retry(attempt -> {
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/leases/revoke-force/" + prefix)
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -185,7 +183,7 @@ public VaultResponse renew(final String leaseId, final long increment) throws Va
return retry(attempt -> {
final String requestJson = Json.object().add("increment", increment).toString();
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/leases/renew/" + leaseId)
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
index 3b8b0226..129cce03 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
@@ -5,7 +5,6 @@
import io.github.jopenlibs.vault.api.OperationsBase;
import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.response.SealResponse;
-import io.github.jopenlibs.vault.rest.Rest;
import io.github.jopenlibs.vault.rest.RestResponse;
import java.nio.charset.StandardCharsets;
@@ -45,7 +44,7 @@ public Seal withNameSpace(final String nameSpace) {
public SealResponse seal() throws VaultException {
return retry((attempt) -> {
// HTTP request to Vault
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/seal")
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -86,7 +85,7 @@ public SealResponse unseal(final String key, final Boolean reset) throws VaultEx
// HTTP request to Vault
final String requestJson = Json.object().add("key", key).add("reset", reset)
.toString();
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/unseal")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -111,7 +110,7 @@ public SealResponse unseal(final String key, final Boolean reset) throws VaultEx
public SealResponse sealStatus() throws VaultException {
return retry((attempt) -> {
// HTTP request to Vault
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/seal-status")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
index 41bd2d02..11d35920 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
@@ -9,7 +9,6 @@
import io.github.jopenlibs.vault.response.LogicalResponse;
import io.github.jopenlibs.vault.response.UnwrapResponse;
import io.github.jopenlibs.vault.response.WrapResponse;
-import io.github.jopenlibs.vault.rest.Rest;
import io.github.jopenlibs.vault.rest.RestResponse;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
@@ -111,7 +110,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
return retry(attempt -> {
// HTTP request to Vault
- var rest = new Rest()//NOPMD
+ var rest = getRest()//NOPMD
.url(config.getAddress() + "/v1/sys/wrapping/lookup")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -212,7 +211,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
final String url = config.getAddress() + "/v1/sys/wrapping/wrap";
// HTTP request to Vault
- final var restResponse = new Rest()
+ final var restResponse = getRest()
.url(url)
.token(config.getToken())
.header("X-Vault-Wrap-TTL", Integer.toString(ttlInSec))
@@ -384,7 +383,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
final String url = config.getAddress() + "/v1/sys/wrapping/unwrap";
// HTTP request to Vault
- var rest = new Rest()
+ var rest = getRest()
.url(url)
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -471,7 +470,7 @@ public WrapResponse rewrap(final char[] wrappedToken) throws VaultException {
final String url = config.getAddress() + "/v1/sys/wrapping/rewrap";
// HTTP request to Vault
- final var restResponse = new Rest()
+ final var restResponse = getRest()
.url(url)
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
index 6d97fbf3..423d89f7 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
@@ -4,8 +4,6 @@
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
import io.github.jopenlibs.vault.response.MountResponse;
-import io.github.jopenlibs.vault.rest.Rest;
-import io.github.jopenlibs.vault.rest.RestResponse;
import java.nio.charset.StandardCharsets;
/**
@@ -45,7 +43,7 @@ public Mounts(final VaultConfig config) {
*/
public MountResponse list() throws VaultException {
return retry(attempt -> {
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(String.format("%s/v1/sys/mounts", config.getAddress()))
.token(config.getToken())
.header("X-Vault-Request", "true")
@@ -115,7 +113,7 @@ public MountResponse enable(final String path, final MountType type, final Mount
return retry(attempt -> {
final String requestJson = payload.toEnableJson(type).toString();
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path))
.token(config.getToken())
.header("X-Vault-Request", "true")
@@ -165,7 +163,7 @@ public MountResponse enable(final String path, final MountType type, final Mount
*/
public MountResponse disable(final String path) throws VaultException {
return retry(attempt -> {
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path))
.token(config.getToken())
.header("X-Vault-Request", "true")
@@ -214,7 +212,7 @@ public MountResponse disable(final String path) throws VaultException {
*/
public MountResponse read(final String path) throws VaultException {
return retry(attempt -> {
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path))
.token(config.getToken())
.header("X-Vault-Request", "true")
@@ -278,7 +276,7 @@ public MountResponse tune(final String path, final MountPayload payload) throws
final String requestJson = payload.toTuneJson().toString();
- final var restResponse = new Rest()//NOPMD
+ final var restResponse = getRest()//NOPMD
.url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path))
.token(config.getToken())
.header("X-Vault-Request", "true")
diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
index 5cd2afcf..0cc22a87 100644
--- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
+++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
@@ -203,8 +203,8 @@ public Rest parameter(final String name, final String value) throws RestExceptio
* This method may be chained together repeatedly
*
* @param value The vault token value as a {@code char[]}
- * @return This object, with a vault token added, ready for other builder-pattern config methods or
- * an HTTP verb method
+ * @return This object, with a vault token added, ready for other builder-pattern config methods
+ * or an HTTP verb method
*/
public Rest token(final char[] value) {
if (value != null && value.length > 0) {
@@ -461,6 +461,22 @@ private HttpClient getClient() {
if (configuredClient != null) {
return configuredClient;
}
+ return newHttpClient(connectTimeoutSeconds, sslVerification, sslContext);
+ }
+
+ /**
+ * Builds an {@link HttpClient} with the given connection settings. The returned client is meant
+ * to be built once and shared between requests (see {@link #Rest(HttpClient)}), so that
+ * connections and worker threads are reused.
+ *
+ * @param connectTimeoutSeconds Number of seconds to wait for a connection, or
+ * null for no timeout
+ * @param sslVerification false to disable SSL certificate verification
+ * @param sslContext The SSLContext to use, or null for the JVM default
+ * @return A new {@link HttpClient} instance
+ */
+ public static HttpClient newHttpClient(final Integer connectTimeoutSeconds,
+ final Boolean sslVerification, final SSLContext sslContext) {
final var client = HttpClient.newBuilder()
//Stick to HTTP/1.1 by default, coz Vault Agent fails proxying h2c request to https
.version(Version.HTTP_1_1);
diff --git a/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java b/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java
index 777b9ae2..c36dc9ab 100644
--- a/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java
@@ -1,8 +1,8 @@
package io.github.jopenlibs.vault;
import io.github.jopenlibs.vault.api.Logical;
-import io.github.jopenlibs.vault.response.LogicalResponse;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.LogicalResponse;
import java.net.Socket;
import java.net.http.HttpClient;
import java.nio.ByteBuffer;
@@ -72,6 +72,29 @@ public void readShouldReuseConnectionAfterSuccessfulRequestByHttp() throws Excep
assertTrue("Too many connections opened: " + opened, opened <= (closed + 1));
}
+ @Test
+ public void readShouldReuseConnectionWithDefaultHttpClient() throws Exception {
+ int readNum = 10;
+
+ Logical vault = Vault.create(new VaultConfig()
+ .address("http://localhost:8999")
+ .token(TOKEN)
+ .openTimeout(10)
+ .readTimeout(10)
+ .engineVersion(1)
+ .build()).logical();
+
+ for (int i = 0; i < readNum; i++) {
+ LogicalResponse resp = vault.read("testing/p1");
+ assertEquals("value", resp.getData().get("key"));
+ }
+
+ int closed = connectionListener.getClosed();
+ int opened = connectionListener.getOpened();
+
+ assertTrue("Too many connections opened: " + opened, opened <= (closed + 1));
+ }
+
private static class NetworkConnectionListener implements NetworkTrafficListener {
private final AtomicInteger opened = new AtomicInteger();
@@ -109,7 +132,8 @@ public void reset() {
}
}
- public Server initHttpMockVaultWithListener(final MockVault mock, NetworkTrafficListener listener) {
+ public Server initHttpMockVaultWithListener(final MockVault mock,
+ NetworkTrafficListener listener) {
final var server = new Server();
final var http = new HttpConfiguration();
From 67566e702af4d5a02d5a72748ea0a6e84fb9c1c6 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 22:43:07 +0200
Subject: [PATCH 02/34] Returned response at same mode as returned from remote
resource
---
.../io/github/jopenlibs/vault/rest/Rest.java | 7 ++-
.../vault/mock/BinaryBodyMockVault.java | 34 +++++++++++++
.../vault/rest/ResponseBodyTests.java | 51 +++++++++++++++++++
3 files changed, 88 insertions(+), 4 deletions(-)
create mode 100644 src/test/java/io/github/jopenlibs/vault/mock/BinaryBodyMockVault.java
create mode 100644 src/test/java/io/github/jopenlibs/vault/rest/ResponseBodyTests.java
diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
index 0cc22a87..acfb1203 100644
--- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
+++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
@@ -445,16 +445,15 @@ private String parametersToQueryString() {
*/
private RestResponse send(final HttpRequest req) throws IOException, InterruptedException {
final var client = getClient();
- final var response = client.send(req, BodyHandlers.ofString());
+ final var response = client.send(req, BodyHandlers.ofByteArray());
// Get the resulting status code
final var statusCode = response.statusCode();
- // Download and parse response
+ // Keep the raw response bytes, decoding is left to the caller
final var mimeType = response.headers().firstValue("Content-Type").orElse("");
- final var body = response.body().getBytes(StandardCharsets.UTF_8);
- return new RestResponse(statusCode, mimeType, body);
+ return new RestResponse(statusCode, mimeType, response.body());
}
private HttpClient getClient() {
diff --git a/src/test/java/io/github/jopenlibs/vault/mock/BinaryBodyMockVault.java b/src/test/java/io/github/jopenlibs/vault/mock/BinaryBodyMockVault.java
new file mode 100644
index 00000000..77d4ade6
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/mock/BinaryBodyMockVault.java
@@ -0,0 +1,34 @@
+package io.github.jopenlibs.vault.mock;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * This class is used to mock out a Vault server in unit tests verifying that response bodies
+ * are passed through byte for byte. It responds to every request with HTTP 200 and the
+ * pre-determined bytes, sent as application/octet-stream.
+ */
+public class BinaryBodyMockVault extends MockVault {
+
+ private final byte[] mockBody;
+
+ public BinaryBodyMockVault(final byte[] mockBody) {
+ this.mockBody = Arrays.copyOf(mockBody, mockBody.length);
+ }
+
+ @Override
+ public void handle(
+ final String target,
+ final Request baseRequest,
+ final HttpServletRequest request,
+ final HttpServletResponse response
+ ) throws IOException {
+ response.setContentType("application/octet-stream");
+ response.setStatus(200);
+ baseRequest.setHandled(true);
+ response.getOutputStream().write(mockBody);
+ }
+}
diff --git a/src/test/java/io/github/jopenlibs/vault/rest/ResponseBodyTests.java b/src/test/java/io/github/jopenlibs/vault/rest/ResponseBodyTests.java
new file mode 100644
index 00000000..a8bc43b0
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/rest/ResponseBodyTests.java
@@ -0,0 +1,51 @@
+package io.github.jopenlibs.vault.rest;
+
+import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.mock.BinaryBodyMockVault;
+import org.eclipse.jetty.server.Server;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests verifying that the REST client returns response bodies unaltered.
+ */
+public class ResponseBodyTests {
+
+ private static final byte[] BINARY_BODY = new byte[256];
+
+ static {
+ for (int i = 0; i < BINARY_BODY.length; i++) {
+ BINARY_BODY[i] = (byte) i;
+ }
+ }
+
+ private Server server;
+
+ @Before
+ public void startServer() throws Exception {
+ this.server = VaultTestUtils.initHttpMockVault(new BinaryBodyMockVault(BINARY_BODY));
+ this.server.start();
+ }
+
+ @After
+ public void stopServer() throws Exception {
+ VaultTestUtils.shutdownMockVault(this.server);
+ }
+
+ /**
+ * A body that is not valid UTF-8 (e.g. a DER certificate) must be returned byte for byte.
+ */
+ @Test
+ public void testBinaryBodyIsNotAltered() throws RestException {
+ final var restResponse = new Rest()
+ .url("http://127.0.0.1:8999/")
+ .get();
+ assertEquals(200, restResponse.getStatus());
+ assertEquals("application/octet-stream", restResponse.getMimeType());
+ assertArrayEquals(BINARY_BODY, restResponse.getBody());
+ }
+}
From 068dc2079e8820a48d71511a775b809506763370 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:04:07 +0200
Subject: [PATCH 03/34] Checked if an operation is retryable
---
.../jopenlibs/vault/api/OperationsBase.java | 34 ++++-
.../vault/api/OperationsBaseRetryTests.java | 118 ++++++++++++++++++
2 files changed, 147 insertions(+), 5 deletions(-)
create mode 100644 src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java
diff --git a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
index 17ec8d7d..5a0b9f66 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
@@ -3,6 +3,8 @@
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.rest.Rest;
+import io.github.jopenlibs.vault.rest.RestException;
+import java.io.IOException;
/**
@@ -21,7 +23,9 @@ protected T retry(final EndpointOperation op) throws VaultException {
}
/**
- *
+ * Runs an operation, retrying it up to retryCount times when it fails with a
+ * transient error (see {@link #isRetryable(Exception)}). Any other error, or an interrupt
+ * while waiting between attempts, ends the retries and the last error is thrown.
*/
static T retry(final EndpointOperation op, int retryCount, long retryIntervalMs)
throws VaultException {
@@ -32,10 +36,8 @@ static T retry(final EndpointOperation op, int retryCount, long retryInte
return op.run(attempt);
} catch (final Exception e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
- if (attempt < retryCount) {
+ if (attempt < retryCount && isRetryable(e) && sleep(retryIntervalMs)) {
attempt++;
-
- sleep(retryIntervalMs);
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
@@ -62,11 +64,33 @@ public interface EndpointOperation {
T run(int attempt) throws Exception;
}
- private static void sleep(long delay) {
+ /**
+ * Tells whether a failed operation may succeed if run again: I/O errors (e.g. connection
+ * refused, timeout), and HTTP 5xx, 408, 412 and 429 responses. Other 4xx responses, invalid
+ * arguments and unexpected payloads fail the same way on every attempt.
+ */
+ static boolean isRetryable(final Exception e) {
+ if (e instanceof VaultException) {
+ final int status = ((VaultException) e).getHttpStatusCode();
+ return status >= 500 || status == 408 || status == 412 || status == 429;
+ }
+ if (e instanceof RestException) {
+ return e.getCause() instanceof IOException;
+ }
+ return e instanceof IOException;
+ }
+
+ /**
+ * @return false if the thread was interrupted while sleeping, in which case the
+ * interrupt flag is restored
+ */
+ private static boolean sleep(long delay) {
try {
Thread.sleep(delay);
+ return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
+ return false;
}
}
diff --git a/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java b/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java
new file mode 100644
index 00000000..e339f8a8
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java
@@ -0,0 +1,118 @@
+package io.github.jopenlibs.vault.api;
+
+import io.github.jopenlibs.vault.VaultException;
+import io.github.jopenlibs.vault.rest.Rest;
+import io.github.jopenlibs.vault.rest.RestException;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit tests for the retry logic shared by all API operations.
+ */
+public class OperationsBaseRetryTests {
+
+ @After
+ public void clearInterrupt() {
+ Thread.interrupted();
+ }
+
+ @Test
+ public void testTransientHttpStatusesAreRetryable() {
+ for (final int status : new int[]{408, 412, 429, 500, 502, 503}) {
+ assertTrue("HTTP " + status,
+ OperationsBase.isRetryable(new VaultException("error", status)));
+ }
+ }
+
+ @Test
+ public void testPermanentHttpStatusesAreNotRetryable() {
+ for (final int status : new int[]{200, 204, 400, 401, 403, 404, 405}) {
+ assertFalse("HTTP " + status,
+ OperationsBase.isRetryable(new VaultException("error", status)));
+ }
+ }
+
+ @Test
+ public void testIoErrorsAreRetryable() {
+ assertTrue(OperationsBase.isRetryable(connectionRefused()));
+ assertTrue(OperationsBase.isRetryable(new IOException("reset")));
+ }
+
+ @Test
+ public void testOtherErrorsAreNotRetryable() throws Exception {
+ assertFalse(OperationsBase.isRetryable(new VaultException("MountPayload is missing")));
+ assertFalse(OperationsBase.isRetryable(new RuntimeException("unexpected payload")));
+ assertFalse(OperationsBase.isRetryable(new IllegalArgumentException()));
+ try {
+ new Rest().get();
+ fail("Expected RestException for missing URL");
+ } catch (RestException e) {
+ assertFalse(OperationsBase.isRetryable(e));
+ }
+ }
+
+ @Test
+ public void testRetriesTransientErrorUntilSuccess() throws Exception {
+ final var calls = new AtomicInteger();
+ final String result = OperationsBase.retry(attempt -> {
+ if (calls.incrementAndGet() < 3) {
+ throw new VaultException("unavailable", 503);
+ }
+ return "ok:" + attempt;
+ }, 5, 1);
+
+ assertEquals("ok:2", result);
+ assertEquals(3, calls.get());
+ }
+
+ @Test
+ public void testDoesNotRetryPermanentError() {
+ final var calls = new AtomicInteger();
+ final var error = new VaultException("permission denied", 403);
+ try {
+ OperationsBase.retry(attempt -> {
+ calls.incrementAndGet();
+ throw error;
+ }, 5, 1);
+ fail("Expected VaultException");
+ } catch (VaultException e) {
+ assertSame(error, e);
+ }
+ assertEquals(1, calls.get());
+ }
+
+ @Test
+ public void testStopsRetryingWhenInterrupted() {
+ final var calls = new AtomicInteger();
+ final var error = new VaultException("unavailable", 503);
+ Thread.currentThread().interrupt();
+ try {
+ OperationsBase.retry(attempt -> {
+ calls.incrementAndGet();
+ throw error;
+ }, 5, 10_000);
+ fail("Expected VaultException");
+ } catch (VaultException e) {
+ assertSame(error, e);
+ }
+ assertEquals(1, calls.get());
+ assertTrue(Thread.currentThread().isInterrupted());
+ }
+
+ private static RestException connectionRefused() {
+ try {
+ new Rest().url("http://127.0.0.1:1/").connectTimeoutSeconds(1).get();
+ } catch (RestException e) {
+ return e;
+ }
+ throw new AssertionError("Expected connection to port 1 to fail");
+ }
+}
From 99002eb5eef6a1daa4120c44efe8a68b38042054 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:08:03 +0200
Subject: [PATCH 04/34] Fixed CI
---
.github/workflows/ci.yml | 4 +++-
.github/workflows/test-report.yml | 11 +++++++----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fc8fe83b..6a0dd527 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,4 +64,6 @@ jobs:
with:
overwrite: true
name: test-results-${{ matrix.os }}-${{ matrix.vault }}
- path: build/test-results/
+ path: |
+ target/surefire-reports/TEST-*.xml
+ target/failsafe-reports/TEST-*.xml
diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml
index f0eda16e..d208e7e6 100644
--- a/.github/workflows/test-report.yml
+++ b/.github/workflows/test-report.yml
@@ -4,16 +4,19 @@ on:
workflows: [ 'CI' ] # runs after CI workflow
types:
- completed
+permissions:
+ contents: read
+ actions: read # download artifacts of the CI run
+ checks: write # publish the report as a check run
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: dorny/test-reporter@v1
with:
- artifact: test-results
- name: Tests Report
- path: '**/reports/*.*'
+ artifact: /test-results-(.*)/ # one report per CI matrix entry
+ name: Tests Report $1
+ path: '**/TEST-*.xml'
reporter: java-junit
list-suites: 'all'
list-tests: 'all'
-
From fd9514eb33c198fed93e9aa77ddd8ee45b76bbfd Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:14:28 +0200
Subject: [PATCH 05/34] Removed Travis CI
---
.travis.yml | 27 ---------------------------
1 file changed, 27 deletions(-)
delete mode 100644 .travis.yml
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 1c5058e3..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-language: java
-
-dist: xenial
-
-services:
- - docker
-
-before_cache:
- - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
-
-cache:
- directories:
- - $HOME/.gradle/caches/
- - $HOME/.gradle/wrapper/
-
-matrix:
- include:
- - jdk: openjdk12
- - jdk: openjdk11
- - jdk: openjdk9
-
-script:
- - ./gradlew build integrationTest -s --info
-
-notifications:
- email: false
From cbdb991a4f17328fdd1493fdf39a58330387eb12 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:15:26 +0200
Subject: [PATCH 06/34] Pinned versions
---
pom.xml | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 903b8889..d7923178 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,7 +111,7 @@
org.slf4j
slf4j-simple
- 2.0.5
+ 2.0.7
test
@@ -120,7 +120,9 @@
+ org.apache.maven.plugins
maven-compiler-plugin
+ 3.13.0
11
@@ -128,7 +130,9 @@
+ org.apache.maven.plugins
maven-source-plugin
+ 3.4.0
attach-sources
@@ -140,7 +144,9 @@
+ org.apache.maven.plugins
maven-javadoc-plugin
+ 3.12.0
attach-javadocs
@@ -161,7 +167,6 @@
maven-surefire-plugin
3.2.5
- ${project.basedir}/test
**/*Test.java
**/*Tests.java
From 20028d8fe09a8025dd9c4c881b11028ff3e25570 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:18:41 +0200
Subject: [PATCH 07/34] Pinned build plugins versions in pom.xml
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index d7923178..d3e31d1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -232,7 +232,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.6.3
+ 3.12.0
attach-javadocs
From cd4a1da94e54aa973bda009ae7942729d8f05a68 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:18:51 +0200
Subject: [PATCH 08/34] Removed empty section in README
---
README.md | 3 ---
1 file changed, 3 deletions(-)
diff --git a/README.md b/README.md
index 483e2e51..7efaac5b 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,6 @@ most commonly used Java clients for Hashicorp, but has had no activity or releas
December 2019. The project is not
maintaining [by author](https://github.com/BetterCloud/vault-java-driver/pull/245#issuecomment-954066376).
-This Change
------------
-
Table of Contents
-----------------
From 8f8ade5008770f7ae03d8f11e91360119e767288 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:19:01 +0200
Subject: [PATCH 09/34] Used java.util.logging instead of System.err
---
.../github/jopenlibs/vault/VaultConfig.java | 48 +++++++++++--------
.../io/github/jopenlibs/vault/VaultImpl.java | 29 +++++------
2 files changed, 41 insertions(+), 36 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index f62d55ea..68919fa9 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -6,6 +6,7 @@
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
/**
* A container for the configuration settings needed to initialize a Vault driver
@@ -34,6 +35,8 @@ public class VaultConfig implements Serializable {
private static final String VAULT_OPEN_TIMEOUT = "VAULT_OPEN_TIMEOUT";
private static final String VAULT_READ_TIMEOUT = "VAULT_READ_TIMEOUT";
+ private static final Logger LOGGER = Logger.getLogger(VaultConfig.class.getCanonicalName());
+
private Map secretsEnginePathMap = new ConcurrentHashMap<>();
private String address;
private char[] token;
@@ -369,27 +372,11 @@ public VaultConfig build() throws VaultException {
if (this.token == null && envVaultToken != null) {
this.token = envVaultToken.toCharArray();
}
- if (this.openTimeout == null
- && environmentLoader.loadVariable(VAULT_OPEN_TIMEOUT) != null) {
- try {
- this.openTimeout = Integer.valueOf(
- environmentLoader.loadVariable(VAULT_OPEN_TIMEOUT));
- } catch (NumberFormatException e) {
- System.err.printf("The " + VAULT_OPEN_TIMEOUT
- + " environment variable contains value \"%s\", which cannot be parsed as an integer timeout period.%n",
- environmentLoader.loadVariable(VAULT_OPEN_TIMEOUT));
- }
+ if (this.openTimeout == null) {
+ this.openTimeout = loadTimeoutVariable(VAULT_OPEN_TIMEOUT);
}
- if (this.readTimeout == null
- && environmentLoader.loadVariable(VAULT_READ_TIMEOUT) != null) {
- try {
- this.readTimeout = Integer.valueOf(
- environmentLoader.loadVariable(VAULT_READ_TIMEOUT));
- } catch (NumberFormatException e) {
- System.err.printf("The " + VAULT_READ_TIMEOUT
- + " environment variable contains value \"%s\", which cannot be parsed as an integer timeout period.%n",
- environmentLoader.loadVariable(VAULT_READ_TIMEOUT));
- }
+ if (this.readTimeout == null) {
+ this.readTimeout = loadTimeoutVariable(VAULT_READ_TIMEOUT);
}
if (this.sslConfig == null) {
this.sslConfig = new SslConfig().environmentLoader(this.environmentLoader).build();
@@ -398,6 +385,27 @@ public VaultConfig build() throws VaultException {
return this;
}
+ /**
+ * Reads a timeout period from an environment variable.
+ *
+ * @param name The environment variable name
+ * @return The timeout, or null if the variable is unset or not an integer
+ */
+ private Integer loadTimeoutVariable(final String name) {
+ final String value = environmentLoader.loadVariable(name);
+ if (value == null) {
+ return null;
+ }
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException e) {
+ LOGGER.warning(String.format(
+ "The %s environment variable contains value \"%s\", which cannot be parsed as an integer timeout period.",
+ name, value));
+ return null;
+ }
+ }
+
public Map getSecretsEnginePathMap() {
return Collections.unmodifiableMap(secretsEnginePathMap);
}
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index eab96b9a..883b3da6 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -69,11 +69,7 @@ public class VaultImpl implements Vault {
*/
public VaultImpl(final VaultConfig vaultConfig) {
this.vaultConfig = vaultConfig;
- if (this.vaultConfig.getNameSpace() != null && !this.vaultConfig.getNameSpace().isEmpty()) {
- logger.info(String.format(
- "The NameSpace %s has been bound to this Vault instance. Please keep this in mind when running operations.",
- this.vaultConfig.getNameSpace()));
- }
+ logNameSpace();
if (this.vaultConfig.getSecretsEnginePathMap().isEmpty()
&& this.vaultConfig.getGlobalEngineVersion() == null) {
logger.info(
@@ -99,11 +95,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Integer engineVersion) {
}
vaultConfig.setEngineVersion(engineVersion);
this.vaultConfig = vaultConfig;
- if (this.vaultConfig.getNameSpace() != null && !this.vaultConfig.getNameSpace().isEmpty()) {
- logger.info(String.format(
- "The Namespace %s has been bound to this Vault instance. Please keep this in mind when running operations.",
- this.vaultConfig.getNameSpace()));
- }
+ logNameSpace();
}
/**
@@ -123,11 +115,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
final Integer globalFallbackVersion)
throws VaultException {
this.vaultConfig = vaultConfig;
- if (this.vaultConfig.getNameSpace() != null && !this.vaultConfig.getNameSpace().isEmpty()) {
- logger.info(String.format(
- "The Namespace %s has been bound to this Vault instance. Please keep this in mind when running operations.",
- this.vaultConfig.getNameSpace()));
- }
+ logNameSpace();
this.vaultConfig.setEngineVersion(globalFallbackVersion);
if (useSecretsEnginePathMap && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
try {
@@ -146,6 +134,15 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
}
}
+ private void logNameSpace() {
+ final String nameSpace = this.vaultConfig.getNameSpace();
+ if (nameSpace != null && !nameSpace.isEmpty()) {
+ logger.info(String.format(
+ "The Namespace %s has been bound to this Vault instance. Please keep this in mind when running operations.",
+ nameSpace));
+ }
+ }
+
/**
* This method is chained ahead of endpoints (e.g. logical(), auth(),
* etc... to specify retry rules for any API operations invoked on that endpoint.
@@ -337,7 +334,7 @@ private Map collectSecretEngineVersions() {
}
return data;
} catch (RestException e) {
- System.err.print(
+ logger.warning(
String.format("Unable to retrieve the KV Engine secrets, due to exception: %s",
e.getMessage()));
return null;
From edbc0259c84c42d2c2a748085529db8cc3f1aabe Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:39:16 +0200
Subject: [PATCH 10/34] Enforced null checks
---
.../io/github/jopenlibs/vault/VaultImpl.java | 4 +-
.../io/github/jopenlibs/vault/api/Auth.java | 4 +-
.../io/github/jopenlibs/vault/api/Debug.java | 4 +-
.../github/jopenlibs/vault/api/Logical.java | 10 +--
.../vault/api/database/Database.java | 4 +-
.../github/jopenlibs/vault/api/pki/Pki.java | 4 +-
.../jopenlibs/vault/api/sys/Leases.java | 4 +-
.../github/jopenlibs/vault/api/sys/Seal.java | 4 +-
.../github/jopenlibs/vault/api/sys/Sys.java | 4 +-
.../jopenlibs/vault/api/sys/Wrapping.java | 4 +-
.../vault/response/AuthResponse.java | 24 ++++---
.../vault/response/LookupResponse.java | 24 ++++---
.../vault/response/UnwrapResponse.java | 12 +++-
.../vault/response/VaultResponse.java | 8 +++
.../vault/response/WrapResponse.java | 10 ++-
.../io/github/jopenlibs/vault/VaultTests.java | 22 +++++++
.../response/ResponseNullHandlingTests.java | 64 +++++++++++++++++++
17 files changed, 154 insertions(+), 56 deletions(-)
create mode 100644 src/test/java/io/github/jopenlibs/vault/response/ResponseNullHandlingTests.java
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 883b3da6..54331bb4 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -88,7 +88,7 @@ public VaultImpl(final VaultConfig vaultConfig) {
* 2)
*/
public VaultImpl(final VaultConfig vaultConfig, final Integer engineVersion) {
- if (engineVersion < 1 || engineVersion > 2) {
+ if (engineVersion == null || engineVersion < 1 || engineVersion > 2) {
throw new IllegalArgumentException(
"The Engine version must be '1' or '2', the version supplied was: '"
+ engineVersion + "'.");
@@ -117,7 +117,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
this.vaultConfig = vaultConfig;
logNameSpace();
this.vaultConfig.setEngineVersion(globalFallbackVersion);
- if (useSecretsEnginePathMap && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
+ if (Boolean.TRUE.equals(useSecretsEnginePathMap) && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
try {
logger.info(
"No secrets Engine version map was supplied, attempting to generate one.");
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
index d84fcc1a..9b2e276a 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
@@ -256,9 +256,7 @@ public String getEntityAlias() {
public Auth(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
public Auth withNameSpace(final String nameSpace) {
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Debug.java b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
index 2422595e..7a7c48e5 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Debug.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
@@ -23,9 +23,7 @@ public class Debug extends OperationsBase {
public Debug(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
public Debug withNameSpace(final String nameSpace) {
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 70507ee3..04f724bd 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -10,6 +10,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
+import java.util.Objects;
import static io.github.jopenlibs.vault.api.LogicalUtilities.adjustPathForDelete;
import static io.github.jopenlibs.vault.api.LogicalUtilities.adjustPathForList;
@@ -38,9 +39,7 @@ public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, list
public Logical(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
/**
@@ -633,7 +632,8 @@ public LogicalResponse upgrade(final String kvPath) throws VaultException {
*
*
* @param secretPath The Vault secret path to check (e.g. secret/hello).
- * @return the detected engine version (1 or 2), or the global default if not found
+ * @return the detected engine version (1 or 2), or the global default if not found, or 2 if
+ * no global default is set
*/
private Integer engineVersionForSecretPath(final String secretPath) {
final var pathMap = this.config.getSecretsEnginePathMap();
@@ -648,7 +648,7 @@ private Integer engineVersionForSecretPath(final String secretPath) {
idx = prefix.lastIndexOf('/');
} while (idx != -1);
}
- return this.config.getGlobalEngineVersion();
+ return Objects.requireNonNullElse(this.config.getGlobalEngineVersion(), 2);
}
/**
diff --git a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
index f7188d96..e4475806 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
@@ -52,9 +52,7 @@ public Database(final VaultConfig config, final String mountPath) {
super(config);
this.mountPath = mountPath;
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
/**
diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
index 6da24214..8b818b5e 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
@@ -53,9 +53,7 @@ public Pki(final VaultConfig config, final String mountPath) {
super(config);
this.mountPath = mountPath;
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
/**
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
index 8265f964..76b99549 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
@@ -25,9 +25,7 @@ public class Leases extends OperationsBase {
public Leases(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
public Leases withNameSpace(final String nameSpace) {
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
index 129cce03..bf3fbb96 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
@@ -25,9 +25,7 @@ public class Seal extends OperationsBase {
public Seal(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
public Seal withNameSpace(final String nameSpace) {
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
index f18f5317..98011247 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
@@ -21,9 +21,7 @@ public class Sys extends OperationsBase {
public Sys(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
public Sys withNameSpace(final String nameSpace) {
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
index 11d35920..9f3051b6 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
@@ -29,9 +29,7 @@ public class Wrapping extends OperationsBase {
public Wrapping(final VaultConfig config) {
super(config);
- if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) {
- this.nameSpace = this.config.getNameSpace();
- }
+ this.nameSpace = this.config.getNameSpace();
}
/**
diff --git a/src/main/java/io/github/jopenlibs/vault/response/AuthResponse.java b/src/main/java/io/github/jopenlibs/vault/response/AuthResponse.java
index c1c69486..354dc377 100644
--- a/src/main/java/io/github/jopenlibs/vault/response/AuthResponse.java
+++ b/src/main/java/io/github/jopenlibs/vault/response/AuthResponse.java
@@ -40,15 +40,14 @@ public AuthResponse(final RestResponse restResponse, final int retries) {
final var responseJson = new String(restResponse.getBody(), StandardCharsets.UTF_8);
jsonResponse = Json.parse(responseJson).asObject();
final var authJsonVal = jsonResponse.get("auth");
- final var authJsonObject =
- authJsonVal != null && !authJsonVal.isNull() ? authJsonVal.asObject() : null;
+ final var authJsonObject = hasValue(authJsonVal) ? authJsonVal.asObject() : null;
if (authJsonObject != null) {
authLeaseDuration = authJsonObject.getInt("lease_duration", 0);
authRenewable = authJsonObject.getBoolean("renewable", false);
- if (authJsonObject.get("metadata") != null && !authJsonObject.get("metadata")
- .toString().equalsIgnoreCase("null")) {
- final var metadata = authJsonObject.get("metadata").asObject();
+ final var metadataJsonVal = authJsonObject.get("metadata");
+ if (hasValue(metadataJsonVal)) {
+ final var metadata = metadataJsonVal.asObject();
appId = metadata.getString("app-id", "");
userId = metadata.getString("user-id", "");
username = metadata.getString("username", "");
@@ -58,13 +57,18 @@ public AuthResponse(final RestResponse restResponse, final int retries) {
authClientToken = authJsonObject.getString("client_token", "");
tokenAccessor = authJsonObject.getString("accessor", "");
- final var authPoliciesJsonArray = authJsonObject.get("policies").asArray();
- authPolicies = authPoliciesJsonArray.values().stream()
- .map(JsonValue::asString)
- .collect(Collectors.toUnmodifiableList());
+ final var authPoliciesJsonVal = authJsonObject.get("policies");
+ if (hasValue(authPoliciesJsonVal)) {
+ authPolicies = authPoliciesJsonVal.asArray().values().stream()
+ .map(JsonValue::asString)
+ .collect(Collectors.toUnmodifiableList());
+ }
}
- renewable = jsonResponse.get("renewable").asBoolean();
+ final var renewableJsonVal = jsonResponse.get("renewable");
+ if (hasValue(renewableJsonVal)) {
+ renewable = renewableJsonVal.asBoolean();
+ }
} catch (ParseException e) {
// No-op.
}
diff --git a/src/main/java/io/github/jopenlibs/vault/response/LookupResponse.java b/src/main/java/io/github/jopenlibs/vault/response/LookupResponse.java
index 5c86a7d7..8006d210 100644
--- a/src/main/java/io/github/jopenlibs/vault/response/LookupResponse.java
+++ b/src/main/java/io/github/jopenlibs/vault/response/LookupResponse.java
@@ -41,7 +41,11 @@ public LookupResponse(final RestResponse restResponse, final int retries) {
try {
final var responseJson = new String(restResponse.getBody(), StandardCharsets.UTF_8);
final var jsonObject = Json.parse(responseJson).asObject();
- final var dataJsonObject = jsonObject.get("data").asObject();
+ final var dataJsonVal = jsonObject.get("data");
+ if (!hasValue(dataJsonVal)) {
+ return;
+ }
+ final var dataJsonObject = dataJsonVal.asObject();
accessor = dataJsonObject.getString("accessor", "");
creationTime = dataJsonObject.getLong("creation_time", 0);
@@ -50,21 +54,23 @@ public LookupResponse(final RestResponse restResponse, final int retries) {
explicitMaxTTL = dataJsonObject.getLong("explicit_max_ttl", 0);
id = dataJsonObject.getString("id", "");
final var lastRenewalTimeJsonValue = dataJsonObject.get("last_renewal_time");
- if (lastRenewalTimeJsonValue != null) {
+ if (hasValue(lastRenewalTimeJsonValue)) {
lastRenewalTime = lastRenewalTimeJsonValue.asLong();
}
- if (dataJsonObject.get("metadata") != null && !dataJsonObject.get("metadata").toString()
- .equalsIgnoreCase("null")) {
- final var metadata = dataJsonObject.get("metadata").asObject();
+ final var metadataJsonVal = dataJsonObject.get("metadata");
+ if (hasValue(metadataJsonVal)) {
+ final var metadata = metadataJsonVal.asObject();
username = metadata.getString("username", "");
}
numUses = dataJsonObject.getInt("num_uses", 0);
orphan = dataJsonObject.getBoolean("orphan", true);
path = dataJsonObject.getString("path", "");
- final var policiesJsonArray = dataJsonObject.get("policies").asArray();
- policies = policiesJsonArray.values().stream()
- .map(JsonValue::asString)
- .collect(Collectors.toUnmodifiableList());
+ final var policiesJsonVal = dataJsonObject.get("policies");
+ if (hasValue(policiesJsonVal)) {
+ policies = policiesJsonVal.asArray().values().stream()
+ .map(JsonValue::asString)
+ .collect(Collectors.toUnmodifiableList());
+ }
renewable = dataJsonObject.getBoolean("renewable", false);
ttl = dataJsonObject.getLong("ttl", 0);
diff --git a/src/main/java/io/github/jopenlibs/vault/response/UnwrapResponse.java b/src/main/java/io/github/jopenlibs/vault/response/UnwrapResponse.java
index daa21fef..9cc69f00 100644
--- a/src/main/java/io/github/jopenlibs/vault/response/UnwrapResponse.java
+++ b/src/main/java/io/github/jopenlibs/vault/response/UnwrapResponse.java
@@ -18,9 +18,15 @@ public UnwrapResponse(final RestResponse restResponse, final int retries) {
super(restResponse, retries);
}
+ /**
+ * @return The data object of the unwrapped response, or null if it
+ * has none
+ */
public JsonObject getData() {
- assert jsonResponse.get("data").isObject();
-
- return jsonResponse.get("data").asObject();
+ if (jsonResponse == null) {
+ return null;
+ }
+ final var data = jsonResponse.get("data");
+ return data != null && data.isObject() ? data.asObject() : null;
}
}
diff --git a/src/main/java/io/github/jopenlibs/vault/response/VaultResponse.java b/src/main/java/io/github/jopenlibs/vault/response/VaultResponse.java
index c8524c47..bfcbc16e 100644
--- a/src/main/java/io/github/jopenlibs/vault/response/VaultResponse.java
+++ b/src/main/java/io/github/jopenlibs/vault/response/VaultResponse.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.response;
+import io.github.jopenlibs.vault.json.JsonValue;
import io.github.jopenlibs.vault.rest.RestResponse;
import java.io.Serializable;
@@ -30,4 +31,11 @@ public RestResponse getRestResponse() {
public int getRetries() {
return retries;
}
+
+ /**
+ * @return true if a JSON field is present and not null
+ */
+ static boolean hasValue(final JsonValue value) {
+ return value != null && !value.isNull();
+ }
}
diff --git a/src/main/java/io/github/jopenlibs/vault/response/WrapResponse.java b/src/main/java/io/github/jopenlibs/vault/response/WrapResponse.java
index 053832c2..dcf5a06b 100644
--- a/src/main/java/io/github/jopenlibs/vault/response/WrapResponse.java
+++ b/src/main/java/io/github/jopenlibs/vault/response/WrapResponse.java
@@ -31,16 +31,20 @@ public WrapResponse(final RestResponse restResponse, final int retries) {
final var responseJson = new String(restResponse.getBody(), StandardCharsets.UTF_8);
final var jsonResponse = Json.parse(responseJson).asObject();
final var wrapInfoJsonVal = jsonResponse.get("wrap_info");
- if (wrapInfoJsonVal != null && !wrapInfoJsonVal.isNull()) {
+ if (hasValue(wrapInfoJsonVal)) {
final var wrapInfoJsonObject = wrapInfoJsonVal.asObject();
- token = wrapInfoJsonObject.getString("token", null).toCharArray();
+ final var tokenString = wrapInfoJsonObject.getString("token", null);
+ token = tokenString == null ? null : tokenString.toCharArray();
accessor = wrapInfoJsonObject.getString("accessor", null);
ttl = wrapInfoJsonObject.getInt("ttl", 0);
creationTime = wrapInfoJsonObject.getString("creation_time", null);
creationPath = wrapInfoJsonObject.getString("creation_path", null);
}
- renewable = jsonResponse.get("renewable").asBoolean();
+ final var renewableJsonVal = jsonResponse.get("renewable");
+ if (hasValue(renewableJsonVal)) {
+ renewable = renewableJsonVal.asBoolean();
+ }
} catch (ParseException e) {
// No-op.
}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultTests.java b/src/test/java/io/github/jopenlibs/vault/VaultTests.java
index 16694db4..299b3a9b 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultTests.java
@@ -11,6 +11,28 @@
*/
public class VaultTests {
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullEngineVersionIsRejected() throws VaultException {
+ final var config = new VaultConfig().address("http://127.0.0.1:8200").build();
+ Vault.create(config, (Integer) null);
+ }
+
+ @Test
+ public void testNullUseSecretsEnginePathMapIsFalse() throws VaultException {
+ final var config = new VaultConfig().address("http://127.0.0.1:8200").build();
+ final var vault = Vault.create(config, null, 1);
+ Assert.assertEquals(Integer.valueOf(1),
+ vault.logical().getEngineVersionForSecretPath("secret/hello"));
+ }
+
+ @Test
+ public void testUnsetEngineVersionDefaultsTo2() throws VaultException {
+ final var config = new VaultConfig().address("http://127.0.0.1:8200").build();
+ final var vault = Vault.create(config, false, null);
+ Assert.assertEquals(Integer.valueOf(2),
+ vault.logical().getEngineVersionForSecretPath("secret/hello"));
+ }
+
@Test
public void testDefaultVaultConstructor() {
var vaultConfig = new VaultConfig();
diff --git a/src/test/java/io/github/jopenlibs/vault/response/ResponseNullHandlingTests.java b/src/test/java/io/github/jopenlibs/vault/response/ResponseNullHandlingTests.java
new file mode 100644
index 00000000..48b59edd
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/response/ResponseNullHandlingTests.java
@@ -0,0 +1,64 @@
+package io.github.jopenlibs.vault.response;
+
+import io.github.jopenlibs.vault.rest.RestResponse;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests verifying that response classes tolerate missing and null JSON fields.
+ */
+public class ResponseNullHandlingTests {
+
+ private static RestResponse json(final String body) {
+ return new RestResponse(200, "application/json", body.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testAuthResponseWithoutPoliciesOrRenewable() {
+ final var response = new AuthResponse(
+ json("{\"auth\":{\"client_token\":\"token\",\"metadata\":null}}"), 0);
+ assertEquals("token", response.getAuthClientToken());
+ assertNull(response.getAuthPolicies());
+ assertNull(response.getRenewable());
+ }
+
+ @Test
+ public void testAuthResponseWithNullFields() {
+ final var response = new AuthResponse(
+ json("{\"auth\":{\"policies\":null},\"renewable\":null}"), 0);
+ assertNull(response.getAuthPolicies());
+ assertNull(response.getRenewable());
+ }
+
+ @Test
+ public void testWrapResponseWithoutToken() {
+ final var response = new WrapResponse(json("{\"wrap_info\":{\"accessor\":\"acc\"}}"), 0);
+ assertNull(response.getToken());
+ assertEquals("acc", response.getAccessor());
+ assertNull(response.getRenewable());
+ }
+
+ @Test
+ public void testLookupResponseWithNullFields() {
+ final var response = new LookupResponse(
+ json("{\"data\":{\"id\":\"id\",\"last_renewal_time\":null,\"metadata\":null}}"), 0);
+ assertEquals("id", response.getId());
+ assertNull(response.getLastRenewalTime());
+ assertNull(response.getPolicies());
+ }
+
+ @Test
+ public void testLookupResponseWithoutData() {
+ final var response = new LookupResponse(json("{\"errors\":[]}"), 0);
+ assertNull(response.getId());
+ }
+
+ @Test
+ public void testUnwrapResponseWithoutData() {
+ final var response = new UnwrapResponse(json("{\"auth\":{\"client_token\":\"t\"}}"), 0);
+ assertNull(response.getData());
+ }
+}
From cb621b111f4f82c9bc6a8ac73fb6b8bfcc706f46 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 6 Oct 2026 23:44:38 +0200
Subject: [PATCH 11/34] Removed unused calls
---
.../io/github/jopenlibs/vault/VaultImpl.java | 3 --
.../io/github/jopenlibs/vault/api/Auth.java | 39 -------------------
.../io/github/jopenlibs/vault/api/Debug.java | 5 +--
.../github/jopenlibs/vault/api/Logical.java | 24 ------------
.../vault/api/database/Database.java | 15 -------
.../github/jopenlibs/vault/api/pki/Pki.java | 15 -------
.../jopenlibs/vault/api/sys/Leases.java | 12 ------
.../github/jopenlibs/vault/api/sys/Seal.java | 9 -----
.../jopenlibs/vault/api/sys/Wrapping.java | 16 +-------
.../vault/api/sys/mounts/Mounts.java | 15 -------
10 files changed, 3 insertions(+), 150 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 54331bb4..9a7df5f9 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -307,10 +307,7 @@ private Map collectSecretEngineVersions() {
.token(vaultConfig.getToken())
.header("X-Vault-Namespace", this.vaultConfig.getNameSpace())
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(vaultConfig.getOpenTimeout())
.readTimeoutSeconds(vaultConfig.getReadTimeout())
- .sslVerification(vaultConfig.getSslConfig().isVerify())
- .sslContext(vaultConfig.getSslConfig().getSslContext())
.get();
if (restResponse.getStatus() != 200) {
return null;
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
index 9b2e276a..32fc259e 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
@@ -378,10 +378,7 @@ public AuthResponse createToken(final TokenRequest tokenRequest, final String to
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -424,10 +421,7 @@ public AuthResponse loginByAppID(final String path, final String appId, final St
.url(config.getAddress() + "/v1/auth/" + path)
.header("X-Vault-Namespace", this.nameSpace)
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -500,10 +494,7 @@ public AuthResponse loginByAppRole(final String path, final String roleId,
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -565,10 +556,7 @@ public AuthResponse loginByUserPass(final String username, final String password
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -672,10 +660,7 @@ public AuthResponse loginByAwsEc2(final String role, final String identity,
.body(requestJson.getBytes(StandardCharsets.UTF_8))
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -728,10 +713,7 @@ public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final S
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -791,10 +773,7 @@ public AuthResponse loginByAwsIam(final String role, final String iamRequestUrl,
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -852,10 +831,7 @@ public AuthResponse loginByGithub(final String githubToken, final String githubA
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -921,10 +897,7 @@ public AuthResponse loginByJwt(final String provider, final String role, final S
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -1067,10 +1040,7 @@ public AuthResponse loginByCert(final String certAuthMount) throws VaultExceptio
.url(config.getAddress() + "/v1/auth/" + mount + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -1128,10 +1098,7 @@ public AuthResponse renewSelf(final long increment, final String tokenAuthMount)
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -1169,10 +1136,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate restResponse
@@ -1235,10 +1199,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Debug.java b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
index 7a7c48e5..52832f34 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Debug.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
@@ -107,10 +107,7 @@ public HealthResponse health(
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
- .readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext());
+ .readTimeoutSeconds(config.getReadTimeout());
// Add params if present
if (standbyOk != null) {
rest.parameter("standbyok", standbyOk.toString());
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 04f724bd..ca7c91e7 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -91,10 +91,7 @@ private LogicalResponse read(final String path, final logicalOperations operatio
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response - don't treat 4xx class errors as exceptions (except 412), we want to return an error as the response
@@ -151,10 +148,7 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.parameter("version", version.toString())
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response - don't treat 4xx class errors as exceptions (except 412), we want to return an error as the response
@@ -291,10 +285,7 @@ private LogicalResponse write(final String path, final Map nameV
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.header("X-Vault-Wrap-TTL", wrapTTL != null ? wrapTTL.toString() : null)
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// HTTP Status should be either 200 (with content - e.g. PKI write) or 204 (no content)
@@ -400,10 +391,7 @@ private LogicalResponse delete(final String path, final Logical.logicalOperation
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.delete();
// Validate response
@@ -450,10 +438,7 @@ public LogicalResponse delete(final String path, final int[] versions) throws Va
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.body(versionsToDelete.toString().getBytes(StandardCharsets.UTF_8))
.post();
@@ -510,10 +495,7 @@ public LogicalResponse unDelete(final String path, final int[] versions) throws
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.body(versionsToUnDelete.toString().getBytes(StandardCharsets.UTF_8))
.post();
@@ -557,10 +539,7 @@ public LogicalResponse destroy(final String path, final int[] versions) throws V
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.body(versionsToDestroy.toString().getBytes(StandardCharsets.UTF_8))
.post();
@@ -594,10 +573,7 @@ public LogicalResponse upgrade(final String kvPath) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.body(kvToUpgrade.toString().getBytes(StandardCharsets.UTF_8))
.post();
diff --git a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
index e4475806..b58fee17 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
@@ -95,10 +95,7 @@ public DatabaseResponse createOrUpdateRole(final String roleName,
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -140,10 +137,7 @@ public DatabaseResponse getRole(final String roleName) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response
@@ -192,11 +186,8 @@ public DatabaseResponse revoke(final String serialNumber) throws VaultException
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
@@ -238,10 +229,7 @@ public DatabaseResponse deleteRole(final String roleName) throws VaultException
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.delete();
// Validate response
@@ -285,10 +273,7 @@ public DatabaseResponse creds(final String roleName) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response
diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
index 8b818b5e..1a3148e8 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
@@ -121,10 +121,7 @@ public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions o
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -168,10 +165,7 @@ public PkiResponse getRole(final String roleName) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response
@@ -219,11 +213,8 @@ public PkiResponse revoke(final String serialNumber) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
@@ -265,10 +256,7 @@ public PkiResponse deleteRole(final String roleName) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.delete();
// Validate response
@@ -529,10 +517,7 @@ public PkiResponse issue(
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
index 76b99549..0e203dc0 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
@@ -56,10 +56,7 @@ public VaultResponse revoke(final String leaseId) throws VaultException {
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
@@ -96,10 +93,7 @@ public VaultResponse revokePrefix(final String prefix) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
@@ -138,10 +132,7 @@ public VaultResponse revokeForce(final String prefix) throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
@@ -187,10 +178,7 @@ public VaultResponse renew(final String leaseId, final long increment) throws Va
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate response
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
index bf3fbb96..d4bd8e02 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
@@ -47,10 +47,7 @@ public SealResponse seal() throws VaultException {
.token(config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
return getSealResponse(attempt, restResponse, 204);
@@ -88,10 +85,7 @@ public SealResponse unseal(final String key, final Boolean reset) throws VaultEx
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -112,10 +106,7 @@ public SealResponse sealStatus() throws VaultException {
.url(config.getAddress() + "/v1/sys/seal-status")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate restResponse
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
index 9f3051b6..59772bf7 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
@@ -112,10 +112,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
.url(config.getAddress() + "/v1/sys/wrapping/lookup")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
- .readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext());
+ .readTimeoutSeconds(config.getReadTimeout());
if (inBody) {
rest = rest
@@ -216,10 +213,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -385,10 +379,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
.url(url)
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
- .readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext());
+ .readTimeoutSeconds(config.getReadTimeout());
if (inBody) {
final String requestJson = Json.object().add("token", new String(wrappedToken)).toString();
@@ -474,10 +465,7 @@ public WrapResponse rewrap(final char[] wrappedToken) throws VaultException {
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
index 423d89f7..4baf21d5 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
@@ -47,10 +47,7 @@ public MountResponse list() throws VaultException {
.url(String.format("%s/v1/sys/mounts", config.getAddress()))
.token(config.getToken())
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate restResponse
@@ -118,10 +115,7 @@ public MountResponse enable(final String path, final MountType type, final Mount
.token(config.getToken())
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
@@ -167,10 +161,7 @@ public MountResponse disable(final String path) throws VaultException {
.url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path))
.token(config.getToken())
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.delete();
// Validate restResponse
@@ -216,10 +207,7 @@ public MountResponse read(final String path) throws VaultException {
.url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path))
.token(config.getToken())
.header("X-Vault-Request", "true")
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.get();
// Validate restResponse
@@ -281,10 +269,7 @@ public MountResponse tune(final String path, final MountPayload payload) throws
.token(config.getToken())
.header("X-Vault-Request", "true")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
- .connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
- .sslVerification(config.getSslConfig().isVerify())
- .sslContext(config.getSslConfig().getSslContext())
.post();
// Validate restResponse
From caf0980023dacecf315c7914b0ae68bc5e195a03 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 00:29:30 +0200
Subject: [PATCH 12/34] Improved parsing of VAULT_SSL_VERIFY variable
---
.../io/github/jopenlibs/vault/SslConfig.java | 40 +++++++-
.../jopenlibs/vault/VaultConfigTests.java | 94 +++++++++++++++++++
2 files changed, 129 insertions(+), 5 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/SslConfig.java b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
index b60b24b1..c3d5e9cc 100644
--- a/src/main/java/io/github/jopenlibs/vault/SslConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
@@ -27,6 +27,9 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@@ -46,6 +49,8 @@ public class SslConfig implements Serializable {
private static final String VAULT_SSL_VERIFY = "VAULT_SSL_VERIFY";
private static final String VAULT_SSL_CERT = "VAULT_SSL_CERT";
+ private static final List VERIFY_TRUE_VALUES = List.of("t", "true", "1", "y", "yes", "on");
+ private static final Logger LOGGER = Logger.getLogger(SslConfig.class.getCanonicalName());
private boolean verify;
private transient SSLContext sslContext;
@@ -90,7 +95,8 @@ protected SslConfig environmentLoader(final EnvironmentLoader environmentLoader)
*
* If no verify is explicitly set, either by this method in a builder pattern approach or
* else by one of the convenience constructors, then SslConfig will look to the
- * VAULT_SSL_VERIFY environment variable.
+ * VAULT_SSL_VERIFY environment variable. Only the value false
+ * (case-insensitive) disables verification; any other value keeps it enabled.
*
* @param verify Whether or not to verify the SSL certificate used by Vault with HTTPS
* connections. Default is true.
@@ -495,12 +501,10 @@ public SslConfig build() throws VaultException {
if (this.environmentLoader == null) {
this.environmentLoader = new EnvironmentLoader();
}
- if (this.verifyObject == null && environmentLoader.loadVariable(VAULT_SSL_VERIFY) != null) {
- this.verify = Boolean.parseBoolean(environmentLoader.loadVariable(VAULT_SSL_VERIFY));
- } else if (this.verifyObject != null) {
+ if (this.verifyObject != null) {
this.verify = verifyObject;
} else {
- this.verify = true;
+ this.verify = parseVerify(environmentLoader.loadVariable(VAULT_SSL_VERIFY));
}
if (this.verify && this.pemUTF8 == null
&& environmentLoader.loadVariable(VAULT_SSL_CERT) != null) {
@@ -515,6 +519,32 @@ public SslConfig build() throws VaultException {
return this;
}
+ /**
+ * Parses the VAULT_SSL_VERIFY environment variable, ignoring case and surrounding
+ * whitespace. Verification is only disabled by an explicit false, so that a typo
+ * or an unexpected value can never silently turn it off. t, true,
+ * 1, y, yes and on enable it; any other
+ * value also enables it, with a warning.
+ *
+ * @param value The variable value, or null if unset
+ * @return false only if the value is false
+ */
+ static boolean parseVerify(final String value) {
+ if (value == null || value.isBlank()) {
+ return true;
+ }
+ final String normalized = value.strip().toLowerCase(Locale.ROOT);
+ if (normalized.equals("false")) {
+ return false;
+ }
+ if (!VERIFY_TRUE_VALUES.contains(normalized)) {
+ LOGGER.warning(String.format(
+ "The %s environment variable contains value \"%s\", which is neither \"false\" nor one of %s. SSL verification stays enabled.",
+ VAULT_SSL_VERIFY, value, VERIFY_TRUE_VALUES));
+ }
+ return true;
+ }
+
public boolean isVerify() {
return verify;
}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index afc8c32e..81751eaa 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -8,10 +8,18 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import static junit.framework.TestCase.assertEquals;
@@ -254,4 +262,90 @@ public void testConfigBuilder_WithNamespace() throws VaultException {
.build();
Assert.assertEquals(vaultConfig.getNameSpace(), "namespace");
}
+
+ private final List sslConfigWarnings = new ArrayList<>();
+
+ private final Handler sslConfigWarningsHandler = new Handler() {
+ @Override
+ public void publish(final LogRecord record) {
+ if (record.getLevel() == Level.WARNING) {
+ sslConfigWarnings.add(record);
+ }
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() {
+ }
+ };
+
+ @Before
+ public void captureSslConfigWarnings() {
+ Logger.getLogger(SslConfig.class.getCanonicalName()).addHandler(sslConfigWarningsHandler);
+ }
+
+ @After
+ public void releaseSslConfigWarnings() {
+ Logger.getLogger(SslConfig.class.getCanonicalName())
+ .removeHandler(sslConfigWarningsHandler);
+ }
+
+ private boolean sslVerifyFromEnv(final String value) throws VaultException {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_ADDR", "http://127.0.0.1:8200");
+ mock.override("VAULT_SSL_VERIFY", value);
+ return new VaultConfig().environmentLoader(mock).build().getSslConfig().isVerify();
+ }
+
+ /**
+ * Only an explicit false may disable SSL verification.
+ */
+ @Test
+ public void testConfigBuilder_LoadFromEnv_SslVerifyFalse() throws VaultException {
+ for (final String value : new String[]{"false", "FALSE", " false\n"}) {
+ Assert.assertFalse("\"" + value + "\"", sslVerifyFromEnv(value));
+ }
+ Assert.assertEquals(List.of(), sslConfigWarnings);
+ }
+
+ /**
+ * The recognized true values, unset and empty values enable SSL verification without warnings.
+ */
+ @Test
+ public void testConfigBuilder_LoadFromEnv_SslVerifyTrue() throws VaultException {
+ for (final String value : new String[]{"t", "T", "true", "TRUE", " true", "1", "y", "Y", "yes", "YES", "on", "On\n", ""}) {
+ Assert.assertTrue("\"" + value + "\"", sslVerifyFromEnv(value));
+ }
+ Assert.assertTrue(sslVerifyFromEnv(null));
+ Assert.assertEquals(List.of(), sslConfigWarnings);
+ }
+
+ /**
+ * Any other value, including typos and other "false-like" values, keeps SSL verification
+ * enabled, and logs a warning.
+ */
+ @Test
+ public void testConfigBuilder_LoadFromEnv_SslVerifyFailsClosed() throws VaultException {
+ final String[] values = {"0", "f", "n", "no", "off", "ture", "disabled"};
+ for (final String value : values) {
+ Assert.assertTrue("\"" + value + "\"", sslVerifyFromEnv(value));
+ }
+ Assert.assertEquals(values.length, sslConfigWarnings.size());
+ }
+
+ /**
+ * An explicit verify() setting takes priority over the environment variable.
+ */
+ @Test
+ public void testConfigBuilder_SslVerifyExplicitOverridesEnv() throws VaultException {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_SSL_VERIFY", "false");
+ Assert.assertTrue(new SslConfig().environmentLoader(mock).verify(true).build().isVerify());
+
+ mock.override("VAULT_SSL_VERIFY", "true");
+ Assert.assertFalse(new SslConfig().environmentLoader(mock).verify(false).build().isVerify());
+ }
}
From 3ac83e1b41dc18c72b292e1da13157c7f3fc84a8 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 00:38:13 +0200
Subject: [PATCH 13/34] Fixed empty env variables checks
---
.../jopenlibs/vault/EnvironmentLoader.java | 67 ++++++++++-----
.../io/github/jopenlibs/vault/SslConfig.java | 18 +++--
.../github/jopenlibs/vault/VaultConfig.java | 6 +-
.../vault/EnvironmentLoaderTests.java | 81 +++++++++++++++++++
.../jopenlibs/vault/VaultConfigTests.java | 31 +++++++
5 files changed, 172 insertions(+), 31 deletions(-)
create mode 100644 src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java
diff --git a/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java b/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
index 0e2cbfc7..0ea56b6b 100644
--- a/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
+++ b/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
@@ -3,6 +3,7 @@
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
/**
@@ -11,30 +12,56 @@
*/
public class EnvironmentLoader implements Serializable {
+ /**
+ * Loads an environment variable. An empty or blank value is treated as unset.
+ *
+ * If VAULT_TOKEN is unset, it is read from the .vault-token file
+ * in the executing user's home directory, if any.
+ *
+ * @param name The environment variable name
+ * @return The variable value, or null if it is unset
+ */
public String loadVariable(final String name) {
- String value = null;
- if (VaultConfig.VAULT_TOKEN.equals(name)) {
-
- // Special handling for the VAULT_TOKEN variable, since it can be read from the filesystem if it's not
- // found in the environment
- if (System.getenv(VaultConfig.VAULT_TOKEN) != null) {
- // Found it in the environment
- value = System.getenv(name);
- } else {
- // Not in the environment, looking for a ".vault-token" file in the executing user's home directory instead
- try {
- value = Files.readString(
- Paths.get(System.getProperty("user.home")).resolve(".vault-token")).strip();
- } catch (IOException e) {
- // No-op... there simply isn't a token value available
- }
+ String value = nonBlank(getenv(name));
+ if (value == null && VaultConfig.VAULT_TOKEN.equals(name)) {
+ // Not in the environment, looking for a ".vault-token" file in the executing user's home directory instead
+ try {
+ value = nonBlank(Files.readString(userHome().resolve(".vault-token")).strip());
+ } catch (IOException e) {
+ // No-op... there simply isn't a token value available
}
- } else {
-
- // Normal handling for all other variables. We just check the environment.
- value = System.getenv(name);
}
return value;
}
+ /**
+ * Loads an environment variable with the given loader, treating an empty or blank value as
+ * unset. The check is done here too, because {@link #loadVariable(String)} may be overridden.
+ *
+ * @param loader The environment loader to use
+ * @param name The environment variable name
+ * @return The variable value, or null if it is unset, empty or blank
+ */
+ static String load(final EnvironmentLoader loader, final String name) {
+ return nonBlank(loader.loadVariable(name));
+ }
+
+ /**
+ * Reads a variable from the process environment. Overridden by unit tests.
+ */
+ String getenv(final String name) {
+ return System.getenv(name);
+ }
+
+ /**
+ * @return The executing user's home directory. Overridden by unit tests.
+ */
+ Path userHome() {
+ return Paths.get(System.getProperty("user.home"));
+ }
+
+ private static String nonBlank(final String value) {
+ return value == null || value.isBlank() ? null : value;
+ }
+
}
diff --git a/src/main/java/io/github/jopenlibs/vault/SslConfig.java b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
index c3d5e9cc..69249066 100644
--- a/src/main/java/io/github/jopenlibs/vault/SslConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
@@ -504,15 +504,17 @@ public SslConfig build() throws VaultException {
if (this.verifyObject != null) {
this.verify = verifyObject;
} else {
- this.verify = parseVerify(environmentLoader.loadVariable(VAULT_SSL_VERIFY));
+ this.verify = parseVerify(EnvironmentLoader.load(environmentLoader, VAULT_SSL_VERIFY));
}
- if (this.verify && this.pemUTF8 == null
- && environmentLoader.loadVariable(VAULT_SSL_CERT) != null) {
- final var pemFile = new File(environmentLoader.loadVariable(VAULT_SSL_CERT));
- try {
- this.pemUTF8 = Files.readString(pemFile.toPath(), StandardCharsets.UTF_8);
- } catch (IOException e) {
- throw new VaultException(e);
+ if (this.verify && this.pemUTF8 == null) {
+ final String pemPath = EnvironmentLoader.load(environmentLoader, VAULT_SSL_CERT);
+ if (pemPath != null) {
+ try {
+ this.pemUTF8 = Files.readString(new File(pemPath).toPath(),
+ StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new VaultException(e);
+ }
}
}
buildSsl();
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index 68919fa9..9d4e405d 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -361,14 +361,14 @@ public VaultConfig build() throws VaultException {
this.environmentLoader = new EnvironmentLoader();
}
if (this.address == null) {
- final String addressFromEnv = environmentLoader.loadVariable(VAULT_ADDR);
+ final String addressFromEnv = EnvironmentLoader.load(environmentLoader, VAULT_ADDR);
if (addressFromEnv != null) {
this.address = addressFromEnv;
} else {
throw new VaultException("No address is set");
}
}
- String envVaultToken = environmentLoader.loadVariable(VAULT_TOKEN);
+ String envVaultToken = EnvironmentLoader.load(environmentLoader, VAULT_TOKEN);
if (this.token == null && envVaultToken != null) {
this.token = envVaultToken.toCharArray();
}
@@ -392,7 +392,7 @@ public VaultConfig build() throws VaultException {
* @return The timeout, or null if the variable is unset or not an integer
*/
private Integer loadTimeoutVariable(final String name) {
- final String value = environmentLoader.loadVariable(name);
+ final String value = EnvironmentLoader.load(environmentLoader, name);
if (value == null) {
return null;
}
diff --git a/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java b/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java
new file mode 100644
index 00000000..22cc3fb6
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java
@@ -0,0 +1,81 @@
+package io.github.jopenlibs.vault;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests for the default EnvironmentLoader, with a fake process environment and
+ * home directory.
+ */
+public class EnvironmentLoaderTests {
+
+ @Rule
+ public TemporaryFolder home = new TemporaryFolder();
+
+ private final Map env = new HashMap<>();
+
+ private final EnvironmentLoader loader = new EnvironmentLoader() {
+ @Override
+ String getenv(final String name) {
+ return env.get(name);
+ }
+
+ @Override
+ Path userHome() {
+ return home.getRoot().toPath();
+ }
+ };
+
+ private void writeTokenFile(final String content) throws IOException {
+ Files.writeString(home.getRoot().toPath().resolve(".vault-token"), content);
+ }
+
+ @Test
+ public void testValueIsReturned() {
+ env.put("VAULT_ADDR", "http://127.0.0.1:8200");
+ assertEquals("http://127.0.0.1:8200", loader.loadVariable("VAULT_ADDR"));
+ }
+
+ @Test
+ public void testUnsetEmptyAndBlankValuesAreNull() {
+ assertNull(loader.loadVariable("VAULT_ADDR"));
+ env.put("VAULT_ADDR", "");
+ assertNull(loader.loadVariable("VAULT_ADDR"));
+ env.put("VAULT_ADDR", " \n");
+ assertNull(loader.loadVariable("VAULT_ADDR"));
+ }
+
+ @Test
+ public void testTokenFromEnvironmentWinsOverFile() throws IOException {
+ writeTokenFile("from-file");
+ env.put("VAULT_TOKEN", "from-env");
+ assertEquals("from-env", loader.loadVariable("VAULT_TOKEN"));
+ }
+
+ @Test
+ public void testEmptyTokenFallsBackToFile() throws IOException {
+ writeTokenFile("from-file\n");
+ env.put("VAULT_TOKEN", "");
+ assertEquals("from-file", loader.loadVariable("VAULT_TOKEN"));
+ }
+
+ @Test
+ public void testBlankTokenFileIsNull() throws IOException {
+ writeTokenFile(" \n");
+ assertNull(loader.loadVariable("VAULT_TOKEN"));
+ }
+
+ @Test
+ public void testMissingTokenFileIsNull() {
+ assertNull(loader.loadVariable("VAULT_TOKEN"));
+ }
+}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index 81751eaa..91f2beb9 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -348,4 +348,35 @@ public void testConfigBuilder_SslVerifyExplicitOverridesEnv() throws VaultExcept
mock.override("VAULT_SSL_VERIFY", "true");
Assert.assertFalse(new SslConfig().environmentLoader(mock).verify(false).build().isVerify());
}
+
+ /**
+ * Empty or blank values from any environment loader, even a custom one, are treated as unset.
+ */
+ @Test
+ public void testConfigBuilder_LoadFromEnv_EmptyAddressIsUnset() {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_ADDR", "");
+ try {
+ new VaultConfig().environmentLoader(mock).build();
+ Assert.fail("Expected VaultException");
+ } catch (VaultException e) {
+ assertEquals("No address is set", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testConfigBuilder_LoadFromEnv_EmptyValuesAreUnset() throws VaultException {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_ADDR", "https://127.0.0.1:8200");
+ mock.override("VAULT_TOKEN", "");
+ mock.override("VAULT_OPEN_TIMEOUT", "");
+ mock.override("VAULT_READ_TIMEOUT", " ");
+ mock.override("VAULT_SSL_CERT", "");
+
+ final var config = new VaultConfig().environmentLoader(mock).build();
+ Assert.assertNull(config.getToken());
+ Assert.assertNull(config.getOpenTimeout());
+ Assert.assertNull(config.getReadTimeout());
+ Assert.assertNull(config.getSslConfig().getSslContext());
+ }
}
From dbe7c53b40afa03a25a0f5a7f24cb9eb1c4827b9 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 00:46:38 +0200
Subject: [PATCH 14/34] Trimmed env vars
---
.../jopenlibs/vault/EnvironmentLoader.java | 21 ++++++++------
.../vault/EnvironmentLoaderTests.java | 8 +++++
.../jopenlibs/vault/VaultConfigTests.java | 29 +++++++++++++++++++
3 files changed, 49 insertions(+), 9 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java b/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
index 0ea56b6b..f80b79e4 100644
--- a/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
+++ b/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
@@ -13,7 +13,8 @@
public class EnvironmentLoader implements Serializable {
/**
- * Loads an environment variable. An empty or blank value is treated as unset.
+ * Loads an environment variable. Surrounding whitespace (e.g. a trailing newline) is removed,
+ * and an empty or blank value is treated as unset.
*
* If VAULT_TOKEN is unset, it is read from the .vault-token file
* in the executing user's home directory, if any.
@@ -22,11 +23,11 @@ public class EnvironmentLoader implements Serializable {
* @return The variable value, or null if it is unset
*/
public String loadVariable(final String name) {
- String value = nonBlank(getenv(name));
+ String value = normalize(getenv(name));
if (value == null && VaultConfig.VAULT_TOKEN.equals(name)) {
// Not in the environment, looking for a ".vault-token" file in the executing user's home directory instead
try {
- value = nonBlank(Files.readString(userHome().resolve(".vault-token")).strip());
+ value = normalize(Files.readString(userHome().resolve(".vault-token")));
} catch (IOException e) {
// No-op... there simply isn't a token value available
}
@@ -35,15 +36,17 @@ public String loadVariable(final String name) {
}
/**
- * Loads an environment variable with the given loader, treating an empty or blank value as
- * unset. The check is done here too, because {@link #loadVariable(String)} may be overridden.
+ * Loads an environment variable with the given loader, removing surrounding whitespace and
+ * treating an empty or blank value as unset. This is done here too, because
+ * {@link #loadVariable(String)} may be overridden.
*
* @param loader The environment loader to use
* @param name The environment variable name
- * @return The variable value, or null if it is unset, empty or blank
+ * @return The variable value without surrounding whitespace, or null if it is
+ * unset, empty or blank
*/
static String load(final EnvironmentLoader loader, final String name) {
- return nonBlank(loader.loadVariable(name));
+ return normalize(loader.loadVariable(name));
}
/**
@@ -60,8 +63,8 @@ Path userHome() {
return Paths.get(System.getProperty("user.home"));
}
- private static String nonBlank(final String value) {
- return value == null || value.isBlank() ? null : value;
+ private static String normalize(final String value) {
+ return value == null || value.isBlank() ? null : value.strip();
}
}
diff --git a/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java b/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java
index 22cc3fb6..659ba70e 100644
--- a/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/EnvironmentLoaderTests.java
@@ -45,6 +45,14 @@ public void testValueIsReturned() {
assertEquals("http://127.0.0.1:8200", loader.loadVariable("VAULT_ADDR"));
}
+ @Test
+ public void testValueIsTrimmed() {
+ env.put("VAULT_ADDR", " http://127.0.0.1:8200\n");
+ assertEquals("http://127.0.0.1:8200", loader.loadVariable("VAULT_ADDR"));
+ env.put("VAULT_TOKEN", "from-env\r\n");
+ assertEquals("from-env", loader.loadVariable("VAULT_TOKEN"));
+ }
+
@Test
public void testUnsetEmptyAndBlankValuesAreNull() {
assertNull(loader.loadVariable("VAULT_ADDR"));
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index 91f2beb9..0740d0dd 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -379,4 +379,33 @@ public void testConfigBuilder_LoadFromEnv_EmptyValuesAreUnset() throws VaultExce
Assert.assertNull(config.getReadTimeout());
Assert.assertNull(config.getSslConfig().getSslContext());
}
+
+ /**
+ * Surrounding whitespace (e.g. a trailing newline from a mounted secret) is removed from
+ * values of any environment loader, even a custom one.
+ */
+ @Test
+ public void testConfigBuilder_LoadFromEnv_ValuesAreTrimmed() throws IOException, VaultException {
+ final var pemFile = Files.createTempFile("cert", ".pem");
+ try (InputStream input = this.getClass().getResourceAsStream("/cert.pem")) {
+ Files.write(pemFile, input.readAllBytes());
+ }
+ try {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_ADDR", " https://127.0.0.1:8200\n");
+ mock.override("VAULT_TOKEN", "s.token\n");
+ mock.override("VAULT_OPEN_TIMEOUT", " 5");
+ mock.override("VAULT_READ_TIMEOUT", "30\n");
+ mock.override("VAULT_SSL_CERT", pemFile + "\n");
+
+ final var config = new VaultConfig().environmentLoader(mock).build();
+ assertEquals("https://127.0.0.1:8200", config.getAddress());
+ assertEquals("s.token", new String(config.getToken()));
+ assertEquals(Integer.valueOf(5), config.getOpenTimeout());
+ assertEquals(Integer.valueOf(30), config.getReadTimeout());
+ Assert.assertNotNull(config.getSslConfig().getSslContext());
+ } finally {
+ Files.delete(pemFile);
+ }
+ }
}
From 2868197ba0253329a39bdd086198734969497f26 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 00:58:05 +0200
Subject: [PATCH 15/34] Checked that timeout isn't negative
---
.../github/jopenlibs/vault/VaultConfig.java | 30 ++++++++++-----
.../io/github/jopenlibs/vault/rest/Rest.java | 10 +++--
.../github/jopenlibs/vault/TimeoutTests.java | 38 +++++++++++++++++++
.../jopenlibs/vault/VaultConfigTests.java | 16 ++++++++
4 files changed, 81 insertions(+), 13 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index 9d4e405d..ec447c62 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -212,7 +212,7 @@ public VaultConfig sslConfig(final SslConfig sslConfig) {
* environment variable.
*
* @param openTimeout Number of seconds to wait for an HTTP(S) connection to successfully
- * establish
+ * establish, or 0 for no timeout
* @return This object, with openTimeout populated, ready for additional builder-pattern method
* calls or else finalization with the build() method
*/
@@ -231,7 +231,7 @@ public VaultConfig openTimeout(final Integer openTimeout) {
* environment variable.
*
* @param readTimeout Number of seconds to wait for all data to be retrieved from an established
- * HTTP(S) connection
+ * HTTP(S) connection, or 0 for no timeout
* @return This object, with readTimeout populated, ready for additional builder-pattern method
* calls or else finalization with the build() method
*/
@@ -351,7 +351,8 @@ void setEngineVersion(final Integer engineVersion) {
*
* @return This object, with all available config options parsed and loaded
* @throws VaultException If the address field was left unset, and there is no
- * VAULT_ADDR environment variable value with which to populate it.
+ * VAULT_ADDR environment variable value with which to populate it, or if a
+ * negative timeout was set.
*/
public VaultConfig build() throws VaultException {
if (this.nameSpace != null && this.nameSpace.isEmpty()) {
@@ -378,6 +379,12 @@ public VaultConfig build() throws VaultException {
if (this.readTimeout == null) {
this.readTimeout = loadTimeoutVariable(VAULT_READ_TIMEOUT);
}
+ if (this.openTimeout != null && this.openTimeout < 0) {
+ throw new VaultException("The open timeout cannot be negative: " + this.openTimeout);
+ }
+ if (this.readTimeout != null && this.readTimeout < 0) {
+ throw new VaultException("The read timeout cannot be negative: " + this.readTimeout);
+ }
if (this.sslConfig == null) {
this.sslConfig = new SslConfig().environmentLoader(this.environmentLoader).build();
}
@@ -389,7 +396,8 @@ public VaultConfig build() throws VaultException {
* Reads a timeout period from an environment variable.
*
* @param name The environment variable name
- * @return The timeout, or null if the variable is unset or not an integer
+ * @return The timeout, or null if the variable is unset, not an integer, or
+ * negative
*/
private Integer loadTimeoutVariable(final String name) {
final String value = EnvironmentLoader.load(environmentLoader, name);
@@ -397,13 +405,17 @@ private Integer loadTimeoutVariable(final String name) {
return null;
}
try {
- return Integer.valueOf(value);
+ final int timeout = Integer.parseInt(value);
+ if (timeout >= 0) {
+ return timeout;
+ }
} catch (NumberFormatException e) {
- LOGGER.warning(String.format(
- "The %s environment variable contains value \"%s\", which cannot be parsed as an integer timeout period.",
- name, value));
- return null;
+ // Reported below
}
+ LOGGER.warning(String.format(
+ "The %s environment variable contains value \"%s\", which is not a valid timeout period (a non-negative integer).",
+ name, value));
+ return null;
}
public Map getSecretsEnginePathMap() {
diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
index acfb1203..64636b22 100644
--- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
+++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
@@ -235,7 +235,7 @@ public Rest header(final String name, final String value) {
* The number of seconds to wait before giving up on establishing an HTTP(S) connection.
*
* @param connectTimeoutSeconds Number of seconds to wait for an HTTP(S) connection to
- * successfully establish
+ * successfully establish, or 0 for no timeout
* @return This object, with connectTimeoutSeconds populated, ready for other builder-pattern
* config methods or an HTTP verb method
*/
@@ -249,7 +249,7 @@ public Rest connectTimeoutSeconds(final Integer connectTimeoutSeconds) {
* to wait for all data to finish downloading.
*
* @param readTimeoutSeconds Number of seconds to wait for all data to be retrieved from an
- * established HTTP(S) connection
+ * established HTTP(S) connection, or 0 for no timeout
* @return This object, with readTimeoutSeconds populated, ready for other builder-pattern
* config methods or an HTTP verb method
*/
@@ -480,7 +480,8 @@ public static HttpClient newHttpClient(final Integer connectTimeoutSeconds,
//Stick to HTTP/1.1 by default, coz Vault Agent fails proxying h2c request to https
.version(Version.HTTP_1_1);
- if (connectTimeoutSeconds != null) {
+ // As with HttpURLConnection, 0 means no timeout
+ if (connectTimeoutSeconds != null && connectTimeoutSeconds > 0) {
client.connectTimeout(Duration.ofSeconds(connectTimeoutSeconds));
}
@@ -528,7 +529,8 @@ private Builder buildRequest(final boolean isGetOrDelete)
headers.forEach(request::header);
- if (readTimeoutSeconds != null) {
+ // As with HttpURLConnection, 0 means no timeout
+ if (readTimeoutSeconds != null && readTimeoutSeconds > 0) {
request.timeout(Duration.ofSeconds(readTimeoutSeconds));
}
diff --git a/src/test/java/io/github/jopenlibs/vault/TimeoutTests.java b/src/test/java/io/github/jopenlibs/vault/TimeoutTests.java
index b0b91185..948afaf8 100644
--- a/src/test/java/io/github/jopenlibs/vault/TimeoutTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/TimeoutTests.java
@@ -102,4 +102,42 @@ public void testReadTimeout_BeyondThreshold() throws Exception {
}
}
+
+ /**
+ * A timeout of 0 means no timeout, as with the former HttpURLConnection client.
+ */
+ @Test
+ public void testZeroTimeouts_MeanNoTimeout() throws Exception {
+ // Mock Vault takes 2 seconds to respond
+ final var readTimeoutsMockVault = new ReadTimeoutsMockVault(2, 200,
+ "{\"data\":{\"value\":\"mock\"}}");
+ final var server = VaultTestUtils.initHttpMockVault(readTimeoutsMockVault);
+ server.start();
+
+ final var vaultConfig = new VaultConfig()
+ .address("http://127.0.0.1:8999")
+ .token("mock_token")
+ .openTimeout(0)
+ .readTimeout(0)
+ .engineVersion(1)
+ .build();
+ final var vault = Vault.create(vaultConfig);
+
+ try {
+ final var response = vault.logical().read("secret/hello");
+ org.junit.Assert.assertEquals("mock", response.getData().get("value"));
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
+ @Test(expected = VaultException.class)
+ public void testNegativeOpenTimeout_IsRejected() throws VaultException {
+ new VaultConfig().address("http://127.0.0.1:8999").openTimeout(-1).build();
+ }
+
+ @Test(expected = VaultException.class)
+ public void testNegativeReadTimeout_IsRejected() throws VaultException {
+ new VaultConfig().address("http://127.0.0.1:8999").readTimeout(-1).build();
+ }
}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index 0740d0dd..e270897c 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -408,4 +408,20 @@ public void testConfigBuilder_LoadFromEnv_ValuesAreTrimmed() throws IOException,
Files.delete(pemFile);
}
}
+
+ /**
+ * A zero timeout from the environment is kept (it means no timeout), a negative one is
+ * ignored.
+ */
+ @Test
+ public void testConfigBuilder_LoadFromEnv_ZeroAndNegativeTimeouts() throws VaultException {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_ADDR", "http://127.0.0.1:8200");
+ mock.override("VAULT_OPEN_TIMEOUT", "0");
+ mock.override("VAULT_READ_TIMEOUT", "-1");
+
+ final var config = new VaultConfig().environmentLoader(mock).build();
+ assertEquals(Integer.valueOf(0), config.getOpenTimeout());
+ Assert.assertNull(config.getReadTimeout());
+ }
}
From c3752ac0f9b40d72a2e60ce6a6b17e9513b9b527 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 01:02:25 +0200
Subject: [PATCH 16/34] Checked if VAULT_TOKEN env var is null
---
.../github/jopenlibs/vault/VaultConfig.java | 9 ++++---
.../jopenlibs/vault/VaultConfigTests.java | 25 +++++++++++++++++++
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index ec447c62..5eb67a3d 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -369,9 +369,12 @@ public VaultConfig build() throws VaultException {
throw new VaultException("No address is set");
}
}
- String envVaultToken = EnvironmentLoader.load(environmentLoader, VAULT_TOKEN);
- if (this.token == null && envVaultToken != null) {
- this.token = envVaultToken.toCharArray();
+ if (this.token == null) {
+ // Only looked up when needed: the default loader may read ~/.vault-token from disk
+ final String envVaultToken = EnvironmentLoader.load(environmentLoader, VAULT_TOKEN);
+ if (envVaultToken != null) {
+ this.token = envVaultToken.toCharArray();
+ }
}
if (this.openTimeout == null) {
this.openTimeout = loadTimeoutVariable(VAULT_OPEN_TIMEOUT);
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index e270897c..6c93b9f5 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -424,4 +424,29 @@ public void testConfigBuilder_LoadFromEnv_ZeroAndNegativeTimeouts() throws Vault
assertEquals(Integer.valueOf(0), config.getOpenTimeout());
Assert.assertNull(config.getReadTimeout());
}
+
+ /**
+ * VAULT_TOKEN is not looked up when a token was set explicitly, since the default
+ * loader may read the ~/.vault-token file for it.
+ */
+ @Test
+ public void testConfigBuilder_ExplicitTokenSkipsEnvLookup() throws VaultException {
+ final List loaded = new ArrayList<>();
+ final MockEnvironmentLoader mock = new MockEnvironmentLoader() {
+ @Override
+ public String loadVariable(final String name) {
+ loaded.add(name);
+ return super.loadVariable(name);
+ }
+ };
+ mock.override("VAULT_TOKEN", "from-env");
+
+ final var config = new VaultConfig()
+ .environmentLoader(mock)
+ .address("http://127.0.0.1:8200")
+ .token("explicit")
+ .build();
+ assertEquals("explicit", new String(config.getToken()));
+ Assert.assertFalse(loaded.contains("VAULT_TOKEN"));
+ }
}
From 63081a814de9f8c17ab54ca427f29e4f1d2a7a30 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 01:14:40 +0200
Subject: [PATCH 17/34] Enforced SSL verify in build() phase
---
.../io/github/jopenlibs/vault/SslConfig.java | 37 ++++++-
.../github/jopenlibs/vault/VaultConfig.java | 7 +-
.../jopenlibs/vault/VaultConfigTests.java | 97 +++++++++++++++++++
3 files changed, 139 insertions(+), 2 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/SslConfig.java b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
index 69249066..f2c45bee 100644
--- a/src/main/java/io/github/jopenlibs/vault/SslConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
@@ -52,7 +52,7 @@ public class SslConfig implements Serializable {
private static final List VERIFY_TRUE_VALUES = List.of("t", "true", "1", "y", "yes", "on");
private static final Logger LOGGER = Logger.getLogger(SslConfig.class.getCanonicalName());
- private boolean verify;
+ private boolean verify = true;
private transient SSLContext sslContext;
private transient KeyStore trustStore;
private transient KeyStore keyStore;
@@ -62,6 +62,7 @@ public class SslConfig implements Serializable {
private String clientKeyPemUTF8;
private Boolean verifyObject;
private EnvironmentLoader environmentLoader;
+ private boolean built;
/**
* The code used to load environment variables is encapsulated here, so that a mock version
@@ -518,9 +519,30 @@ public SslConfig build() throws VaultException {
}
}
buildSsl();
+ this.built = true;
return this;
}
+ /**
+ * Builds this object, unless {@link #build()} was already called. Used by
+ * {@link VaultConfig#build()}, so that a SslConfig passed to it is ready for use
+ * even if its own build() was not called.
+ *
+ * @param defaultEnvironmentLoader The environment loader to use, if none was set on this object
+ * @return This object
+ * @throws VaultException If any problem occurs while building
+ */
+ SslConfig buildIfNeeded(final EnvironmentLoader defaultEnvironmentLoader)
+ throws VaultException {
+ if (built) {
+ return this;
+ }
+ if (this.environmentLoader == null) {
+ this.environmentLoader = defaultEnvironmentLoader;
+ }
+ return build();
+ }
+
/**
* Parses the VAULT_SSL_VERIFY environment variable, ignoring case and surrounding
* whitespace. Verification is only disabled by an explicit false, so that a typo
@@ -547,7 +569,20 @@ static boolean parseVerify(final String value) {
return true;
}
+ /**
+ *
Whether HTTPS connections should verify the Vault server's SSL certificate.
+ *
+ * A value set with {@link #verify(Boolean)} always takes priority, even if it was set after
+ * {@link #build()}. Otherwise this is the value resolved by {@link #build()} (from the
+ * VAULT_SSL_VERIFY environment variable), or true if
+ * {@link #build()} was not called.
+ *
+ * @return false only if verification was explicitly disabled
+ */
public boolean isVerify() {
+ if (verifyObject != null) {
+ return verifyObject;
+ }
return verify;
}
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index 5eb67a3d..83ae148c 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -193,6 +193,10 @@ public VaultConfig putSecretsEngineVersionForPath(String path, String version) {
* SslConfig object with {@link SslConfig#verify(Boolean)} explicitly set to
* false.
*
+ * If {@link SslConfig#build()} was not called on the passed object, {@link #build()} calls
+ * it, using this object's environment loader unless the SslConfig has its
+ * own.
+ *
* @param sslConfig SSL-related configuration options
* @return This object, with SSL configuration options populated, ready for additional
* builder-pattern method calls or else finalization with the build() method
@@ -389,8 +393,9 @@ public VaultConfig build() throws VaultException {
throw new VaultException("The read timeout cannot be negative: " + this.readTimeout);
}
if (this.sslConfig == null) {
- this.sslConfig = new SslConfig().environmentLoader(this.environmentLoader).build();
+ this.sslConfig = new SslConfig();
}
+ this.sslConfig.buildIfNeeded(this.environmentLoader);
this.defaultHttpClient = null;
return this;
}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index 6c93b9f5..f88fb3d9 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -449,4 +449,101 @@ public String loadVariable(final String name) {
assertEquals("explicit", new String(config.getToken()));
Assert.assertFalse(loaded.contains("VAULT_TOKEN"));
}
+
+ /**
+ * An SslConfig that was never built must not disable SSL verification.
+ */
+ @Test
+ public void testUnbuiltSslConfigVerifies() {
+ Assert.assertTrue(new SslConfig().isVerify());
+ }
+
+ /**
+ * An explicit verify() value is honored whether or not build() was
+ * called, and also when set after it.
+ */
+ @Test
+ public void testSslVerifyExplicitValueAlwaysWins() throws VaultException {
+ Assert.assertFalse(new SslConfig().verify(false).isVerify());
+ Assert.assertFalse(new SslConfig().build().verify(false).isVerify());
+
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_SSL_VERIFY", "false");
+ Assert.assertTrue(new SslConfig().environmentLoader(mock).build().verify(true).isVerify());
+ }
+
+ /**
+ * VaultConfig.build() builds an SslConfig whose own
+ * build() was not called, so a supplied certificate is actually used.
+ */
+ @Test
+ public void testBuildBuildsPassedSslConfig() throws VaultException {
+ final var sslConfig = new SslConfig().pemResource("/cert.pem");
+ Assert.assertNull(sslConfig.getSslContext());
+
+ final var config = new VaultConfig()
+ .environmentLoader(new MockEnvironmentLoader())
+ .address("https://127.0.0.1:8200")
+ .sslConfig(sslConfig)
+ .build();
+ Assert.assertTrue(config.getSslConfig().isVerify());
+ Assert.assertNotNull(sslConfig.getSslContext());
+ }
+
+ /**
+ * A passed SslConfig without its own environment loader uses the one of the
+ * VaultConfig.
+ */
+ @Test
+ public void testBuildPassesEnvironmentLoaderToSslConfig() throws VaultException {
+ final var mock = new MockEnvironmentLoader();
+ mock.override("VAULT_SSL_VERIFY", "false");
+ final var config = new VaultConfig()
+ .environmentLoader(mock)
+ .address("https://127.0.0.1:8200")
+ .sslConfig(new SslConfig())
+ .build();
+ Assert.assertFalse(config.getSslConfig().isVerify());
+ }
+
+ /**
+ * An already built SslConfig is left untouched.
+ */
+ @Test
+ public void testBuildDoesNotRebuildBuiltSslConfig() throws VaultException {
+ final var sslEnv = new MockEnvironmentLoader();
+ sslEnv.override("VAULT_SSL_VERIFY", "false");
+ final var sslConfig = new SslConfig().environmentLoader(sslEnv).build();
+
+ final var vaultEnv = new MockEnvironmentLoader();
+ vaultEnv.override("VAULT_SSL_VERIFY", "true");
+ final var config = new VaultConfig()
+ .environmentLoader(vaultEnv)
+ .address("https://127.0.0.1:8200")
+ .sslConfig(sslConfig)
+ .build();
+ Assert.assertFalse(config.getSslConfig().isVerify());
+
+ final var pemConfig = new SslConfig().pemResource("/cert.pem").build();
+ final var sslContext = pemConfig.getSslContext();
+ new VaultConfig()
+ .environmentLoader(new MockEnvironmentLoader())
+ .address("https://127.0.0.1:8200")
+ .sslConfig(pemConfig)
+ .build();
+ Assert.assertSame(sslContext, pemConfig.getSslContext());
+ }
+
+ /**
+ * An invalid certificate in a passed SslConfig is reported by
+ * VaultConfig.build().
+ */
+ @Test(expected = VaultException.class)
+ public void testBuildFailsOnInvalidPassedSslConfig() throws VaultException {
+ new VaultConfig()
+ .environmentLoader(new MockEnvironmentLoader())
+ .address("https://127.0.0.1:8200")
+ .sslConfig(new SslConfig().pemUTF8("not a certificate"))
+ .build();
+ }
}
From 368a32bf68ce9f62570c134cc4d29fdae6347ee2 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 01:17:28 +0200
Subject: [PATCH 18/34] Restored serialized fields when read object in SSL
context
---
.../io/github/jopenlibs/vault/SslConfig.java | 2 +
.../vault/SslConfigSerializationTests.java | 82 +++++++++++++++++++
2 files changed, 84 insertions(+)
create mode 100644 src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java
diff --git a/src/main/java/io/github/jopenlibs/vault/SslConfig.java b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
index f2c45bee..626d0b65 100644
--- a/src/main/java/io/github/jopenlibs/vault/SslConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
@@ -788,6 +788,8 @@ private static String inputStreamToUTF8(final InputStream input) throws IOExcept
*/
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {//NOPMD
+ // Restore the serialized fields first: buildSsl() depends on them (e.g. verify, pemUTF8)
+ in.defaultReadObject();
try {
buildSsl();
} catch (VaultException e) {
diff --git a/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java b/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java
new file mode 100644
index 00000000..38864a51
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java
@@ -0,0 +1,82 @@
+package io.github.jopenlibs.vault;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests verifying that SSL settings survive Java serialization.
+ */
+public class SslConfigSerializationTests {
+
+ @SuppressWarnings("unchecked")
+ private static T roundTrip(final T object) throws IOException, ClassNotFoundException {
+ final var bytes = new ByteArrayOutputStream();
+ try (var out = new ObjectOutputStream(bytes)) {
+ out.writeObject(object);
+ }
+ try (var in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
+ return (T) in.readObject();
+ }
+ }
+
+ private static String certPem() throws IOException {
+ try (var input = SslConfigSerializationTests.class.getResourceAsStream("/cert.pem")) {
+ return new String(input.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+
+ @Test
+ public void testVerifyEnabledSurvives() throws Exception {
+ assertTrue(roundTrip(new SslConfig().verify(true).build()).isVerify());
+ }
+
+ @Test
+ public void testVerifyDisabledSurvives() throws Exception {
+ assertFalse(roundTrip(new SslConfig().verify(false).build()).isVerify());
+ }
+
+ @Test
+ public void testPemCertificateSurvives() throws Exception {
+ final String pem = certPem();
+ final var copy = roundTrip(new SslConfig().pemUTF8(pem).build());
+
+ assertEquals(pem, copy.getPemUTF8());
+ assertNotNull(copy.getSslContext());
+ }
+
+ /**
+ * A deserialized, already built SslConfig is not rebuilt by
+ * VaultConfig.build().
+ */
+ @Test
+ public void testBuiltStateSurvives() throws Exception {
+ final var copy = roundTrip(new SslConfig().pemUTF8(certPem()).build());
+ final var sslContext = copy.getSslContext();
+
+ new VaultConfig().address("https://127.0.0.1:8200").sslConfig(copy).build();
+ assertSame(sslContext, copy.getSslContext());
+ }
+
+ @Test
+ public void testVaultConfigSslSettingsSurvive() throws Exception {
+ final var config = new VaultConfig()
+ .address("https://127.0.0.1:8200")
+ .sslConfig(new SslConfig().pemUTF8(certPem()))
+ .build();
+ final var copy = roundTrip(config);
+
+ assertTrue(copy.getSslConfig().isVerify());
+ assertNotNull(copy.getSslConfig().getSslContext());
+ }
+}
From bd4694eacaf5fd8b34a7428b0651662de44e8f05 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 01:32:00 +0200
Subject: [PATCH 19/34] Updated README
---
README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 74 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 7efaac5b..0d1745f6 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Table of Contents
* [Installing the Driver](#installing-the-driver)
* [Initializing a Driver Instance](#initializing-a-driver-instance)
+* [Environment Variables](#environment-variables)
* [Key/Value Secret Engine Config](#key-value-secret-engine-config)
* [SSL Config](#ssl-config)
* [General Options](#general-options)
@@ -82,12 +83,14 @@ final VaultConfig config =
new VaultConfig()
.address("http://127.0.0.1:8200") // Defaults to "VAULT_ADDR" environment variable
.token("3c9fd6be-7bc2-9d1f-6fb3-cd746c0fc4e8") // Defaults to "VAULT_TOKEN" environment variable
- .openTimeout(5) // Defaults to "VAULT_OPEN_TIMEOUT" environment variable
- .readTimeout(30) // Defaults to "VAULT_READ_TIMEOUT" environment variable
+ .openTimeout(5) // Defaults to "VAULT_OPEN_TIMEOUT" environment variable (0 = no timeout)
+ .readTimeout(30) // Defaults to "VAULT_READ_TIMEOUT" environment variable (0 = no timeout)
.sslConfig(new SslConfig().build()) // See "SSL Config" section below
.build();
```
+See [Environment Variables](#environment-variables) for how these variables are read.
+
Once you have initialized a `VaultConfig` object, you can use it to construct an instance of
the `Vault` primary driver class:
@@ -95,6 +98,42 @@ the `Vault` primary driver class:
final Vault vault = Vault.create(config);
```
+Environment Variables
+---------------------
+Any setting that is not set in code is read from an environment variable when `build()` is called.
+A value set in code always takes priority, and the matching environment variable is then not read
+at all.
+
+| Variable | Setting | Notes |
+|----------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------|
+| `VAULT_ADDR` | `VaultConfig.address` | Required if no address is set in code, otherwise `build()` fails with "No address is set". |
+| `VAULT_TOKEN` | `VaultConfig.token` | If unset, the token is read from the `.vault-token` file in the user's home directory, if present. |
+| `VAULT_OPEN_TIMEOUT` | `VaultConfig.openTimeout` | Seconds to wait for a connection. `0` means no timeout. |
+| `VAULT_READ_TIMEOUT` | `VaultConfig.readTimeout` | Seconds to wait for a response. `0` means no timeout. |
+| `VAULT_SSL_VERIFY` | `SslConfig.verify` | Whether to verify the Vault server's SSL certificate. Only `false` disables verification (see below). |
+| `VAULT_SSL_CERT` | `SslConfig.pemFile` | Path to a PEM file with the Vault server's certificate. Only used if verification is enabled and no certificate is set in code. |
+
+All values are handled the same way:
+
+* Surrounding whitespace is removed, so a trailing newline (e.g. from a mounted Kubernetes secret)
+ is harmless.
+* An empty or blank value is treated as unset. This also applies to the `.vault-token` file.
+* An invalid timeout (not an integer, or negative) is ignored, and a warning is logged. A negative
+ timeout set in code makes `build()` throw a `VaultException`.
+
+`VAULT_SSL_VERIFY` fails closed, so that a typo can never disable SSL verification by accident:
+
+* `false` (in any case) disables verification.
+* `true`, `t`, `1`, `y`, `yes` and `on` (in any case), or an empty value, enable it.
+* Any other value, including `0`, `no` and `off`, also enables it, and a warning is logged.
+
+To disable loading from environment variables, or to read the values from another source, pass
+your own `EnvironmentLoader` implementation to `VaultConfig.environmentLoader()`. Whitespace removal
+and empty values are still handled as above for the values it returns, while the `.vault-token`
+fallback is part of the default `EnvironmentLoader` only. An `SslConfig` passed to `VaultConfig`
+without calling its own `build()` is built by `VaultConfig.build()`, using the same
+`EnvironmentLoader`.
+
Key Value Secret Engine Config
------------------------------
Shortly before its `1.0` release, Vault added a Version 2 of
@@ -163,7 +202,9 @@ own `SslConfig` class. This class likewise using a builder pattern.
.verify(false) // Defaults to "VAULT_SSL_VERIFY" environment variable (or else "true")
```
-To disable SSL certificate verification altogether, set `sslVerify(false)`. YOU SHOULD NOT DO THIS
+To disable SSL certificate verification altogether, set `verify(false)`, or set the
+`VAULT_SSL_VERIFY` environment variable to `false` (see
+[Environment Variables](#environment-variables)). YOU SHOULD NOT DO THIS
IS A REAL PRODUCTION SETTING! However, it can be useful in a development or testing server context.
If this value is explicitly set to `false`, then all other SSL config is basically unused.
@@ -218,7 +259,7 @@ the `SslConfig` Javadocs for more detail).
If SSL verification is enabled, no JKS-based config is provided, AND none of these three methods are
called, then `SslConfig` will by default check for a `VAULT_SSL_CERT` environment variable. If
-that's sets then it will be treated as a filesystem path.
+that's set, then it will be treated as a filesystem path.
To use Vault's TLS Certificate auth backend for SSL client auth, you must provide your client
certificate and private key, using some pair from the following options:
@@ -302,6 +343,35 @@ and may require modifications in your code to migrate. Changes to the minor vers
number) should represent non-breaking changes. The third number represents any very minor bugfix
patches.
+* **Unreleased**: This release contains the following updates:
+ * Environment variables: surrounding whitespace is removed, and empty values are treated as unset
+ * `VAULT_SSL_VERIFY` now only disables SSL verification when set to `false`. Previously any value
+ other than `true` disabled it, including `1`, `yes`, typos and empty values. **Values like `0`,
+ `no` or `off` now keep verification enabled: use `false` instead.**
+ * A timeout of `0` means no timeout again, as before 6.0.0. Negative timeouts are rejected
+ * `VAULT_TOKEN` and the `.vault-token` file are no longer read when a token is set in code
+ * An `SslConfig` passed to `VaultConfig` without calling its `build()` is now built by
+ `VaultConfig.build()`, and verifies SSL certificates by default. Previously it silently disabled
+ verification and ignored its certificates
+ * A deserialized `SslConfig` now keeps its settings. Previously it had SSL verification disabled
+ * Each `VaultConfig` now reuses a single HTTP client, so connections are reused between requests
+ instead of a new client being created for every request. `VaultConfig.getHttpClient()` now
+ returns the client in use instead of `null` when none was set
+ * Response bodies are returned exactly as received. Previously non-UTF-8 (e.g. binary) bodies
+ were corrupted
+ * Only transient errors are retried: I/O errors and HTTP 5xx, 408, 412 and 429 responses.
+ **Other 4xx responses (e.g. 400, 403, 404) now fail immediately instead of being retried.** An
+ interrupt while waiting between retries stops retrying
+ * `AuthResponse`, `WrapResponse`, `LookupResponse` and `UnwrapResponse` no longer throw on
+ missing or `null` JSON fields
+ * `Vault.create(config, null)` throws `IllegalArgumentException`, and a `null`
+ `useSecretsEnginePathMap` counts as `false`. When no KV engine version is set, version 2 is used
+ (`Logical.getEngineVersionForSecretPath()` returns `2` instead of `null`)
+ * `VaultConfig.nameSpace()` no longer throws `VaultException`: an empty namespace is rejected by
+ `build()` instead. `VaultConfig.getSecretsEnginePathMap()` returns an unmodifiable map
+ * Warnings are logged with `java.util.logging` instead of being printed to standard error
+ * Build: pinned Maven plugin versions, fixed test reports in the GitHub Actions CI, removed the
+ unused Travis CI configuration
* **6.2.3**: This release contains the following updates:
* Improved Java 11 compatibility
* Fixed integration tests with Vault 2.x
From 0d9ab442ae942b3a121b56e3b0bc23e6daf5b4d2 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 15:52:14 +0200
Subject: [PATCH 20/34] Fixed query parameters encoding
---
.../io/github/jopenlibs/vault/rest/Rest.java | 18 ++++++++--------
.../github/jopenlibs/vault/rest/GetTests.java | 21 +++++++++++++++++++
2 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
index 64636b22..77c37d16 100644
--- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
+++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java
@@ -510,18 +510,18 @@ private Builder buildRequest(final boolean isGetOrDelete)
throw new RestException("No URL is set");
}
- final var baseUri = new URI(urlString);
+ var uri = new URI(urlString);
final var params = isGetOrDelete ? parametersToQueryString() : "";
- var query = params;
- if (baseUri.getQuery() != null) {
- query = baseUri.getQuery();
- if (!params.isEmpty()) {
- query = baseUri.getQuery() + "&" + params;
- }
+ if (!params.isEmpty()) {
+ // Work on the raw (still encoded) components: parameters are already url-encoded, and
+ // the multi-argument URI constructors would encode them again (e.g. '%' to "%25")
+ final var query = uri.getRawQuery() == null ? params : uri.getRawQuery() + "&" + params;
+ uri = new URI(uri.getScheme() + "://" + uri.getRawAuthority()
+ + (uri.getRawPath() == null ? "" : uri.getRawPath())
+ + "?" + query
+ + (uri.getRawFragment() == null ? "" : "#" + uri.getRawFragment()));
}
- final var uri = new URI(baseUri.getScheme(), baseUri.getUserInfo(), baseUri.getHost(),
- baseUri.getPort(), baseUri.getPath(), query, baseUri.getFragment());
// Initialize HTTP(S) connection, and set any header values
final var request = HttpRequest.newBuilder()
diff --git a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java
index 84934ce9..54b70cfe 100644
--- a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java
@@ -119,6 +119,27 @@ public void testGet_UpdateParams() throws RestException {
args.getString("multi part", null));
}
+ /**
+ * Parameters containing reserved characters (and '%') are url-encoded exactly once, so the
+ * server receives their original values.
+ */
+ @Test
+ public void testGet_ParamsWithReservedCharacters() throws RestException {
+ final var restResponse = new Rest()
+ .url(this.URL + "?hot=cold")
+ .parameter("key", "a/b c&d=e%f?g#h")
+ .get();
+ assertEquals(200, restResponse.getStatus());
+
+ final var jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8);
+ final var jsonObject = Json.parse(jsonString).asObject();
+ assertEquals("http://127.0.0.1:8999/?hot=cold&key=a%2Fb+c%26d%3De%25f%3Fg%23h",
+ jsonObject.getString("URL", null));
+ final var args = jsonObject.get("args").asObject();
+ assertEquals("cold", args.getString("hot", null));
+ assertEquals("a/b c&d=e%f?g#h", args.getString("key", null));
+ }
+
/**
* Verify a GET request that passes HTTP headers.
*
From ca868fc1c3278ff15197d0cba5e13c7513b53c3b Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 17:54:23 +0200
Subject: [PATCH 21/34] Set transient a custom HttpClient
---
.../github/jopenlibs/vault/VaultConfig.java | 6 ++-
.../jopenlibs/vault/VaultConfigTests.java | 47 +++++++++++++++++++
2 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index 83ae148c..5c8eef6c 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -49,7 +49,8 @@ public class VaultConfig implements Serializable {
private Integer globalEngineVersion;
private String nameSpace;
private EnvironmentLoader environmentLoader;
- private HttpClient httpClient;
+ // HttpClient is not serializable: a deserialized config falls back to the default client
+ private transient HttpClient httpClient;
private transient volatile HttpClient defaultHttpClient;
/**
@@ -300,6 +301,9 @@ public VaultConfig prefixPath(String prefixPath) {
* http resources (connections, worker threads) between calls. If a preconfigured HttpClient is
* specified, then sslConfig and openTimeout values passed to VaultConfig are ignored.
*
+ * The client is not serialized: a deserialized VaultConfig builds its own
+ * client from sslConfig and openTimeout, unless a new one is set with this method.
+ *
* @param httpClient preconfigured http client instance
* @return VaultConfig
*/
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index f88fb3d9..55311681 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -1,10 +1,15 @@
package io.github.jopenlibs.vault;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.PrintWriter;
+import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -546,4 +551,46 @@ public void testBuildFailsOnInvalidPassedSslConfig() throws VaultException {
.sslConfig(new SslConfig().pemUTF8("not a certificate"))
.build();
}
+
+ /**
+ * An environment loader with no variables set, that can be serialized (unlike the inner
+ * MockEnvironmentLoader, which holds a reference to the test instance).
+ */
+ static class EmptyEnvironmentLoader extends EnvironmentLoader {
+
+ @Override
+ public String loadVariable(final String name) {
+ return null;
+ }
+ }
+
+ /**
+ * A VaultConfig with a preconfigured HttpClient (which is not
+ * serializable) can be serialized. The deserialized copy falls back to its own default client.
+ */
+ @Test
+ public void testSerializationWithCustomHttpClient() throws Exception {
+ final HttpClient httpClient = HttpClient.newHttpClient();
+ final VaultConfig config = new VaultConfig()
+ .environmentLoader(new EmptyEnvironmentLoader())
+ .address("http://127.0.0.1:8200")
+ .token("token")
+ .httpClient(httpClient)
+ .build();
+
+ final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
+ out.writeObject(config);
+ }
+ final VaultConfig copy;
+ try (ObjectInputStream in = new ObjectInputStream(
+ new ByteArrayInputStream(bytes.toByteArray()))) {
+ copy = (VaultConfig) in.readObject();
+ }
+
+ assertEquals("http://127.0.0.1:8200", copy.getAddress());
+ Assert.assertNotNull(copy.getHttpClient());
+ Assert.assertNotSame(httpClient, copy.getHttpClient());
+ Assert.assertSame(copy.getHttpClient(), copy.getHttpClient());
+ }
}
From 2da3fc9f2eb40bd8a656264a0d08ed5e71becfbe Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:02:59 +0200
Subject: [PATCH 22/34] Fixed possible stale of HttpClient if SSlConfig change
---
.../github/jopenlibs/vault/VaultConfig.java | 55 +++++++++++++++----
.../jopenlibs/vault/VaultConfigTests.java | 41 ++++++++++++++
2 files changed, 84 insertions(+), 12 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index 5c8eef6c..bdb64a98 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -5,8 +5,10 @@
import java.net.http.HttpClient;
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
/**
* A container for the configuration settings needed to initialize a Vault driver
@@ -51,7 +53,7 @@ public class VaultConfig implements Serializable {
private EnvironmentLoader environmentLoader;
// HttpClient is not serializable: a deserialized config falls back to the default client
private transient HttpClient httpClient;
- private transient volatile HttpClient defaultHttpClient;
+ private transient volatile DefaultHttpClient defaultHttpClient;
/**
*
The code used to load environment variables is encapsulated here, so that a mock version
@@ -204,7 +206,6 @@ public VaultConfig putSecretsEngineVersionForPath(String path, String version) {
*/
public VaultConfig sslConfig(final SslConfig sslConfig) {
this.sslConfig = sslConfig;
- this.defaultHttpClient = null;
return this;
}
@@ -223,7 +224,6 @@ public VaultConfig sslConfig(final SslConfig sslConfig) {
*/
public VaultConfig openTimeout(final Integer openTimeout) {
this.openTimeout = openTimeout;
- this.defaultHttpClient = null;
return this;
}
@@ -400,7 +400,6 @@ public VaultConfig build() throws VaultException {
this.sslConfig = new SslConfig();
}
this.sslConfig.buildIfNeeded(this.environmentLoader);
- this.defaultHttpClient = null;
return this;
}
@@ -477,8 +476,13 @@ public int getPrefixPathDepth() {
/**
*
Returns the HttpClient used by REST API calls: the one set with
* {@link #httpClient(HttpClient)} if any, otherwise a client built from this config's sslConfig
- * and openTimeout values. The latter is built once and shared by all calls, so connections and
- * worker threads are reused.
+ * and openTimeout values. The latter is shared by all calls, so connections and worker threads
+ * are reused, and is rebuilt whenever those values change (e.g. a new sslConfig, SSL
+ * verification turned on or off, or a new SSLContext from calling {@link SslConfig#build()}
+ * again).
+ *
+ * Certificate settings changed on the sslConfig (e.g. {@link SslConfig#pemUTF8(String)})
+ * only take effect once {@link SslConfig#build()} is called again.
*
* @return The HttpClient used by REST API calls
*/
@@ -486,18 +490,45 @@ public HttpClient getHttpClient() {
if (httpClient != null) {
return httpClient;
}
+ final Boolean verify = sslConfig == null ? null : sslConfig.isVerify();
+ final SSLContext sslContext = sslConfig == null ? null : sslConfig.getSslContext();
var client = defaultHttpClient;
- if (client == null) {
+ if (client == null || !client.matches(openTimeout, verify, sslContext)) {
synchronized (this) {
client = defaultHttpClient;
- if (client == null) {
- client = Rest.newHttpClient(openTimeout,
- sslConfig == null ? null : sslConfig.isVerify(),
- sslConfig == null ? null : sslConfig.getSslContext());
+ if (client == null || !client.matches(openTimeout, verify, sslContext)) {
+ client = new DefaultHttpClient(openTimeout, verify, sslContext);
defaultHttpClient = client;
}
}
}
- return client;
+ return client.httpClient;
+ }
+
+ /**
+ * The default HttpClient, along with the settings it was built from.
+ */
+ private static final class DefaultHttpClient {
+
+ private final HttpClient httpClient;
+ private final Integer openTimeout;
+ private final Boolean verify;
+ private final SSLContext sslContext;
+
+ private DefaultHttpClient(final Integer openTimeout, final Boolean verify,
+ final SSLContext sslContext) {
+ this.httpClient = Rest.newHttpClient(openTimeout, verify, sslContext);
+ this.openTimeout = openTimeout;
+ this.verify = verify;
+ this.sslContext = sslContext;
+ }
+
+ private boolean matches(final Integer openTimeout, final Boolean verify,
+ final SSLContext sslContext) {
+ // SSLContext has no equals(): a new instance means new certificates
+ return Objects.equals(this.openTimeout, openTimeout)
+ && Objects.equals(this.verify, verify)
+ && this.sslContext == sslContext;
+ }
}
}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index 55311681..0085589d 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -13,6 +13,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -593,4 +594,44 @@ public void testSerializationWithCustomHttpClient() throws Exception {
Assert.assertNotSame(httpClient, copy.getHttpClient());
Assert.assertSame(copy.getHttpClient(), copy.getHttpClient());
}
+
+ /**
+ * The default HttpClient is reused while its settings are unchanged, and rebuilt when the
+ * passed SslConfig is changed after VaultConfig.build().
+ */
+ @Test
+ public void testDefaultHttpClientFollowsSslConfigChanges() throws Exception {
+ final String pem;
+ try (InputStream input = this.getClass().getResourceAsStream("/cert.pem")) {
+ pem = new String(input.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ final SslConfig sslConfig = new SslConfig().verify(false);
+ final VaultConfig config = new VaultConfig()
+ .environmentLoader(new MockEnvironmentLoader())
+ .address("https://127.0.0.1:8200")
+ .sslConfig(sslConfig)
+ .build();
+
+ final HttpClient unverified = config.getHttpClient();
+ Assert.assertSame(unverified, config.getHttpClient());
+
+ // Verification turned on, with new certificates: build() creates a new SSLContext
+ sslConfig.verify(true).pemUTF8(pem).build();
+ final HttpClient withPem = config.getHttpClient();
+ Assert.assertNotSame(unverified, withPem);
+ Assert.assertSame(sslConfig.getSslContext(), withPem.sslContext());
+ Assert.assertSame(withPem, config.getHttpClient());
+
+ // Verification turned off, without calling build() again
+ sslConfig.verify(false);
+ final HttpClient unverifiedAgain = config.getHttpClient();
+ Assert.assertNotSame(withPem, unverifiedAgain);
+ Assert.assertNotSame(sslConfig.getSslContext(), unverifiedAgain.sslContext());
+
+ // A new connect timeout
+ config.openTimeout(5);
+ final HttpClient withTimeout = config.getHttpClient();
+ Assert.assertNotSame(unverifiedAgain, withTimeout);
+ assertEquals(Duration.ofSeconds(5), withTimeout.connectTimeout().orElse(null));
+ }
}
From cb1059e6172cfc27b1bb492ebb98363b319f7955 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:05:46 +0200
Subject: [PATCH 23/34] Added code style plugin in Maven
---
pom.xml | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/pom.xml b/pom.xml
index d3e31d1c..ece485af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -128,6 +128,29 @@
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+ ${project.basedir}/config/checkstyle/checkstyle.xml
+ true
+ true
+ true
+ warning
+
+
+
+ checkstyle
+ validate
+
+ check
+
+
+
+
+
org.apache.maven.plugins
From b2844870ab6ce6be938901fbea2d73233323ec19 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:07:49 +0200
Subject: [PATCH 24/34] Rearranged imports
---
src/test/java/io/github/jopenlibs/vault/RetryTests.java | 2 +-
src/test/java/io/github/jopenlibs/vault/SSLTests.java | 4 ++--
.../io/github/jopenlibs/vault/api/AuthBackendAwsTests.java | 4 ++--
.../java/io/github/jopenlibs/vault/api/AuthLookupTest.java | 4 ++--
.../java/io/github/jopenlibs/vault/api/TransitApiTest.java | 4 ++--
.../jopenlibs/vault/api/sys/WrappingLookupWrapTest.java | 4 ++--
.../io/github/jopenlibs/vault/api/sys/WrappingUnwrapTest.java | 4 ++--
.../vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java | 4 ++--
.../io/github/jopenlibs/vault/api/sys/WrappingWrapTest.java | 4 ++--
src/test/java/io/github/jopenlibs/vault/rest/DeleteTests.java | 2 +-
src/test/java/io/github/jopenlibs/vault/rest/GetTests.java | 2 +-
src/test/java/io/github/jopenlibs/vault/rest/PostTests.java | 2 +-
src/test/java/io/github/jopenlibs/vault/rest/PutTests.java | 2 +-
src/test/java/io/github/jopenlibs/vault/util/SSLUtils.java | 2 +-
14 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryTests.java b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
index 4728359e..2b021f9b 100644
--- a/src/test/java/io/github/jopenlibs/vault/RetryTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
@@ -1,7 +1,7 @@
package io.github.jopenlibs.vault;
-import io.github.jopenlibs.vault.response.LogicalResponse;
import io.github.jopenlibs.vault.mock.RetriesMockVault;
+import io.github.jopenlibs.vault.response.LogicalResponse;
import java.util.Map;
import org.junit.Test;
diff --git a/src/test/java/io/github/jopenlibs/vault/SSLTests.java b/src/test/java/io/github/jopenlibs/vault/SSLTests.java
index 432b33d0..fd09cd4c 100644
--- a/src/test/java/io/github/jopenlibs/vault/SSLTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/SSLTests.java
@@ -1,7 +1,7 @@
package io.github.jopenlibs.vault;
-import io.github.jopenlibs.vault.response.LogicalResponse;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.LogicalResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
@@ -10,9 +10,9 @@
import java.security.KeyStore;
import java.util.List;
import java.util.Map;
+import javax.net.ssl.SSLContext;
import org.junit.Test;
-import javax.net.ssl.SSLContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAwsTests.java b/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAwsTests.java
index 9fd1af42..eb0277e3 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAwsTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAwsTests.java
@@ -3,10 +3,10 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
-import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.AuthResponse;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.JsonObject;
import io.github.jopenlibs.vault.mock.AuthRequestValidatingMockVault;
+import io.github.jopenlibs.vault.response.AuthResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.util.function.Predicate;
import org.junit.Test;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/AuthLookupTest.java b/src/test/java/io/github/jopenlibs/vault/api/AuthLookupTest.java
index dcd9751a..f1985d79 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/AuthLookupTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/AuthLookupTest.java
@@ -2,11 +2,11 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
+import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.json.JsonArray;
import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.LookupResponse;
-import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.LookupResponse;
import java.util.Optional;
import org.eclipse.jetty.server.Server;
import org.junit.After;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/TransitApiTest.java b/src/test/java/io/github/jopenlibs/vault/api/TransitApiTest.java
index 07d5c329..9f03a675 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/TransitApiTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/TransitApiTest.java
@@ -2,11 +2,11 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
+import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.json.JsonArray;
import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.LogicalResponse;
-import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.LogicalResponse;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jetty.server.Server;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingLookupWrapTest.java b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingLookupWrapTest.java
index a093fb0c..e11bb33c 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingLookupWrapTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingLookupWrapTest.java
@@ -2,10 +2,10 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
-import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.LogicalResponse;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.JsonObject;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.LogicalResponse;
import java.util.Optional;
import org.eclipse.jetty.server.Server;
import org.junit.After;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapTest.java b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapTest.java
index 69cc85ce..5b4cbae2 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapTest.java
@@ -2,11 +2,11 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
+import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.json.JsonArray;
import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.AuthResponse;
-import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.AuthResponse;
import org.eclipse.jetty.server.Server;
import org.junit.After;
import org.junit.Before;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java
index 15657ee7..4a7db3e8 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java
@@ -2,11 +2,11 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
+import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.UnwrapResponse;
-import io.github.jopenlibs.vault.VaultTestUtils;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.UnwrapResponse;
import org.eclipse.jetty.server.Server;
import org.junit.After;
import org.junit.Test;
diff --git a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingWrapTest.java b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingWrapTest.java
index 86d4e033..06477a0f 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingWrapTest.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/sys/WrappingWrapTest.java
@@ -2,10 +2,10 @@
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
-import io.github.jopenlibs.vault.json.JsonObject;
-import io.github.jopenlibs.vault.response.WrapResponse;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.JsonObject;
import io.github.jopenlibs.vault.mock.MockVault;
+import io.github.jopenlibs.vault.response.WrapResponse;
import org.eclipse.jetty.server.Server;
import org.junit.After;
import org.junit.Before;
diff --git a/src/test/java/io/github/jopenlibs/vault/rest/DeleteTests.java b/src/test/java/io/github/jopenlibs/vault/rest/DeleteTests.java
index c9fe2287..a2a81993 100644
--- a/src/test/java/io/github/jopenlibs/vault/rest/DeleteTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/rest/DeleteTests.java
@@ -1,7 +1,7 @@
package io.github.jopenlibs.vault.rest;
-import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.mock.EchoInputMockVault;
import org.junit.Test;
diff --git a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java
index 54b70cfe..1f1ee75a 100644
--- a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java
@@ -1,7 +1,7 @@
package io.github.jopenlibs.vault.rest;
-import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.mock.EchoInputMockVault;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.server.Server;
diff --git a/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java b/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java
index 68533f3c..7363fd33 100644
--- a/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java
@@ -1,7 +1,7 @@
package io.github.jopenlibs.vault.rest;
-import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.mock.EchoInputMockVault;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.server.Server;
diff --git a/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java b/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java
index 9115b729..15aabbd3 100644
--- a/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java
@@ -1,7 +1,7 @@
package io.github.jopenlibs.vault.rest;
-import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.mock.EchoInputMockVault;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.server.Server;
diff --git a/src/test/java/io/github/jopenlibs/vault/util/SSLUtils.java b/src/test/java/io/github/jopenlibs/vault/util/SSLUtils.java
index ad79c9c7..e2c6d3ae 100644
--- a/src/test/java/io/github/jopenlibs/vault/util/SSLUtils.java
+++ b/src/test/java/io/github/jopenlibs/vault/util/SSLUtils.java
@@ -6,6 +6,7 @@
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -20,7 +21,6 @@
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
-import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
From 398a326481c401a4f96d5a9da7fc1103c2a814cc Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:08:59 +0200
Subject: [PATCH 25/34] Checked if address is null
---
.../java/io/github/jopenlibs/vault/VaultConfig.java | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index bdb64a98..c480c487 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -121,12 +121,18 @@ public VaultConfig engineVersion(final Integer globalEngineVersion) {
* it explicitly AND no environment variable value is found, then initialization of the
* VaultConfig object will fail.
*
+ * A null, empty or blank address is treated as unset.
+ *
* @param address The Vault server base URL
* @return This object, with address populated, ready for additional builder-pattern method
* calls or else finalization with the build() method
*/
public VaultConfig address(final String address) {
- this.address = address.trim();
+ if (address == null || address.isBlank()) {
+ this.address = null;
+ return this;
+ }
+ this.address = address.strip();
if (this.address.endsWith("/")) {
this.address = this.address.substring(0, this.address.length() - 1);
}
@@ -255,7 +261,8 @@ public VaultConfig readTimeout(final Integer readTimeout) {
*/
public VaultConfig prefixPathDepth(int prefixPathDepth) {
if (prefixPathDepth < 1) {
- throw new IllegalArgumentException("pathLength must be > 1");
+ throw new IllegalArgumentException(
+ "prefixPathDepth must be at least 1, was: " + prefixPathDepth);
}
this.prefixPathDepth = prefixPathDepth;
From 83a8fa7d51be984e9378ee868246325d96d2a79d Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:09:58 +0200
Subject: [PATCH 26/34] Better null check
---
.../io/github/jopenlibs/vault/VaultImpl.java | 25 +++++++++++--------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 9a7df5f9..0fc97294 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -118,19 +118,24 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
logNameSpace();
this.vaultConfig.setEngineVersion(globalFallbackVersion);
if (Boolean.TRUE.equals(useSecretsEnginePathMap) && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
+ logger.info(
+ "No secrets Engine version map was supplied, attempting to generate one.");
+ final Map secretsEnginePathMap;
try {
- logger.info(
- "No secrets Engine version map was supplied, attempting to generate one.");
- final Map secretsEnginePathMap = collectSecretEngineVersions();
- assert secretsEnginePathMap != null;
- this.vaultConfig.secretsEnginePathMap(secretsEnginePathMap);
- } catch (Exception e) {
+ secretsEnginePathMap = collectSecretEngineVersions();
+ } catch (RuntimeException e) {
throw new VaultException(String.format(
- "An Engine KV version map was not supplied, and unable to determine " +
- "KV Engine " +
- "version, " + "due to exception: %s",
- e.getMessage() + ". Do you have admin rights?"));
+ "An Engine KV version map was not supplied, and unable to determine KV "
+ + "Engine version, due to exception: %s. Do you have admin rights?",
+ e.getMessage()));
+ }
+ // null if Vault could not be reached or did not answer with HTTP 200
+ if (secretsEnginePathMap == null) {
+ throw new VaultException(
+ "An Engine KV version map was not supplied, and unable to determine KV "
+ + "Engine version. Do you have admin rights?");
}
+ this.vaultConfig.secretsEnginePathMap(secretsEnginePathMap);
}
}
From 1dd56efc358da8bcf9eff64a3ec021cf35bf36cd Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:10:06 +0200
Subject: [PATCH 27/34] Added tests
---
.../jopenlibs/vault/VaultConfigTests.java | 27 +++++++++++++++++++
.../io/github/jopenlibs/vault/VaultTests.java | 24 +++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
index 0085589d..d79b1e3c 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultConfigTests.java
@@ -634,4 +634,31 @@ public void testDefaultHttpClientFollowsSslConfigChanges() throws Exception {
Assert.assertNotSame(unverifiedAgain, withTimeout);
assertEquals(Duration.ofSeconds(5), withTimeout.connectTimeout().orElse(null));
}
+
+ /**
+ * A null or blank address is treated as unset, so VAULT_ADDR is used.
+ */
+ @Test
+ public void testConfigBuilder_NullOrBlankAddressFallsBackToEnv() throws VaultException {
+ final MockEnvironmentLoader mock = new MockEnvironmentLoader();
+ mock.override("VAULT_ADDR", "http://127.0.0.1:8200");
+
+ assertEquals("http://127.0.0.1:8200",
+ new VaultConfig().environmentLoader(mock).address(null).build().getAddress());
+ assertEquals("http://127.0.0.1:8200",
+ new VaultConfig().environmentLoader(mock).address(" \n").build().getAddress());
+ }
+
+ /**
+ * A prefix path depth lower than 1 is rejected with a message stating the valid range.
+ */
+ @Test
+ public void testPrefixPathDepthBelowOneIsRejected() {
+ try {
+ new VaultConfig().prefixPath("/");
+ Assert.fail("Expected an IllegalArgumentException to be thrown");
+ } catch (IllegalArgumentException e) {
+ assertEquals("prefixPathDepth must be at least 1, was: 0", e.getMessage());
+ }
+ }
}
diff --git a/src/test/java/io/github/jopenlibs/vault/VaultTests.java b/src/test/java/io/github/jopenlibs/vault/VaultTests.java
index 299b3a9b..eec1ca93 100644
--- a/src/test/java/io/github/jopenlibs/vault/VaultTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/VaultTests.java
@@ -159,4 +159,28 @@ public void testConfigBuiler_WithInvalidRequestAsNonError() throws Exception {
Assert.assertEquals(403, response.getRestResponse().getStatus());
Assert.assertEquals(0, response.getRetries());
}
+
+ /**
+ * When the KV Engine version map cannot be generated (here, Vault denies access to
+ * sys/mounts), a VaultException with a clear message is thrown.
+ */
+ @Test
+ public void testVaultWithKVEnginePathMapAccessDenied() throws Exception {
+ final var server = VaultTestUtils.initHttpMockVault(new MockVault(403, "{}"));
+ server.start();
+ try {
+ final var vaultConfig = new VaultConfig()
+ .address("http://127.0.0.1:8999")
+ .token("mock_token")
+ .build();
+ Vault.create(vaultConfig, true, 1);
+ Assert.fail("Expected a VaultException to be thrown");
+ } catch (VaultException e) {
+ Assert.assertEquals(
+ "An Engine KV version map was not supplied, and unable to determine KV "
+ + "Engine version. Do you have admin rights?", e.getMessage());
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
}
From e43d405370070b44e976123c059345a7d673ba91 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 7 Oct 2026 18:11:44 +0200
Subject: [PATCH 28/34] Updated README
---
README.md | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 0d1745f6..6b9bd975 100644
--- a/README.md
+++ b/README.md
@@ -331,6 +331,10 @@ final LogicalResponse response = vault.withRetries(5, 1000)
.read("secret/hello");
```
+Only transient failures are retried: I/O errors (e.g. connection refused or timed out) and HTTP 5xx,
+408, 412 and 429 responses. Other 4xx responses (e.g. 400, 403, 404) fail immediately, since
+retrying them would not help.
+
API Reference (Javadocs)
------------------------
Full [Javadoc documentation](https://jopenlibs.github.io/vault-java-driver/javadoc).
@@ -356,7 +360,13 @@ patches.
* A deserialized `SslConfig` now keeps its settings. Previously it had SSL verification disabled
* Each `VaultConfig` now reuses a single HTTP client, so connections are reused between requests
instead of a new client being created for every request. `VaultConfig.getHttpClient()` now
- returns the client in use instead of `null` when none was set
+ returns the client in use instead of `null` when none was set. The client is rebuilt when the
+ `SslConfig` changes (e.g. SSL verification turned on or off, or `SslConfig.build()` called again
+ with new certificates)
+ * A `VaultConfig` with a custom `HttpClient` can be serialized. The client itself is not
+ serialized: the deserialized copy uses its own default client
+ * Query parameters are url-encoded only once. Previously reserved characters (e.g. `/`, `&`, `%`)
+ were encoded twice
* Response bodies are returned exactly as received. Previously non-UTF-8 (e.g. binary) bodies
were corrupted
* Only transient errors are retried: I/O errors and HTTP 5xx, 408, 412 and 429 responses.
@@ -369,9 +379,13 @@ patches.
(`Logical.getEngineVersionForSecretPath()` returns `2` instead of `null`)
* `VaultConfig.nameSpace()` no longer throws `VaultException`: an empty namespace is rejected by
`build()` instead. `VaultConfig.getSecretsEnginePathMap()` returns an unmodifiable map
+ * `VaultConfig.address()` treats a `null` or blank address as unset, falling back to `VAULT_ADDR`,
+ instead of throwing `NullPointerException`
+ * When the KV engine version map cannot be generated (e.g. no access to `sys/mounts`), the
+ `VaultException` message says so, instead of ending in `null`
* Warnings are logged with `java.util.logging` instead of being printed to standard error
* Build: pinned Maven plugin versions, fixed test reports in the GitHub Actions CI, removed the
- unused Travis CI configuration
+ unused Travis CI configuration. Checkstyle now runs in the build, and fails it on violations
* **6.2.3**: This release contains the following updates:
* Improved Java 11 compatibility
* Fixed integration tests with Vault 2.x
From c13827083c3a385573466d6bcaf8acf53c0169c9 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 8 Oct 2026 10:58:56 +0200
Subject: [PATCH 29/34] Added serialization and checked if object is
initialized
---
.../jopenlibs/vault/EnvironmentLoader.java | 3 +
.../io/github/jopenlibs/vault/SslConfig.java | 6 ++
.../vault/SslConfigSerializationTests.java | 56 +++++++++++++++++++
3 files changed, 65 insertions(+)
diff --git a/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java b/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
index f80b79e4..78614fe4 100644
--- a/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
+++ b/src/main/java/io/github/jopenlibs/vault/EnvironmentLoader.java
@@ -12,6 +12,9 @@
*/
public class EnvironmentLoader implements Serializable {
+ // The value computed for release 6.2.3, which declared none: keeps its serialized configs readable
+ private static final long serialVersionUID = 1109558394682383170L;
+
/**
* Loads an environment variable. Surrounding whitespace (e.g. a trailing newline) is removed,
* and an empty or blank value is treated as unset.
diff --git a/src/main/java/io/github/jopenlibs/vault/SslConfig.java b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
index 626d0b65..11d8fd05 100644
--- a/src/main/java/io/github/jopenlibs/vault/SslConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/SslConfig.java
@@ -790,6 +790,12 @@ private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {//NOPMD
// Restore the serialized fields first: buildSsl() depends on them (e.g. verify, pemUTF8)
in.defaultReadObject();
+ // Field initializers don't run on deserialization, so a stream written by an older release
+ // (which has no "built" field) or by a never-built instance may carry verify=false without
+ // it ever being requested. Unless explicitly disabled, verification stays enabled.
+ if (!built && verifyObject == null) {
+ this.verify = true;
+ }
try {
buildSsl();
} catch (VaultException e) {
diff --git a/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java b/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java
index 38864a51..3622bc03 100644
--- a/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/SslConfigSerializationTests.java
@@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
@@ -46,6 +47,61 @@ public void testVerifyDisabledSurvives() throws Exception {
assertFalse(roundTrip(new SslConfig().verify(false).build()).isVerify());
}
+ /**
+ * A stream from an older release, or from a never-built instance, carries verify
+ * set to false without it being requested: verification must stay enabled.
+ */
+ @Test
+ public void testUnbuiltVerifyFalseIsNotRestored() throws Exception {
+ final var config = new SslConfig();
+ final Field verify = SslConfig.class.getDeclaredField("verify");
+ verify.setAccessible(true);
+ verify.setBoolean(config, false);
+
+ assertTrue(roundTrip(config).isVerify());
+ }
+
+ @Test
+ public void testVerifyDisabledByEnvironmentSurvives() throws Exception {
+ final var config = new SslConfig().environmentLoader(new VerifyFalseLoader()).build();
+
+ assertFalse(config.isVerify());
+ assertFalse(roundTrip(config).isVerify());
+ }
+
+ private static class VerifyFalseLoader extends EnvironmentLoader {
+
+ @Override
+ public String loadVariable(final String name) {
+ return "VAULT_SSL_VERIFY".equals(name) ? "false" : null;
+ }
+ }
+
+ /**
+ * Reads an SslConfig serialized by release 6.2.3.
+ */
+ private static SslConfig read623(final String name) throws Exception {
+ try (var in = new ObjectInputStream(SslConfigSerializationTests.class
+ .getResourceAsStream("/serialized-6.2.3/" + name))) {
+ return (SslConfig) in.readObject();
+ }
+ }
+
+ @Test
+ public void testRelease623UnbuiltConfigVerifies() throws Exception {
+ assertTrue(read623("sslconfig-unbuilt.ser").isVerify());
+ }
+
+ @Test
+ public void testRelease623BuiltConfigVerifies() throws Exception {
+ assertTrue(read623("sslconfig-built.ser").isVerify());
+ }
+
+ @Test
+ public void testRelease623VerifyDisabledSurvives() throws Exception {
+ assertFalse(read623("sslconfig-verify-false.ser").isVerify());
+ }
+
@Test
public void testPemCertificateSurvives() throws Exception {
final String pem = certPem();
From e70c3062741d63b68c6965ccfa14bafe992e60a2 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 8 Oct 2026 11:00:00 +0200
Subject: [PATCH 30/34] Updated README
---
README.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6b9bd975..1b12feb5 100644
--- a/README.md
+++ b/README.md
@@ -208,6 +208,11 @@ To disable SSL certificate verification altogether, set `verify(false)`, or set
IS A REAL PRODUCTION SETTING! However, it can be useful in a development or testing server context.
If this value is explicitly set to `false`, then all other SSL config is basically unused.
+Certificate settings changed after calling `build()` only take effect once `build()` is called
+again. An `SslConfig` can be serialized together with its PEM data, but not with its JKS keystore
+and truststore: after deserialization, a JKS-based config verifies the Vault server's certificate
+against the JVM's default trusted CAs instead.
+
#### Java Keystore (JKS) based config
You can provide the driver with a JKS truststore, containing Vault's server-side certificate for
@@ -357,7 +362,10 @@ patches.
* An `SslConfig` passed to `VaultConfig` without calling its `build()` is now built by
`VaultConfig.build()`, and verifies SSL certificates by default. Previously it silently disabled
verification and ignored its certificates
- * A deserialized `SslConfig` now keeps its settings. Previously it had SSL verification disabled
+ * A deserialized `SslConfig` now keeps its settings. Previously it had SSL verification disabled.
+ An `SslConfig` serialized by an older release can still be deserialized, and verifies SSL
+ certificates unless it was serialized with `verify(false)`. This includes one built with
+ `VAULT_SSL_VERIFY=false`, since the serialized data cannot tell where that value came from
* Each `VaultConfig` now reuses a single HTTP client, so connections are reused between requests
instead of a new client being created for every request. `VaultConfig.getHttpClient()` now
returns the client in use instead of `null` when none was set. The client is rebuilt when the
From 5ef4ddde4822e4abdefd2edf537d0d9e39bbd872 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 8 Oct 2026 11:48:01 +0200
Subject: [PATCH 31/34] Added serialized 6.2.3 test fixtures
---
.../serialized-6.2.3/sslconfig-built.ser | Bin 0 -> 336 bytes
.../serialized-6.2.3/sslconfig-unbuilt.ser | Bin 0 -> 277 bytes
.../serialized-6.2.3/sslconfig-verify-false.ser | Bin 0 -> 378 bytes
3 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 src/test/resources/serialized-6.2.3/sslconfig-built.ser
create mode 100644 src/test/resources/serialized-6.2.3/sslconfig-unbuilt.ser
create mode 100644 src/test/resources/serialized-6.2.3/sslconfig-verify-false.ser
diff --git a/src/test/resources/serialized-6.2.3/sslconfig-built.ser b/src/test/resources/serialized-6.2.3/sslconfig-built.ser
new file mode 100644
index 0000000000000000000000000000000000000000..4b00553e9d29b740609f71768c074dd0a637ad9a
GIT binary patch
literal 336
zcmZ|Lu}T9$5C-76fTmH5`UsZdZDS#%i)iD5fisQWUNSl7-0ZBolQW0la|phIuVJ
zc({j%;@=JY{Cv#YJ8Z=ON5pwWa;%attc_wh6YttBIjjL24fm}7#u$y{)=>eW0)3A@Op}=m{Eoe3i3<4*b
zSS5@4wx}V0w_
literal 0
HcmV?d00001
diff --git a/src/test/resources/serialized-6.2.3/sslconfig-unbuilt.ser b/src/test/resources/serialized-6.2.3/sslconfig-unbuilt.ser
new file mode 100644
index 0000000000000000000000000000000000000000..8342c78993e0364f492af241afeedfc357e849c9
GIT binary patch
literal 277
zcmZ9GO=?2O%>a)@iaK=%_m6?)p&8v0vH=wylY
z8jqWg#YAAJ3sV_ojmuRko?JfF(2HCHo!|y74Fhx)d3MLUL9^MR7kJaeDz>O+i+Z%E
v`G5Q_?jNvDg*~oK2p9Id#@#JrPjg>m@G2{CD*~fGjql7BjhkLeC?ER;CevIR
literal 0
HcmV?d00001
diff --git a/src/test/resources/serialized-6.2.3/sslconfig-verify-false.ser b/src/test/resources/serialized-6.2.3/sslconfig-verify-false.ser
new file mode 100644
index 0000000000000000000000000000000000000000..9147badce395232ba09e30d1689facb426404bbf
GIT binary patch
literal 378
zcmZ{g!Ab)$5Qb+LYA=edzJTCSOfDXT-YR-AU}5#rTeh99S<@sY+1N!8A4AYb@Krqe
z7z$oJxos8^#kmdq^UwGH&u`Fi7IuXuh47bEPD*W1Ns&7ecqKi_oIKKMCJKm?q|hD1
zT0kpiHG|$n3RM0S>k-Z`&QA_KY_XCDP9;|b&Ab(=7%j@zcaSjXqYA=mHE$f4
z=2NsD_LN%=*1KyWPrI&1;Iq8MiTAMmgK?;}
zM6L!41I9FE?EPQHAAn8#xIK7y9HsyZv%2mt!;@ur(i*?)*!$O~&-WCPF?0f#6*jNF
E0Pq!oqW}N^
literal 0
HcmV?d00001
From 5305e4a89cbc96435e55fd441a819d31ecea7a5a Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 8 Oct 2026 11:52:23 +0200
Subject: [PATCH 32/34] Added new versions in CI
---
.github/workflows/ci.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6a0dd527..e690a39e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,6 +36,9 @@ jobs:
- 2.0.2
- 2.0.3
- 2.0.4
+ - 2.1.0
+ - 2.1.1
+ - 2.1.2
- latest
os:
- ubuntu-latest
From 89625d61abac68587074d21a516235a9b1f254e6 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 8 Oct 2026 12:24:27 +0200
Subject: [PATCH 33/34] Removed unmaintained versions and deprecated code
---
.github/workflows/ci.yml | 8 -----
README.md | 10 ++++++
.../java/io/github/jopenlibs/vault/Vault.java | 3 ++
.../io/github/jopenlibs/vault/VaultImpl.java | 9 +++++
.../io/github/jopenlibs/vault/api/Auth.java | 10 ++++++
.../jopenlibs/vault/api/sys/Leases.java | 8 ++---
.../jopenlibs/vault/api/sys/Wrapping.java | 36 +++++++++----------
.../vault/api/sys/mounts/Mounts.java | 8 ++---
.../vault/api/AuthBackendAppIdIT.java | 3 ++
.../jopenlibs/vault/api/AuthBackendPkiIT.java | 7 ++++
.../github/jopenlibs/vault/api/LogicalIT.java | 2 ++
.../jopenlibs/vault/util/VaultContainer.java | 4 +++
12 files changed, 74 insertions(+), 34 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e690a39e..39ec971f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,14 +17,6 @@ jobs:
java:
- 11
vault:
- - 1.8.12
- - 1.9.10
- - 1.10.11
- - 1.11.12
- - 1.12.11
- - 1.13.8
- - 1.14.4
- - 1.15.6
- 1.16.3
- 1.17.6
- 1.18.5
diff --git a/README.md b/README.md
index 1b12feb5..c4798530 100644
--- a/README.md
+++ b/README.md
@@ -394,6 +394,16 @@ patches.
* Warnings are logged with `java.util.logging` instead of being printed to standard error
* Build: pinned Maven plugin versions, fixed test reports in the GitHub Actions CI, removed the
unused Travis CI configuration. Checkstyle now runs in the build, and fails it on violations
+ * CI tests Vault 1.16 to 2.1 only: versions 1.8 to 1.15 are deprecated. `Auth.loginByAppID()` is
+ deprecated, since the App ID auth backend is not available in Vault 1.16 and later: use
+ `Auth.loginByAppRole()` instead
+ * Methods documented as deprecated are now also annotated with `@Deprecated`, so using them
+ produces a compiler warning: `Vault.leases()`, `Vault.mounts()`, `Vault.seal()`,
+ `Auth.lookupWrap()`, `Auth.unwrap()`, `Auth.wrap()` and `Auth.rewrap()`. Use their `Sys`
+ equivalents instead (e.g. `vault.sys().leases()`, `vault.sys().wrapping().unwrap()`)
+ * Javadoc examples use the `Sys` methods instead of the deprecated ones. The `Wrapping` examples
+ are fixed: they used `new Vault(config)` instead of `Vault.create(config)`, and a misspelled
+ `lookupWarp()` method
* **6.2.3**: This release contains the following updates:
* Improved Java 11 compatibility
* Fixed integration tests with Vault 2.x
diff --git a/src/main/java/io/github/jopenlibs/vault/Vault.java b/src/main/java/io/github/jopenlibs/vault/Vault.java
index 77ef1894..dffb117f 100644
--- a/src/main/java/io/github/jopenlibs/vault/Vault.java
+++ b/src/main/java/io/github/jopenlibs/vault/Vault.java
@@ -124,6 +124,7 @@ static Vault create(final VaultConfig vaultConfig, final Boolean useSecretsEngin
* @see Sys#leases()
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
Leases leases();
/**
@@ -137,12 +138,14 @@ static Vault create(final VaultConfig vaultConfig, final Boolean useSecretsEngin
* @see Sys#mounts()
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
Mounts mounts();
/**
* @see Sys#seal()
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
Seal seal();
Map getSecretEngineVersions();
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 0fc97294..04b81768 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -249,8 +249,11 @@ public Database database(final String mountPath) {
* Returns the implementing class for Vault's lease operations (e.g. revoke, revoke-prefix).
*
* @return The implementing class for Vault's lease operations (e.g. revoke, revoke-prefix).
+ *
+ * @deprecated This method is deprecated and in future it will be removed
*/
@Override
+ @Deprecated
public Leases leases() {
return new Leases(vaultConfig);
}
@@ -270,8 +273,11 @@ public Debug debug() {
* /v1/sys/mounts/* REST endpoints).
*
* @return the implementing class for Vault's sys mounts operations
+ *
+ * @deprecated This method is deprecated and in future it will be removed
*/
@Override
+ @Deprecated
public Mounts mounts() {
return new Mounts(vaultConfig);
}
@@ -280,8 +286,11 @@ public Mounts mounts() {
* Returns the implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus).
*
* @return The implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus).
+ *
+ * @deprecated This method is deprecated and in future it will be removed
*/
@Override
+ @Deprecated
public Seal seal() {
return new Seal(vaultConfig);
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
index 32fc259e..42017d04 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
@@ -409,6 +409,8 @@ public AuthResponse createToken(final TokenRequest tokenRequest, final String to
* @param userId The user-id used for authentication
* @return The auth token, with additional response metadata
* @throws VaultException If any error occurs, or unexpected response received from Vault
+ * @deprecated The App ID auth backend is no longer available in the Vault versions this driver
+ * is tested against (1.16 and later). Use {@link #loginByAppRole(String, String)} instead.
*/
@Deprecated
public AuthResponse loginByAppID(final String path, final String appId, final String userId)
@@ -1149,6 +1151,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
* @see io.github.jopenlibs.vault.api.sys.Wrapping#lookupWrap()
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public LogicalResponse lookupWrap() throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().lookupWrap(config.getToken(), false);
@@ -1158,6 +1161,7 @@ public LogicalResponse lookupWrap() throws VaultException {
* @see io.github.jopenlibs.vault.api.sys.Wrapping#lookupWrap(char[])
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().lookupWrap(wrappedToken, true);
@@ -1167,6 +1171,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultExcepti
* @see io.github.jopenlibs.vault.api.sys.Wrapping#lookupWrap(char[], boolean)
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
throws VaultException {
final var sys = new Sys(this.config);
@@ -1213,6 +1218,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
* @see io.github.jopenlibs.vault.api.sys.Wrapping#unwrap()
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public UnwrapResponse unwrap() throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().unwrap(config.getToken(), false);
@@ -1222,6 +1228,7 @@ public UnwrapResponse unwrap() throws VaultException {
* @see io.github.jopenlibs.vault.api.sys.Wrapping#unwrap(char[])
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().unwrap(wrappedToken, true);
@@ -1231,6 +1238,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
* @see io.github.jopenlibs.vault.api.sys.Wrapping#unwrap(char[], boolean)
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().unwrap(wrappedToken, inBody);
@@ -1240,6 +1248,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
* @see io.github.jopenlibs.vault.api.sys.Wrapping#wrap(JsonObject, int)
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().wrap(jsonObject, ttlInSec);
@@ -1249,6 +1258,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
* @see io.github.jopenlibs.vault.api.sys.Wrapping#rewrap(char[])
* @deprecated This method is deprecated and in future it will be removed
*/
+ @Deprecated
public WrapResponse rewrap(final char[] wrappedToken) throws VaultException {
final var sys = new Sys(this.config);
return sys.wrapping().rewrap(wrappedToken);
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
index 0e203dc0..10938922 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
@@ -38,7 +38,7 @@ public Leases withNameSpace(final String nameSpace) {
*
*
* {@code
- * final VaultResponse response = vault.leases().revoke("7c63da27-a56b-3e3b-377d-ef74630a6d0b");
+ * final VaultResponse response = vault.sys().leases().revoke("7c63da27-a56b-3e3b-377d-ef74630a6d0b");
* assertEquals(204, response.getRestResponse().getStatus());
* }
*
@@ -77,7 +77,7 @@ public VaultResponse revoke(final String leaseId) throws VaultException {
*
*
* {@code
- * final VaultResponse response = vault.leases().revokePrefix("aws");
+ * final VaultResponse response = vault.sys().leases().revokePrefix("aws");
* assertEquals(204, response.getRestResponse().getStatus());
* }
*
@@ -116,7 +116,7 @@ public VaultResponse revokePrefix(final String prefix) throws VaultException {
*
*
* {@code
- * final VaultResponse response = vault.leases().revokePrefix("aws");
+ * final VaultResponse response = vault.sys().leases().revokePrefix("aws");
* assertEquals(204, response.getRestResponse().getStatus());
* }
*
@@ -150,7 +150,7 @@ public VaultResponse revokeForce(final String prefix) throws VaultException {
*
*
* {@code
- * final VaultResponse response = vault.leases().renew("mongodb/creds/myapp/cd7f9834-b870-9ebc-3da5-27bf9cdc42ad");
+ * final VaultResponse response = vault.sys().leases().renew("mongodb/creds/myapp/cd7f9834-b870-9ebc-3da5-27bf9cdc42ad");
* assertEquals(200, response.getRestResponse().getStatus());
* }
*
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
index 59772bf7..b8823ff9 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
@@ -40,8 +40,8 @@ public Wrapping(final VaultConfig config) {
* {@code
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
- * final Vault vault = new Vault(config);
- * final LogicalResponse response = vault.sys().wrapping().lookupWarp();
+ * final Vault vault = Vault.create(config);
+ * final LogicalResponse response = vault.sys().wrapping().lookupWrap();
* // Then you can validate "path" for example ...
* final String path = response.getData().get("path");
* }
@@ -61,10 +61,10 @@ public LogicalResponse lookupWrap() throws VaultException {
*
* {@code
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
* ...
* final String wrappingToken = "...";
- * final LogicalResponse response = vault.sys().wrapping().lookupWarp(wrappingToken);
+ * final LogicalResponse response = vault.sys().wrapping().lookupWrap(wrappingToken);
* // Then you can validate "path" for example ...
* final String path = response.getData().get("path");
* }
@@ -85,10 +85,10 @@ public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultExcepti
*
* {@code
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
* ...
* final String wrappingToken = "...";
- * final LogicalResponse response = vault.sys().wrapping().lookupWarp(wrappingToken);
+ * final LogicalResponse response = vault.sys().wrapping().lookupWrap(wrappingToken);
* // Then you can validate "path" for example ...
* final String path = response.getData().get("path");
* }
@@ -174,7 +174,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
* final String authToken = "...";
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
*
* final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
* // Data to wrap
@@ -187,7 +187,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
* );
*
* final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken());
- * final JsonObject unwrappedData = response.getData(); // original data
+ * final JsonObject unwrappedData = unwrapResponse.getData(); // original data
* }
*
*
@@ -249,7 +249,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
* {@code
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
* final AuthResponse response = vault.sys().wrapping().unwrap();
* final String unwrappedToken = response.getAuthClientToken();
* }
@@ -287,7 +287,7 @@ public UnwrapResponse unwrap() throws VaultException {
* final String authToken = "...";
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
*
* final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
* // Data to wrap
@@ -300,7 +300,7 @@ public UnwrapResponse unwrap() throws VaultException {
* );
*
* final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken());
- * final JsonObject unwrappedData = response.getData(); // original data
+ * final JsonObject unwrappedData = unwrapResponse.getData(); // original data
* }
*
*
@@ -340,7 +340,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
* final String authToken = "...";
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
*
* final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
* // Data to wrap
@@ -353,7 +353,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
* );
*
* final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken(), true);
- * final JsonObject unwrappedData = response.getData(); // original data
+ * final JsonObject unwrappedData = unwrapResponse.getData(); // original data
* }
*
*
@@ -426,9 +426,9 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
* final String authToken = "...";
* final String wrappingToken = "...";
* final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
- * final Vault vault = new Vault(config);
+ * final Vault vault = Vault.create(config);
*
- * final WrapResponse wrapResponse = vault.auth().wrap(
+ * final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
* // Data to wrap
* new JsonObject()
* .add("foo", "bar")
@@ -438,10 +438,10 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
* 60
* );
* ...
- * final WrapResponse wrapResponse2 = vault.auth().rewrap(wrapResponse.getToken());
+ * final WrapResponse wrapResponse2 = vault.sys().wrapping().rewrap(wrapResponse.getToken());
*
- * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse2.getToken());
- * final JsonObject unwrappedData = response.getData(); // original data
+ * final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse2.getToken());
+ * final JsonObject unwrappedData = unwrapResponse.getData(); // original data
* }
*
*
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
index 4baf21d5..42b655bd 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
@@ -85,7 +85,7 @@ public MountResponse list() throws VaultException {
* .maxLeaseTtl(TimeToLive.of(86400, TimeUnit.SECONDS))
* .description("description for pki engine");
*
- * final MountResponse response = vault.mounts().enable("pki/mount/point/path", MountType.PKI, payload);
+ * final MountResponse response = vault.sys().mounts().enable("pki/mount/point/path", MountType.PKI, payload);
*
* assertEquals(204, response.getRestResponse().getStatus();
* }
@@ -145,7 +145,7 @@ public MountResponse enable(final String path, final MountType type, final Mount
* final VaultConfig config = new VaultConfig.address(...).token(...).build();
* final Vault vault = Vault.create(config);
*
- * final MountResponse response = vault.mounts().disable("pki/mount/point/path");
+ * final MountResponse response = vault.sys().mounts().disable("pki/mount/point/path");
*
* assertEquals(204, response.getRestResponse().getStatus();
* }
@@ -191,7 +191,7 @@ public MountResponse disable(final String path) throws VaultException {
* final VaultConfig config = new VaultConfig.address(...).token(...).build();
* final Vault vault = Vault.create(config);
*
- * final MountResponse response = vault.mounts().read("pki/mount/point/path");
+ * final MountResponse response = vault.sys().mounts().read("pki/mount/point/path");
* final Mount mount = response.getMount();
* final MountConfig mountConfig = mount.getConfig();
* }
@@ -245,7 +245,7 @@ public MountResponse read(final String path) throws VaultException {
* .maxLeaseTtl(TimeToLive.of(12, TimeUnit.HOURS))
* .description("description of pki");
*
- * final MountResponse response = vault.mounts().tune("pki/mount/point/path", configs);
+ * final MountResponse response = vault.sys().mounts().tune("pki/mount/point/path", configs);
*
* assertEquals(204, response.getRestResponse().getStatus();
* }
diff --git a/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAppIdIT.java b/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAppIdIT.java
index e88a39bd..092112ef 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAppIdIT.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/AuthBackendAppIdIT.java
@@ -17,7 +17,10 @@
* Integration tests for the AppId auth backend. According to the Vault documentation, this method
* is deprecated. Also, from Vault 1.11.7 it fails the usage. For this reason, we skip this test if
* Vault version is greater than 1.11.6
+ *
+ * @deprecated CI only tests Vault 1.16 and later, so this test is always skipped there
*/
+@Deprecated
public class AuthBackendAppIdIT {
@ClassRule
diff --git a/src/test/java/io/github/jopenlibs/vault/api/AuthBackendPkiIT.java b/src/test/java/io/github/jopenlibs/vault/api/AuthBackendPkiIT.java
index a25bad60..4353e3b5 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/AuthBackendPkiIT.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/AuthBackendPkiIT.java
@@ -98,6 +98,7 @@ void issueCredentialTemplate(Function pkiResponseFunction) thr
.allowSubdomains(true)
.maxTtl("9h")
);
+ // Deprecated: Vault before 1.13 (no longer tested in CI) returns 204
int statusCode = VaultVersion.lessThan("1.13.0") ? 204 : 200;
int responseCode = createRoleResponse.getRestResponse().getStatus();
TestCase.assertEquals(statusCode, responseCode);
@@ -159,6 +160,7 @@ void issueCredentialWithCsrTemplate(BiFunction pkiResp
.allowSubdomains(true)
.maxTtl("9h")
);
+ // Deprecated: Vault before 1.13 (no longer tested in CI) returns 204
int statusCode = VaultVersion.lessThan("1.13.0") ? 204 : 200;
int responseCode = createRoleResponse.getRestResponse().getStatus();
TestCase.assertEquals(statusCode, responseCode);
@@ -177,6 +179,8 @@ public void testIssueCredentialWithCsr()
throws VaultException, InterruptedException, NoSuchAlgorithmException {
// Test not work in Vault versions less than 1.11.0
+ // Deprecated: true for every pinned Vault version in CI (1.16 and later), but false for
+ // "latest", where this test is skipped
assumeTrue(VaultVersion.greatThan("1.11.0"));
issueCredentialWithCsrTemplate((pki, csr) -> {
@@ -194,6 +198,8 @@ public void testIssueCredentialWithCsrAndPrivateKeyFormat()
throws VaultException, InterruptedException, NoSuchAlgorithmException {
// Test not works in Vault versions less than 1.11.0
+ // Deprecated: true for every pinned Vault version in CI (1.16 and later), but false for
+ // "latest", where this test is skipped
assumeTrue(VaultVersion.greatThan("1.11.0"));
issueCredentialWithCsrTemplate((pki, csr) -> {
@@ -217,6 +223,7 @@ public void testRevocation() throws VaultException, InterruptedException {
.allowSubdomains(true)
.maxTtl("9h")
);
+ // Deprecated: Vault before 1.13 (no longer tested in CI) returns 204
int statusCode = VaultVersion.lessThan("1.13.0") ? 204 : 200;
int responseCode = createRoleResponse.getRestResponse().getStatus();
TestCase.assertEquals(statusCode, responseCode);
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 7ca24b66..c558488f 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/LogicalIT.java
@@ -307,6 +307,8 @@ public void testList() throws VaultException {
*/
@Test
public void testListSubKeys() throws VaultException {
+ // Deprecated: true for every pinned Vault version in CI (1.16 and later), but false for
+ // "latest", where this test is skipped
assumeTrue(VaultVersion.greatThan("1.9.10"));
final Vault vault = container.getRootVault();
diff --git a/src/test/java/io/github/jopenlibs/vault/util/VaultContainer.java b/src/test/java/io/github/jopenlibs/vault/util/VaultContainer.java
index 5f1690d7..526e7070 100644
--- a/src/test/java/io/github/jopenlibs/vault/util/VaultContainer.java
+++ b/src/test/java/io/github/jopenlibs/vault/util/VaultContainer.java
@@ -118,7 +118,11 @@ public void initAndUnsealVault() throws IOException, InterruptedException {
/**
* Prepares the Vault server for testing of the AppID auth backend (i.e. mounts the backend and
* populates test data).
+ *
+ * @deprecated The AppID auth backend is not available in Vault 1.16 and later, the versions
+ * tested in CI
*/
+ @Deprecated
public void setupBackendAppId() throws IOException, InterruptedException {
runCommand("vault", "login", "-ca-cert=" + CONTAINER_CERT_PEMFILE, rootToken);
From 0ac59d2e4c70ff0a27632466969a9f4a1ed72c51 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Tue, 13 Oct 2026 23:41:57 +0200
Subject: [PATCH 34/34] Updated README
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index c4798530..47971243 100644
--- a/README.md
+++ b/README.md
@@ -353,6 +353,9 @@ number) should represent non-breaking changes. The third number represents any v
patches.
* **Unreleased**: This release contains the following updates:
+ * Security: fixes [CVE-2026-109408](https://www.cve.org/CVERecord?id=CVE-2026-109408), where SSL
+ certificate verification could be silently disabled. See the `VAULT_SSL_VERIFY` and `SslConfig`
+ entries below
* Environment variables: surrounding whitespace is removed, and empty values are treated as unset
* `VAULT_SSL_VERIFY` now only disables SSL verification when set to `false`. Previously any value
other than `true` disabled it, including `1`, `yes`, typos and empty values. **Values like `0`,