diff --git a/README.md b/README.md
index 0340035..f0265ce 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ Java Vault Connector is a connector library for [Vault](https://www.vaultproject
* Write secrets
* List secrets
* Connector Factory with builder pattern
-* Tested against Vault 0.6.0
+* Tested against Vault 0.6.1
**Usage Example**
@@ -38,7 +38,7 @@ String secret = vault.readSecret("some/secret/key").getValue();
de.stklcode.jvault
connector
- 0.1.1
+ 0.2.0
```
diff --git a/pom.xml b/pom.xml
index 6bc1f44..8bfd2d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,14 +6,14 @@
de.stklcode.jvault
connector
- 0.1.1
+ 0.2.0
org.apache.maven.plugins
maven-compiler-plugin
- 3.3
+ 3.5.1
1.8
1.8
@@ -24,36 +24,31 @@
jar
-
- commons-io
- commons-io
- 2.4
-
org.apache.httpcomponents
httpcore
- 4.0.1
+ 4.4.5
org.apache.httpcomponents
httpclient
- 4.0.2
+ 4.5.2
com.fasterxml.jackson.core
jackson-core
- 2.7.2
+ 2.8.1
com.fasterxml.jackson.core
jackson-databind
- 2.7.2
+ 2.8.1
junit
junit
- 4.11
+ 4.12
test
diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
index 0be52bd..b64659b 100644
--- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
+++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
@@ -6,21 +6,23 @@ import de.stklcode.jvault.connector.exception.*;
import de.stklcode.jvault.connector.model.AuthBackend;
import de.stklcode.jvault.connector.model.response.*;
import de.stklcode.jvault.connector.model.response.embedded.AuthMethod;
-import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HTTP;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
@@ -45,11 +47,11 @@ public class HTTPVaultConnector implements VaultConnector {
private final ObjectMapper jsonMapper;
- private final HttpClient httpClient; /* HTTP client for connection */
private final String baseURL; /* Base URL of Vault */
private boolean authorized = false; /* authorization status */
private String token; /* current token */
+ private long tokenTTL = 0; /* expiration time for current token */
/**
* Create connector using hostname and schema.
@@ -90,13 +92,13 @@ public class HTTPVaultConnector implements VaultConnector {
*/
public HTTPVaultConnector(String baseURL) {
this.baseURL = baseURL;
- this.httpClient = new DefaultHttpClient();
this.jsonMapper = new ObjectMapper();
}
@Override
public void resetAuth() {
token = null;
+ tokenTTL = 0;
authorized = false;
}
@@ -108,6 +110,9 @@ public class HTTPVaultConnector implements VaultConnector {
} catch (VaultConnectorException | IOException e) {
e.printStackTrace();
return null;
+ } catch (URISyntaxException ignored) {
+ /* this should never occur and may leak sensible information */
+ return null;
}
}
@@ -124,10 +129,10 @@ public class HTTPVaultConnector implements VaultConnector {
@Override
public SealResponse unseal(final String key, final Boolean reset) {
- Map param = new HashMap<>();
+ Map param = new HashMap<>();
param.put("key", key);
if (reset != null)
- param.put("reset", reset);
+ param.put("reset", reset.toString());
try {
String response = requestPut(PATH_UNSEAL, param);
return jsonMapper.readValue(response, SealResponse.class);
@@ -139,7 +144,7 @@ public class HTTPVaultConnector implements VaultConnector {
@Override
public boolean isAuthorized() {
- return authorized;
+ return authorized && (tokenTTL == 0 || tokenTTL >= System.currentTimeMillis());
}
@Override
@@ -154,9 +159,12 @@ public class HTTPVaultConnector implements VaultConnector {
String response = requestGet(PATH_AUTH, new HashMap<>());
/* Parse response */
AuthMethodsResponse amr = jsonMapper.readValue(response, AuthMethodsResponse.class);
- return amr.getSupportedMethods().stream().map(AuthMethod::getType).collect(Collectors.toList());
+ return amr.getSupportedMethods().values().stream().map(AuthMethod::getType).collect(Collectors.toList());
} catch (IOException e) {
throw new InvalidResponseException("Unable to parse response", e);
+ } catch (URISyntaxException ignored) {
+ /* this should never occur and may leak sensible information */
+ throw new InvalidRequestException("Invalid URI format.");
}
}
@@ -164,6 +172,7 @@ public class HTTPVaultConnector implements VaultConnector {
public TokenResponse authToken(final String token) throws VaultConnectorException {
/* set token */
this.token = token;
+ this.tokenTTL = 0;
try {
String response = requestPost(PATH_TOKEN_LOOKUP, new HashMap<>());
TokenResponse res = jsonMapper.readValue(response, TokenResponse.class);
@@ -185,6 +194,7 @@ public class HTTPVaultConnector implements VaultConnector {
AuthResponse upr = jsonMapper.readValue(response, AuthResponse.class);
/* verify response */
this.token = upr.getAuth().getClientToken();
+ this.tokenTTL = System.currentTimeMillis() + upr.getAuth().getLeaseDuration() * 1000L;
this.authorized = true;
return upr;
} catch (IOException e) {
@@ -204,6 +214,7 @@ public class HTTPVaultConnector implements VaultConnector {
AuthResponse auth = jsonMapper.readValue(response, AuthResponse.class);
/* verify response */
this.token = auth.getAuth().getClientToken();
+ this.tokenTTL = System.currentTimeMillis() + auth.getAuth().getLeaseDuration() * 1000L;
this.authorized = true;
return auth;
} catch (IOException e) {
@@ -250,6 +261,9 @@ public class HTTPVaultConnector implements VaultConnector {
return jsonMapper.readValue(response, SecretResponse.class);
} catch (IOException e) {
throw new InvalidResponseException("Unable to parse response", e);
+ } catch (URISyntaxException ignored) {
+ /* this should never occur and may leak sensible information */
+ throw new InvalidRequestException("Invalid URI format.");
}
}
@@ -258,12 +272,15 @@ public class HTTPVaultConnector implements VaultConnector {
if (!isAuthorized())
throw new AuthorizationRequiredException();
- String response = requestGet(PATH_SECRET + "/" + path + "/?list=true", new HashMap<>());
try {
+ String response = requestGet(PATH_SECRET + "/" + path + "/?list=true", new HashMap<>());
SecretListResponse secrets = jsonMapper.readValue(response, SecretListResponse.class);
return secrets.getKeys();
} catch (IOException e) {
throw new InvalidResponseException("Unable to parse response", e);
+ } catch (URISyntaxException ignored) {
+ /* this should never occur and may leak sensible information */
+ throw new InvalidRequestException("Invalid URI format.");
}
}
@@ -284,7 +301,7 @@ public class HTTPVaultConnector implements VaultConnector {
* @param path URL path (relative to base)
* @param payload Map of payload values (will be converted to JSON)
* @return HTTP response
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on connection error
*/
private String requestPost(final String path, final Map payload) throws VaultConnectorException {
/* Initialize post */
@@ -292,8 +309,8 @@ public class HTTPVaultConnector implements VaultConnector {
/* generate JSON from payload */
StringEntity input;
try {
- input = new StringEntity(jsonMapper.writeValueAsString(payload), HTTP.UTF_8);
- } catch (UnsupportedEncodingException | JsonProcessingException e) {
+ input = new StringEntity(jsonMapper.writeValueAsString(payload), StandardCharsets.UTF_8);
+ } catch (JsonProcessingException e) {
throw new InvalidRequestException("Unable to parse response", e);
}
input.setContentEncoding("UTF-8");
@@ -311,9 +328,9 @@ public class HTTPVaultConnector implements VaultConnector {
* @param path URL path (relative to base)
* @param payload Map of payload values (will be converted to JSON)
* @return HTTP response
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on connection error
*/
- private String requestPut(final String path, final Map payload) throws VaultConnectorException {
+ private String requestPut(final String path, final Map payload) throws VaultConnectorException {
/* Initialize post */
HttpPut put = new HttpPut(baseURL + path);
/* generate JSON from payload */
@@ -337,15 +354,15 @@ public class HTTPVaultConnector implements VaultConnector {
* @param path URL path (relative to base)
* @param payload Map of payload values (will be converted to JSON)
* @return HTTP response
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on connection error
*/
- private String requestGet(final String path, final Map payload) throws VaultConnectorException {
- /* Initialize post */
- HttpGet get = new HttpGet(baseURL + path);
- /* Parse parameters */
- HttpParams params = new BasicHttpParams();
- payload.forEach(params::setParameter);
- get.setParams(params);
+ private String requestGet(final String path, final Map payload) throws VaultConnectorException, URISyntaxException {
+ /* Add parameters to URI */
+ URIBuilder uriBuilder = new URIBuilder(baseURL + path);
+ payload.forEach(uriBuilder::addParameter);
+
+ /* Initialize request */
+ HttpGet get = new HttpGet(uriBuilder.build());
/* Set X-Vault-Token header */
if (token != null)
@@ -358,21 +375,24 @@ public class HTTPVaultConnector implements VaultConnector {
* Execute prepared HTTP request and return result
* @param base Prepares Request
* @return HTTP response
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on connection error
*/
private String request(HttpRequestBase base) throws VaultConnectorException {
/* Set JSON Header */
base.addHeader("accept", "application/json");
HttpResponse response = null;
- try {
+ try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
response = httpClient.execute(base);
/* Check if response is valid */
if (response == null)
throw new InvalidResponseException("Response unavailable");
+
switch (response.getStatusLine().getStatusCode()) {
case 200:
- return IOUtils.toString(response.getEntity().getContent());
+ try(BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) {
+ return br.lines().collect(Collectors.joining("\n"));
+ } catch (IOException ignored) { }
case 204:
return "";
case 403:
@@ -380,19 +400,18 @@ public class HTTPVaultConnector implements VaultConnector {
default:
InvalidResponseException ex = new InvalidResponseException("Invalid response code")
.withStatusCode(response.getStatusLine().getStatusCode());
- try {
- /* Try to parse error response */
- ErrorResponse er = jsonMapper.readValue(IOUtils.toString(response.getEntity().getContent()),
- ErrorResponse.class);
- /* Check for "permission denied" response */
- if (er.getErrors().size() > 0 && er.getErrors().get(0).equals("permission denied"))
- throw new PermissionDeniedException();
-
- throw ex.withResponse(er.toString());
- }
- catch (IOException e) {
- throw ex;
+ if (response.getEntity() != null) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) {
+ String responseString = br.lines().collect(Collectors.joining("\n"));
+ ErrorResponse er = jsonMapper.readValue(responseString, ErrorResponse.class);
+ /* Check for "permission denied" response */
+ if (er.getErrors().size() > 0 && er.getErrors().get(0).equals("permission denied"))
+ throw new PermissionDeniedException();
+ throw ex.withResponse(er.toString());
+ } catch (IOException ignored) {
+ }
}
+ throw ex;
}
} catch (IOException e) {
throw new InvalidResponseException("Unable to read response", e);
@@ -400,7 +419,7 @@ public class HTTPVaultConnector implements VaultConnector {
finally {
if (response != null && response.getEntity() != null)
try {
- response.getEntity().consumeContent();
+ EntityUtils.consume(response.getEntity());
} catch (IOException ignored) {
}
}
diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
index 7124765..d09ec03 100644
--- a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
+++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
@@ -60,6 +60,7 @@ public interface VaultConnector {
/**
* Get all availale authentication backends.
* @return List of backends
+ * @throws VaultConnectorException on error
*/
List getAuthBackends() throws VaultConnectorException;
@@ -67,6 +68,7 @@ public interface VaultConnector {
* Authorize to Vault using token.
* @param token The token
* @return Token response
+ * @throws VaultConnectorException on error
*/
TokenResponse authToken(final String token) throws VaultConnectorException;
@@ -75,7 +77,7 @@ public interface VaultConnector {
* @param username The username
* @param password The password
* @return Authorization result
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on error
*/
AuthResponse authUserPass(final String username, final String password) throws VaultConnectorException;
@@ -84,6 +86,7 @@ public interface VaultConnector {
* @param appID The App ID
* @param userID The User ID
* @return TRUE on success
+ * @throws VaultConnectorException on error
*/
AuthResponse authAppId(final String appID, final String userID) throws VaultConnectorException;
@@ -93,7 +96,7 @@ public interface VaultConnector {
* @param policy The policy to associate with
* @param displayName Arbitrary name to display
* @return TRUE on success
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on error
*/
boolean registerAppId(final String appID, final String policy, final String displayName) throws VaultConnectorException;
@@ -102,7 +105,7 @@ public interface VaultConnector {
* @param appID The App-ID
* @param userID The User-ID
* @return TRUE on success
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on error
*/
boolean registerUserId(final String appID, final String userID) throws VaultConnectorException;
@@ -113,7 +116,7 @@ public interface VaultConnector {
* @param displayName Arbitrary name to display
* @param userID The User-ID
* @return TRUE on success
- * @throws VaultConnectorException
+ * @throws VaultConnectorException on error
*/
default boolean registerAppUserId(final String appID, final String policy, final String displayName, final String userID) throws VaultConnectorException {
return registerAppId(appID, policy, userID) && registerUserId(appID, userID);
@@ -129,6 +132,7 @@ public interface VaultConnector {
* Retrieve secret form Vault.
* @param key Secret identifier
* @return Secret response
+ * @throws VaultConnectorException on error
*/
SecretResponse readSecret(final String key) throws VaultConnectorException;
@@ -136,6 +140,7 @@ public interface VaultConnector {
* List available secrets from Vault.
* @param path Root path to search
* @return List of secret keys
+ * @throws VaultConnectorException on error
*/
List listSecrets(final String path) throws VaultConnectorException;
@@ -144,6 +149,7 @@ public interface VaultConnector {
* @param key Secret path
* @param value Secret value
* @return TRUE on success
+ * @throws VaultConnectorException on error
*/
boolean writeSecret(final String key, final String value) throws VaultConnectorException;
}
diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java
index 9f7b11a..425e4d2 100644
--- a/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java
+++ b/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java
@@ -1,11 +1,12 @@
package de.stklcode.jvault.connector.model.response;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.ObjectMapper;
import de.stklcode.jvault.connector.exception.InvalidResponseException;
import de.stklcode.jvault.connector.model.response.embedded.AuthMethod;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.IOException;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -14,19 +15,27 @@ import java.util.Map;
* @author Stefan Kalscheuer
* @since 0.1
*/
-public class AuthMethodsResponse implements VaultResponse {
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AuthMethodsResponse extends VaultDataResponse {
+ private Map supportedMethods;
- private List supportedMethods;
-
- @JsonAnySetter
- public void setMethod(String path, Map data) throws InvalidResponseException {
- if (supportedMethods == null)
- supportedMethods = new ArrayList<>();
-
- supportedMethods.add(new AuthMethod(path, data.get("description"), data.get("type")));
+ public AuthMethodsResponse() {
+ this.supportedMethods = new HashMap<>();
}
- public List getSupportedMethods() {
+ @Override
+ public void setData(Map data) throws InvalidResponseException {
+ ObjectMapper mapper = new ObjectMapper();
+ for (String path : data.keySet()) {
+ try {
+ this.supportedMethods.put(path, mapper.readValue(mapper.writeValueAsString(data.get(path)), AuthMethod.class));
+ } catch (IOException e) {
+ throw new InvalidResponseException();
+ }
+ }
+ }
+
+ public Map getSupportedMethods() {
return supportedMethods;
}
}
diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java
index 663dcb1..89bbb37 100644
--- a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java
+++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java
@@ -1,8 +1,11 @@
package de.stklcode.jvault.connector.model.response.embedded;
-
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
import de.stklcode.jvault.connector.model.AuthBackend;
+import java.util.Map;
+
/**
* Embedded authentication method response.
*
@@ -12,12 +15,15 @@ import de.stklcode.jvault.connector.model.AuthBackend;
public class AuthMethod {
private AuthBackend type;
private String rawType;
- private String path;
+
+ @JsonProperty("description")
private String description;
- public AuthMethod(String path, String description, String type) {
- this.path = path;
- this.description = description;
+ @JsonProperty("config")
+ private Map config;
+
+ @JsonSetter("type")
+ public void setType(String type) {
this.rawType = type;
this.type = AuthBackend.forType(type);
}
@@ -30,11 +36,11 @@ public class AuthMethod {
return rawType;
}
- public String getPath() {
- return path;
- }
-
public String getDescription() {
return description;
}
+
+ public Map getConfig() {
+ return config;
+ }
}