Skip to content

Commit 670448f

Browse files
committed
Add support for Vault KV Engine version 2.
Add support for Vault 1+ operations. Add support for Namespaces. Add tests for these changes. Increase test coverage.
1 parent f44baa5 commit 670448f

69 files changed

Lines changed: 4740 additions & 3787 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ version '3.1.0'
88
ext.isReleaseVersion = !version.endsWith('SNAPSHOT')
99

1010
compileJava {
11-
sourceCompatibility = 1.7
12-
targetCompatibility = 1.7
11+
sourceCompatibility = 1.8
12+
targetCompatibility = 1.8
1313
}
1414

1515
compileTestJava {
@@ -22,21 +22,21 @@ repositories {
2222
}
2323

2424
dependencies {
25-
compileOnly('org.projectlombok:lombok:1.16.20')
25+
compileOnly('org.projectlombok:lombok:1.18.4')
2626

2727
testCompile('junit:junit:4.12')
28-
testCompile('org.mockito:mockito-core:2.13.0')
29-
testCompile('org.testcontainers:testcontainers:1.5.1')
30-
testCompile('org.eclipse.jetty:jetty-server:9.4.8.v20171121')
28+
testCompile('org.mockito:mockito-core:2.23.4')
29+
testCompile('org.testcontainers:testcontainers:1.6.0')
30+
testCompile('org.eclipse.jetty:jetty-server:9.4.14.v20181114')
3131
testCompile('org.slf4j:slf4j-api:1.7.25')
32-
testCompile('org.bouncycastle:bcprov-jdk15on:1.59')
33-
testCompile('org.bouncycastle:bcpkix-jdk15on:1.59')
32+
testCompile('org.bouncycastle:bcprov-jdk15on:1.60')
33+
testCompile('org.bouncycastle:bcpkix-jdk15on:1.60')
3434

3535
testRuntime('org.slf4j:slf4j-simple:1.7.25')
3636
}
3737

3838
task wrapper(type: Wrapper) {
39-
gradleVersion = '4.2.1'
39+
gradleVersion = '4.10.2'
4040
}
4141

4242
task javadocJar(type: Jar, dependsOn: javadoc) {

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip

src/main/java/com/bettercloud/vault/EnvironmentLoader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.io.Serializable;
5+
import java.nio.charset.StandardCharsets;
56
import java.nio.file.Files;
67
import java.nio.file.Paths;
78

@@ -25,7 +26,7 @@ public String loadVariable(final String name) {
2526
try {
2627
final byte[] bytes = Files.readAllBytes(
2728
Paths.get(System.getProperty("user.home")).resolve(".vault-token"));
28-
value = new String(bytes, "UTF-8").trim();
29+
value = new String(bytes, StandardCharsets.UTF_8).trim();
2930
} catch (IOException e) {
3031
// No-op... there simply isn't a token value available
3132
}

src/main/java/com/bettercloud/vault/SslConfig.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.InputStreamReader;
2020
import java.io.ObjectInputStream;
2121
import java.io.Serializable;
22+
import java.nio.charset.StandardCharsets;
2223
import java.security.KeyFactory;
2324
import java.security.KeyManagementException;
2425
import java.security.KeyStore;
@@ -538,7 +539,7 @@ private SSLContext buildSslContextFromPem() throws VaultException {
538539
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
539540
// Convert the trusted servers PEM data into an X509Certificate
540541
X509Certificate certificate;
541-
try (final ByteArrayInputStream pem = new ByteArrayInputStream(pemUTF8.getBytes("UTF-8"))) {
542+
try (final ByteArrayInputStream pem = new ByteArrayInputStream(pemUTF8.getBytes(StandardCharsets.UTF_8))) {
542543
certificate = (X509Certificate) certificateFactory.generateCertificate(pem);
543544
}
544545
// Build a truststore
@@ -553,7 +554,7 @@ private SSLContext buildSslContextFromPem() throws VaultException {
553554
final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
554555
// Convert the client certificate PEM data into an X509Certificate
555556
X509Certificate clientCertificate;
556-
try (final ByteArrayInputStream pem = new ByteArrayInputStream(clientPemUTF8.getBytes("UTF-8"))) {
557+
try (final ByteArrayInputStream pem = new ByteArrayInputStream(clientPemUTF8.getBytes(StandardCharsets.UTF_8))) {
557558
clientCertificate = (X509Certificate) certificateFactory.generateCertificate(pem);
558559
}
559560

@@ -613,7 +614,7 @@ private KeyStore inputStreamToKeyStore(final InputStream inputStream, final Stri
613614
* @throws IOException
614615
*/
615616
private static String inputStreamToUTF8(final InputStream input) throws IOException {
616-
final BufferedReader in = new BufferedReader(new InputStreamReader(input, "UTF-8"));
617+
final BufferedReader in = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
617618
final StringBuilder utf8 = new StringBuilder("");
618619
String str;
619620
while ((str = in.readLine()) != null) {

src/main/java/com/bettercloud/vault/Vault.java

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66
import com.bettercloud.vault.api.Logical;
77
import com.bettercloud.vault.api.Seal;
88
import com.bettercloud.vault.api.pki.Pki;
9+
import com.bettercloud.vault.json.Json;
10+
import com.bettercloud.vault.json.JsonObject;
11+
import com.bettercloud.vault.json.JsonValue;
12+
import com.bettercloud.vault.rest.Rest;
13+
import com.bettercloud.vault.rest.RestException;
14+
import com.bettercloud.vault.rest.RestResponse;
15+
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.HashMap;
18+
import java.util.Map;
919

1020
/**
1121
* <p>The Vault driver class, the primary interface through which dependent applications will access Vault.</p>
@@ -50,16 +60,63 @@ public class Vault {
5060
* Construct a Vault driver instance with the provided config settings.
5161
*
5262
* @param vaultConfig Configuration settings for Vault interaction (e.g. server address, token, etc)
63+
* If the VaultConfig Engine version path map is not supplied in the config, default to global KV
64+
* engine version 2.
5365
*/
5466
public Vault(final VaultConfig vaultConfig) {
5567
this.vaultConfig = vaultConfig;
68+
if (this.vaultConfig.getSecretsEnginePathMap().isEmpty() && this.vaultConfig.getGlobalEngineVersion() == null) {
69+
System.out.println("Constructing a Vault instance with no provided Engine version, defaulting to version 2.");
70+
this.vaultConfig.setEngineVersion(2);
71+
}
72+
}
73+
74+
/**
75+
* Construct a Vault driver instance with the provided config settings, and use the provided global KV Engine version for all secrets.
76+
*/
77+
public Vault(final VaultConfig vaultConfig, final Integer engineVersion) {
78+
if (engineVersion < 1 || engineVersion > 2) {
79+
throw new IllegalArgumentException("The Engine version must be '1' or '2', the version supplied was: '"
80+
+ engineVersion + "'.");
81+
}
82+
vaultConfig.setEngineVersion(engineVersion);
83+
this.vaultConfig = vaultConfig;
84+
}
85+
86+
/**
87+
* Construct a Vault driver instance with the provided config settings.
88+
*
89+
* @param vaultConfig Configuration settings for Vault interaction (e.g. server address, token, etc)
90+
* If the Secrets engine version path map is not provided, or does not contain the
91+
* requested secret, fall back to the global version supplied.
92+
* @param useSecretsEnginePathMap Whether to use a provided KV Engine version map from the Vault config, or generate one.
93+
* If a secrets KV Engine version map is not supplied, use Vault APIs to determine the
94+
* KV Engine version for each secret. This call requires admin rights.
95+
* @param globalFallbackVersion The Integer version of the KV Engine to use as a global fallback.
96+
*/
97+
public Vault(final VaultConfig vaultConfig, final Boolean useSecretsEnginePathMap, final Integer globalFallbackVersion)
98+
throws VaultException {
99+
this.vaultConfig = vaultConfig;
100+
this.vaultConfig.setEngineVersion(globalFallbackVersion);
101+
if (useSecretsEnginePathMap && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
102+
try {
103+
System.out.println("No secrets Engine version map was supplied, attempting to generate one.");
104+
final Map<String, String> secretsEnginePathMap = collectSecretEngineVersions();
105+
assert secretsEnginePathMap != null;
106+
this.vaultConfig.getSecretsEnginePathMap().putAll(secretsEnginePathMap);
107+
} catch (Exception e) {
108+
throw new VaultException(String.format("An Engine KV version map was not supplied, and unable to determine " +
109+
"KV Engine " +
110+
"version, " + "due to exception: %s", e.getMessage() + ". Do you have admin rights?"));
111+
}
112+
}
56113
}
57114

58115
/**
59116
* This method is chained ahead of endpoints (e.g. <code>logical()</code>, <code>auth()</code>,
60117
* etc... to specify retry rules for any API operations invoked on that endpoint.
61118
*
62-
* @param maxRetries The number of times that API operations will be retried when a failure occurs
119+
* @param maxRetries The number of times that API operations will be retried when a failure occurs
63120
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in between retries
64121
* @return This object, with maxRetries and retryIntervalMilliseconds populated
65122
*/
@@ -146,4 +203,60 @@ public Debug debug() {
146203
public Seal seal() {
147204
return new Seal(vaultConfig);
148205
}
206+
207+
/**
208+
* Makes a REST call to Vault, to collect information on which secret engine version (if any) is used by each available
209+
* mount point. Possibilities are:
210+
*
211+
* <ul>
212+
* <li>"2" - A mount point running on Vault 0.10 or higher, configured to use the engine 2 API</li>
213+
* <li>"1" - A mount point running on Vault 0.10 or higher, configured to use the engine 1 API</li>
214+
* <li>"unknown" - A mount point running on an older version of Vault. Can more or less be treated as "1".</li>
215+
* </ul>
216+
* <p>
217+
* IMPORTANT: Whichever authentication mechanism is being used with the <code>VaultConfig</code> object, that principal
218+
* needs permission to access the <code>/v1/sys/mounts</code> REST endpoint.
219+
*
220+
* @return A map of mount points (e.g. "/secret") to secret engine version numbers (e.g. "2")
221+
*/
222+
private Map<String, String> collectSecretEngineVersions() {
223+
try {
224+
final RestResponse restResponse = new Rest()//NOPMD
225+
.url(vaultConfig.getAddress() + "/v1/sys/mounts")
226+
.header("X-Vault-Token", vaultConfig.getToken())
227+
.connectTimeoutSeconds(vaultConfig.getOpenTimeout())
228+
.readTimeoutSeconds(vaultConfig.getReadTimeout())
229+
.sslVerification(vaultConfig.getSslConfig().isVerify())
230+
.sslContext(vaultConfig.getSslConfig().getSslContext())
231+
.get();
232+
if (restResponse.getStatus() != 200) {
233+
return null;
234+
}
235+
236+
final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8);
237+
final Map<String, String> data = new HashMap<>();
238+
final JsonObject jsonData = Json.parse(jsonString).asObject().get("data").asObject();
239+
for (JsonObject.Member member : jsonData) {
240+
final String name = member.getName();
241+
String version = "unknown";
242+
243+
final JsonValue options = member.getValue().asObject().get("options");
244+
if (options != null && options.isObject()) {
245+
final JsonValue ver = options.asObject().get("version");
246+
if (ver != null && ver.isString()) {
247+
version = ver.asString();
248+
}
249+
}
250+
data.put(name, version);
251+
}
252+
return data;
253+
} catch (RestException e) {
254+
System.err.print(String.format("Unable to retrieve the KV Engine secrets, due to exception: %s", e.getMessage()));
255+
return null;
256+
}
257+
}
258+
259+
public Map<String, String> getSecretEngineVersions() {
260+
return this.collectSecretEngineVersions();
261+
}
149262
}

src/main/java/com/bettercloud/vault/VaultConfig.java

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import lombok.Getter;
44

55
import java.io.Serializable;
6+
import java.util.Map;
7+
import java.util.concurrent.ConcurrentHashMap;
68

79
/**
810
* <p>A container for the configuration settings needed to initialize a <code>Vault</code> driver instance.</p>
@@ -30,13 +32,24 @@ public class VaultConfig implements Serializable {
3032
private static final String VAULT_OPEN_TIMEOUT = "VAULT_OPEN_TIMEOUT";
3133
private static final String VAULT_READ_TIMEOUT = "VAULT_READ_TIMEOUT";
3234

33-
@Getter private String address;
34-
@Getter private String token;
35-
@Getter private SslConfig sslConfig;
36-
@Getter private Integer openTimeout;
37-
@Getter private Integer readTimeout;
38-
@Getter private int maxRetries;
39-
@Getter private int retryIntervalMilliseconds;
35+
@Getter
36+
private Map<String, String> secretsEnginePathMap = new ConcurrentHashMap<>();
37+
@Getter
38+
private String address;
39+
@Getter
40+
private String token;
41+
@Getter
42+
private SslConfig sslConfig;
43+
@Getter
44+
private Integer openTimeout;
45+
@Getter
46+
private Integer readTimeout;
47+
@Getter
48+
private int maxRetries;
49+
@Getter
50+
private int retryIntervalMilliseconds;
51+
@Getter
52+
private Integer globalEngineVersion;
4053
private EnvironmentLoader environmentLoader;
4154

4255
/**
@@ -48,7 +61,7 @@ public class VaultConfig implements Serializable {
4861
* constructor. This method's access level was therefore originally set to <code>protected</code>, but was bumped
4962
* up to <code>public</code> due to community request for the ability to disable environment loading altogether
5063
* (see https://github.com/BetterCloud/vault-java-driver/issues/77).
51-
*
64+
* <p>
5265
* Note that if you do override this, however, then obviously all of the environment checking discussed in the
5366
* documentation becomes disabled.
5467
*
@@ -60,6 +73,20 @@ public VaultConfig environmentLoader(final EnvironmentLoader environmentLoader)
6073
return this;
6174
}
6275

76+
/**
77+
* <p>Sets the KV Secrets Engine version of the Vault server instance.
78+
*
79+
* <p>If no version is explicitly set, it will be defaulted to version 2, the current version.</p>
80+
*
81+
* @param globalEngineVersion The Vault KV Secrets Engine version
82+
* @return This object, with KV Secrets Engine version populated, ready for additional builder-pattern method calls or else
83+
* finalization with the build() method
84+
*/
85+
public VaultConfig engineVersion(final Integer globalEngineVersion) {
86+
this.globalEngineVersion = globalEngineVersion;
87+
return this;
88+
}
89+
6390
/**
6491
* <p>Sets the address (URL) of the Vault server instance to which API calls should be sent.
6592
* E.g. <code>http://127.0.0.1:8200</code>.</p>
@@ -95,6 +122,20 @@ public VaultConfig token(final String token) {
95122
return this;
96123
}
97124

125+
/**
126+
* <p>Sets the secrets Engine paths used by Vault.</p>
127+
*
128+
* @param secretEngineVersions paths to use for accessing Vault secrets.
129+
* Key: secret path, value: Engine version to use.
130+
* Example map: "/secret/foo" , "1",
131+
* "/secret/bar", "2"
132+
* @return This object, with secrets paths populated, ready for additional builder-pattern method calls or else finalization with the build() method
133+
*/
134+
VaultConfig secretsEnginePathMap(final Map<String, String> secretEngineVersions) {
135+
this.secretsEnginePathMap = secretEngineVersions;
136+
return this;
137+
}
138+
98139
/**
99140
* <p>A container for SSL-related configuration options (e.g. certificates).</p>
100141
*
@@ -150,7 +191,7 @@ public VaultConfig readTimeout(final Integer readTimeout) {
150191
*
151192
* @param maxRetries The number of times that API operations will be retried when a failure occurs.
152193
*/
153-
protected void setMaxRetries(final int maxRetries) {
194+
void setMaxRetries(final int maxRetries) {
154195
this.maxRetries = maxRetries;
155196
}
156197

@@ -164,10 +205,20 @@ protected void setMaxRetries(final int maxRetries) {
164205
*
165206
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in between retries.
166207
*/
167-
protected void setRetryIntervalMilliseconds(final int retryIntervalMilliseconds) {
208+
void setRetryIntervalMilliseconds(final int retryIntervalMilliseconds) {
168209
this.retryIntervalMilliseconds = retryIntervalMilliseconds;
169210
}
170211

212+
/**
213+
* <p>Sets the global Engine version for this Vault Config instance. If no KV Engine version map is provided, use this version
214+
* globally.</p>
215+
* If the provided KV Engine version map does not contain a requested secret, or when writing new secrets, fall back to this version.
216+
*
217+
* @param engineVersion The version of the Vault KV Engine to use globally.
218+
*/
219+
void setEngineVersion(final Integer engineVersion) {
220+
this.globalEngineVersion = engineVersion;
221+
}
171222

172223
/**
173224
* <p>This is the terminating method in the builder pattern. The method that validates all of the fields that

0 commit comments

Comments
 (0)