Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
ed2b9d62a3 | |||
007b523295 | |||
061c1e9743 | |||
6904ed6817 | |||
1ed5d8d992 | |||
f70fc084be | |||
4b14ab3f4b | |||
29776f459e | |||
4ca8aa56d2 | |||
32ab9f4bb1 | |||
e002fc749a | |||
b9ad2d1551 | |||
35a8c2e0fa | |||
89f7581d17 | |||
43511dc20b | |||
05b44759c0 | |||
ba17286ab3 | |||
51e505313a | |||
a1784245a3 | |||
df7de5dd73 | |||
23419e94f1 | |||
5b34cfcc27 | |||
b1c78b50d2 | |||
238ac8bd10 | |||
c8a2e0784f | |||
23f98f190b | |||
745ab7a24c | |||
71f68f088f | |||
52a1abfb87 | |||
4bf9df5f73 | |||
8ae4dccdd6 | |||
7ac550230f | |||
c1d519826c | |||
a727ed960b | |||
c685ec82a0 | |||
934628f382 | |||
ce90f9fba2 |
21
.travis.yml
21
.travis.yml
@ -5,9 +5,24 @@ language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
dist: trusty
|
||||
install: true
|
||||
addons:
|
||||
sonarcloud:
|
||||
organization: "stklcode-github"
|
||||
token:
|
||||
secure: "sM9OfX5jW764pn9cb2LSXArnXucKMws+eGeg5NnZxHRcGYt4hpBKLSregBSsBNzUoWVj0zNzPCpnh+UQvgxQzUerOqwEdjTBpy3SNPaxSn7UpoSg+Wz3aUmL9ugmx01b51/wMG4UCHEwTZt2tpgTPVtw8K6uSO78e0dSICCBHDnRcdQwOjMEQHIJJ/qHVRwuy/MzLCAP3W1JPZlsphZg9QsFyhB4hW97dE90joZezfocQIv2xI/r6k+BLz0pY6MxYCul0RiDumaiaej0CPvEJI/uSu//BAQjUdHw+mQgnKUYIbrn2ONOviwNfwdr94JyoZEN2B6zASUmNLjPf4AbIojDeyS+CrpQpm17EVm/Qk/Ds+Xra4PPPIcsZhiWzV0KoDUz9xLfXuRJ526VT5tDPiaeI7oETf0+8l+JIS1b399FyqHi7smzjpvC6GuKflQrbuHK4MuKzDh7WTHiqokGG4SS0wOQIaaHB3dfdwwQzPh6IM24e8CETxh3DjMeqUTU4DWmv5po55jZ934TtxVQvVN78bTG9O0zS9u+JmRY04OZ+OaXuFam6MfMUFQi0EPZzdGul/oWSibGUu3bNfVEBp60CnJwYNM/dKG6U7pJthLHvSwiQFOdKzHZ+l1jZJ4gPaXaIGqpwqVGr28ntqA/El1rytPixr2driE6bYMt5jw="
|
||||
env:
|
||||
- PATH=$PATH:.
|
||||
before_script:
|
||||
- wget https://releases.hashicorp.com/vault/0.7.3/vault_0.7.3_linux_amd64.zip
|
||||
- unzip vault_0.7.3_linux_amd64.zip
|
||||
- rm vault_0.7.3_linux_amd64.zip
|
||||
- wget https://releases.hashicorp.com/vault/0.8.3/vault_0.8.3_linux_amd64.zip
|
||||
- unzip vault_0.8.3_linux_amd64.zip
|
||||
- rm vault_0.8.3_linux_amd64.zip
|
||||
cache:
|
||||
directories:
|
||||
- '$HOME/.m2/repository'
|
||||
- '$HOME/.sonar/cache'
|
||||
script:
|
||||
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar
|
||||
notifications:
|
||||
slack:
|
||||
secure: "YyE5GePOLkCVTtCy8j507BRmQrtrWhtvmUt4kY0Z2/ptf0LzfuDEJQ4ZbCxO5ri5IDJrrvyPAedjft818+bMzdFfxvi1oviIL+LZNhyev8gfeIBF/U2pvSLGKCRX4g4aZ6NKN3Untjdm8lmiVTltOyZ59JizQVwXzAl3LiOpnJugyBqbhOx4EIqBzwW3gaYAofMqY2LczW5W/M+99HJCst8Mb8H06GstCPEHCizAq7VRaUS68PstlxQMV0Q6bsSYMLFbLWmhuXs96WHqOrT+nNsl07ikr3N8c4HafhFutt2Jyc1+8gXO417+eSvVM0iBpHGwTmfGFfCqx/4Pf62DTJuvh8dR4fLgLDiqEeDrBEcRRDOs9cvXVOO22NN1HuBBJY8VRiFcwNAvuVMXCtnC+1RJRAZB2zubsANiFe+ygk/ywj37cVXY+NpqlBwcSph6jPHo2hD6cIl2rTWn1EnZH519Rh38xTSv6MRzAO9kWNVrAlX+UtvYS8Sk7Owrc0tET9Lc4zj6aI5tsA1wYbN3Jk6EbMhsF6K/XF2npt2qg09pxkj8wmxoUoR6/rGuSv55aSxTdLDmH+en4ahEm3uc4h1lYoVCk0yrZoTAas3zS4WpBCKnl+mweuKNxaejyy0Wv6NR9ZCTaS3yFgibNOjvDpxZxTAPdNBL7hn+k4LwgN4="
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,17 @@
|
||||
## 0.7.0 [2017-10-03]
|
||||
* [feature] Retrieval of health status via `getHealth()` (#15)
|
||||
* [improvement] `seal()`, `unseal()` are now `void` and throw Exception on error (#12)
|
||||
* [compatibility] Adaptation to Vault 0.8 endpoints for `renew` and `revoke`, **breaking** 0.7 compatibility (#11)
|
||||
* [deletion] Removed deprecated `listAppRoleSecretss()` (use `listAppRoleSecrets()`) (#14)
|
||||
* [test] Tested against Vault 0.8.3
|
||||
|
||||
## 0.6.2 [2017-08-19]
|
||||
* [fix] Prevent potential NPE on SecretResponse getter
|
||||
* [fix] Removed stack traces on PUT request and response deserialization (#13)
|
||||
* [improvement] Fields of InvalidResposneException made final
|
||||
* [deprecation] `listAppRoleSecretss()` in favor of `listAppRoleSecrets()` (#14)
|
||||
* [test] Tested against Vault 0.8.1, increased coverage
|
||||
|
||||
## 0.6.1 [2017-08-02]
|
||||
* [fix] `TokenModel.getPassword()` returned username instead of password
|
||||
* [fix] `TokenModel.getUsername()` and `getPassword()` could produce NPE in multithreaded environments
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Java Vault Connector
|
||||
|
||||
[](https://travis-ci.org/stklcode/jvaultconnector)
|
||||
[](https://sonarcloud.io/dashboard?id=de.stklcode.jvault%3Aconnector)
|
||||
[](https://github.com/stklcode/jvaultconnector/blob/master/LICENSE.txt)
|
||||
[](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22de.stklcode.jvault%22%20AND%20a%3A%22connector%22)
|
||||
|
||||
@ -30,15 +31,15 @@ Java Vault Connector is a connector library for [Vault](https://www.vaultproject
|
||||
* Raw secret content or JSON decoding
|
||||
* SQL secret handling
|
||||
* Connector Factory with builder pattern
|
||||
* Tested against Vault 0.7.3
|
||||
* Tested against Vault 0.8.3
|
||||
|
||||
|
||||
## Maven Artifact
|
||||
```
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>de.stklcode.jvault</groupId>
|
||||
<artifactId>connector</artifactId>
|
||||
<version>0.6.0</version>
|
||||
<version>0.7.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
51
pom.xml
51
pom.xml
@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>de.stklcode.jvault</groupId>
|
||||
<artifactId>connector</artifactId>
|
||||
<version>0.6.1</version>
|
||||
<version>0.7.0</version>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@ -29,20 +29,49 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>3.7.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.20.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>4.4.6</version>
|
||||
<version>4.4.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
@ -52,12 +81,12 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>2.9.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>2.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -78,5 +107,17 @@
|
||||
<version>1.16.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<version>1.7.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito</artifactId>
|
||||
<version>1.7.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -19,7 +19,10 @@ package de.stklcode.jvault.connector;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stklcode.jvault.connector.exception.*;
|
||||
import de.stklcode.jvault.connector.model.*;
|
||||
import de.stklcode.jvault.connector.model.AppRole;
|
||||
import de.stklcode.jvault.connector.model.AppRoleSecret;
|
||||
import de.stklcode.jvault.connector.model.AuthBackend;
|
||||
import de.stklcode.jvault.connector.model.Token;
|
||||
import de.stklcode.jvault.connector.model.response.*;
|
||||
import de.stklcode.jvault.connector.model.response.embedded.AuthMethod;
|
||||
import org.apache.http.HttpResponse;
|
||||
@ -31,11 +34,16 @@ import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import javax.net.ssl.SSLContext;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -49,7 +57,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
private static final String PATH_SEAL_STATUS = "sys/seal-status";
|
||||
private static final String PATH_SEAL = "sys/seal";
|
||||
private static final String PATH_UNSEAL = "sys/unseal";
|
||||
private static final String PATH_RENEW = "sys/renew";
|
||||
private static final String PATH_RENEW = "sys/leases/renew";
|
||||
private static final String PATH_AUTH = "sys/auth";
|
||||
private static final String PATH_TOKEN = "auth/token";
|
||||
private static final String PATH_LOOKUP = "/lookup";
|
||||
@ -58,7 +66,11 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
private static final String PATH_AUTH_USERPASS = "auth/userpass/login/";
|
||||
private static final String PATH_AUTH_APPID = "auth/app-id/";
|
||||
private static final String PATH_AUTH_APPROLE = "auth/approle/";
|
||||
private static final String PATH_REVOKE = "sys/revoke/";
|
||||
private static final String PATH_AUTH_APPROLE_ROLE = "auth/approle/role/%s%s";
|
||||
private static final String PATH_REVOKE = "sys/leases/revoke/";
|
||||
private static final String PATH_HEALTH = "sys/health";
|
||||
|
||||
private static final String HEADER_VAULT_TOKEN = "X-Vault-Token";
|
||||
|
||||
private final ObjectMapper jsonMapper;
|
||||
|
||||
@ -208,32 +220,25 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SealResponse sealStatus() {
|
||||
public final SealResponse sealStatus() throws VaultConnectorException {
|
||||
try {
|
||||
String response = requestGet(PATH_SEAL_STATUS, new HashMap<>());
|
||||
return jsonMapper.readValue(response, SealResponse.class);
|
||||
} catch (VaultConnectorException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
return null;
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean seal() {
|
||||
try {
|
||||
requestPut(PATH_SEAL, new HashMap<>());
|
||||
return true;
|
||||
} catch (VaultConnectorException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
public final void seal() throws VaultConnectorException {
|
||||
requestPut(PATH_SEAL, new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SealResponse unseal(final String key, final Boolean reset) {
|
||||
public final SealResponse unseal(final String key, final Boolean reset) throws VaultConnectorException {
|
||||
Map<String, String> param = new HashMap<>();
|
||||
param.put("key", key);
|
||||
if (reset != null)
|
||||
@ -241,9 +246,27 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
try {
|
||||
String response = requestPut(PATH_UNSEAL, param);
|
||||
return jsonMapper.readValue(response, SealResponse.class);
|
||||
} catch (VaultConnectorException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthResponse getHealth() throws VaultConnectorException {
|
||||
/* Force status code to be 200, so we don't need to modify the request sequence. */
|
||||
Map<String, String> param = new HashMap<>();
|
||||
param.put("standbycode", "200"); // Default: 429.
|
||||
param.put("sealedcode", "200"); // Default: 503.
|
||||
param.put("uninitcode", "200"); // Default: 501.
|
||||
try {
|
||||
String response = requestGet(PATH_HEALTH, param);
|
||||
/* Parse response */
|
||||
return jsonMapper.readValue(response, HealthResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException e) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,10 +283,10 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
AuthMethodsResponse amr = jsonMapper.readValue(response, AuthMethodsResponse.class);
|
||||
return amr.getSupportedMethods().values().stream().map(AuthMethod::getType).collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,7 +301,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
authorized = true;
|
||||
return res;
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,7 +352,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
this.authorized = true;
|
||||
return auth;
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,8 +368,8 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
/* Get response */
|
||||
String response = requestPost(PATH_AUTH_APPID + "map/app-id/" + appID, payload);
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -360,8 +383,8 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
/* Get response */
|
||||
String response = requestPost(PATH_AUTH_APPID + "map/user-id/" + userID, payload);
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -370,10 +393,10 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
/* Get response */
|
||||
String response = requestPost(PATH_AUTH_APPROLE + "role/" + role.getName(), role);
|
||||
String response = requestPost(String.format(PATH_AUTH_APPROLE_ROLE, role.getName(), ""), role);
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
|
||||
/* Set custom ID if provided */
|
||||
return !(role.getId() != null && !role.getId().isEmpty()) || setAppRoleID(role.getName(), role.getId());
|
||||
@ -385,13 +408,13 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
throw new AuthorizationRequiredException();
|
||||
/* Request HTTP response and parse Secret */
|
||||
try {
|
||||
String response = requestGet(PATH_AUTH_APPROLE + "role/" + roleName, new HashMap<>());
|
||||
String response = requestGet(String.format(PATH_AUTH_APPROLE_ROLE, roleName, ""), new HashMap<>());
|
||||
return jsonMapper.readValue(response, AppRoleResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,11 +424,11 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
throw new AuthorizationRequiredException();
|
||||
|
||||
/* Request HTTP response and expect empty result */
|
||||
String response = requestDelete(PATH_AUTH_APPROLE + "role/" + roleName);
|
||||
String response = requestDelete(String.format(PATH_AUTH_APPROLE_ROLE, roleName, ""));
|
||||
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -416,13 +439,13 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
throw new AuthorizationRequiredException();
|
||||
/* Request HTTP response and parse Secret */
|
||||
try {
|
||||
String response = requestGet(PATH_AUTH_APPROLE + "role/" + roleName + "/role-id", new HashMap<>());
|
||||
String response = requestGet(String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/role-id"), new HashMap<>());
|
||||
return jsonMapper.readValue(response, RawDataResponse.class).getData().get("role_id").toString();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,10 +456,10 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
/* Request HTTP response and parse Secret */
|
||||
Map<String, String> payload = new HashMap<>();
|
||||
payload.put("role_id", roleID);
|
||||
String response = requestPost(PATH_AUTH_APPROLE + "role/" + roleName + "/role-id", payload);
|
||||
String response = requestPost(String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/role-id"), payload);
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -448,15 +471,15 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
/* Get response */
|
||||
String response;
|
||||
if (secret.getId() != null && !secret.getId().isEmpty())
|
||||
response = requestPost(PATH_AUTH_APPROLE + "role/" + roleName + "/custom-secret-id", secret);
|
||||
response = requestPost(String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/custom-secret-id"), secret);
|
||||
else
|
||||
response = requestPost(PATH_AUTH_APPROLE + "role/" + roleName + "/secret-id", secret);
|
||||
response = requestPost(String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/secret-id"), secret);
|
||||
|
||||
try {
|
||||
/* Extract the secret ID from response */
|
||||
return jsonMapper.readValue(response, AppRoleSecretResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response.");
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,11 +491,11 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
/* Request HTTP response and parse Secret */
|
||||
try {
|
||||
String response = requestPost(
|
||||
PATH_AUTH_APPROLE + "role/" + roleName + "/secret-id/lookup",
|
||||
String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/secret-id/lookup"),
|
||||
new AppRoleSecret(secretID));
|
||||
return jsonMapper.readValue(response, AppRoleSecretResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,12 +507,12 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
|
||||
/* Request HTTP response and expect empty result */
|
||||
String response = requestPost(
|
||||
PATH_AUTH_APPROLE + "role/" + roleName + "/secret-id/destroy",
|
||||
String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/secret-id/destroy"),
|
||||
new AppRoleSecret(secretID));
|
||||
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -504,29 +527,29 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
SecretListResponse secrets = jsonMapper.readValue(response, SecretListResponse.class);
|
||||
return secrets.getKeys();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> listAppRoleSecretss(final String roleName) throws VaultConnectorException {
|
||||
public final List<String> listAppRoleSecrets(final String roleName) throws VaultConnectorException {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
|
||||
try {
|
||||
String response = requestGet(
|
||||
PATH_AUTH_APPROLE + "role/" + roleName + "/secret-id?list=true",
|
||||
String.format(PATH_AUTH_APPROLE_ROLE, roleName, "/secret-id?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);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,10 +562,10 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
String response = requestGet(key, new HashMap<>());
|
||||
return jsonMapper.readValue(response, SecretResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,10 +579,10 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
SecretListResponse secrets = jsonMapper.readValue(response, SecretListResponse.class);
|
||||
return secrets.getKeys();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,8 +594,8 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
if (key == null || key.isEmpty())
|
||||
throw new InvalidRequestException("Secret path must not be empty.");
|
||||
|
||||
if (!requestPost(key, data).equals(""))
|
||||
throw new InvalidResponseException("Received response where none was expected.");
|
||||
if (!requestPost(key, data).isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -584,8 +607,8 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
String response = requestDelete(key);
|
||||
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where none was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -597,8 +620,8 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
String response = requestPut(PATH_REVOKE + leaseID, new HashMap<>());
|
||||
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where none was expected.");
|
||||
if (!response.isEmpty())
|
||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -616,7 +639,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
String response = requestPut(PATH_RENEW, payload);
|
||||
return jsonMapper.readValue(response, SecretResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -664,7 +687,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
try {
|
||||
return jsonMapper.readValue(response, AuthResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -677,10 +700,10 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
String response = requestGet(PATH_TOKEN + "/lookup/" + token, new HashMap<>());
|
||||
return jsonMapper.readValue(response, TokenResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response", e);
|
||||
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||
} catch (URISyntaxException ignored) {
|
||||
/* this should never occur and may leak sensible information */
|
||||
throw new InvalidRequestException("Invalid URI format.");
|
||||
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||
}
|
||||
|
||||
}
|
||||
@ -702,14 +725,14 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
try {
|
||||
input = new StringEntity(jsonMapper.writeValueAsString(payload), StandardCharsets.UTF_8);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new InvalidRequestException("Unable to parse response", e);
|
||||
throw new InvalidRequestException(Error.PARSE_RESPONSE, e);
|
||||
}
|
||||
input.setContentEncoding("UTF-8");
|
||||
input.setContentType("application/json");
|
||||
post.setEntity(input);
|
||||
/* Set X-Vault-Token header */
|
||||
if (token != null)
|
||||
post.addHeader("X-Vault-Token", token);
|
||||
post.addHeader(HEADER_VAULT_TOKEN, token);
|
||||
|
||||
return request(post, retries);
|
||||
}
|
||||
@ -730,13 +753,13 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
try {
|
||||
entity = new StringEntity(jsonMapper.writeValueAsString(payload));
|
||||
} catch (UnsupportedEncodingException | JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
throw new InvalidRequestException("Payload serialization failed", e);
|
||||
}
|
||||
/* Parse parameters */
|
||||
put.setEntity(entity);
|
||||
/* Set X-Vault-Token header */
|
||||
if (token != null)
|
||||
put.addHeader("X-Vault-Token", token);
|
||||
put.addHeader(HEADER_VAULT_TOKEN, token);
|
||||
|
||||
return request(put, retries);
|
||||
}
|
||||
@ -753,7 +776,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
HttpDelete delete = new HttpDelete(baseURL + path);
|
||||
/* Set X-Vault-Token header */
|
||||
if (token != null)
|
||||
delete.addHeader("X-Vault-Token", token);
|
||||
delete.addHeader(HEADER_VAULT_TOKEN, token);
|
||||
|
||||
return request(delete, retries);
|
||||
}
|
||||
@ -778,7 +801,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
|
||||
/* Set X-Vault-Token header */
|
||||
if (token != null)
|
||||
get.addHeader("X-Vault-Token", token);
|
||||
get.addHeader(HEADER_VAULT_TOKEN, token);
|
||||
|
||||
return request(get, retries);
|
||||
}
|
||||
@ -808,11 +831,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
|
||||
switch (response.getStatusLine().getStatusCode()) {
|
||||
case 200:
|
||||
try (BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(response.getEntity().getContent()))) {
|
||||
return br.lines().collect(Collectors.joining("\n"));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return handleResult(response);
|
||||
case 204:
|
||||
return "";
|
||||
case 403:
|
||||
@ -824,31 +843,78 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
return request(base, retries - 1);
|
||||
} else {
|
||||
/* Fail on different error code and/or no retries left */
|
||||
InvalidResponseException ex = new InvalidResponseException("Invalid response code")
|
||||
.withStatusCode(response.getStatusLine().getStatusCode());
|
||||
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;
|
||||
handleError(response);
|
||||
|
||||
/* Throw exception withoud details, if response entity is empty. */
|
||||
throw new InvalidResponseException(Error.RESPONSE_CODE,
|
||||
response.getStatusLine().getStatusCode());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to read response", e);
|
||||
throw new InvalidResponseException(Error.READ_RESPONSE, e);
|
||||
} finally {
|
||||
if (response != null && response.getEntity() != null)
|
||||
try {
|
||||
EntityUtils.consume(response.getEntity());
|
||||
} catch (IOException ignored) {
|
||||
// Exception ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful result.
|
||||
*
|
||||
* @param response The raw HTTP response (assuming status code 200)
|
||||
* @return Complete response body as String
|
||||
* @throws InvalidResponseException on reading errors
|
||||
*/
|
||||
private String handleResult(final HttpResponse response) throws InvalidResponseException {
|
||||
try (BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(response.getEntity().getContent()))) {
|
||||
return br.lines().collect(Collectors.joining("\n"));
|
||||
} catch (IOException ignored) {
|
||||
throw new InvalidResponseException(Error.READ_RESPONSE, 200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful response. Throw detailed exception if possible.
|
||||
*
|
||||
* @param response The raw HTTP response (assuming status code 5xx)
|
||||
* @throws VaultConnectorException Expected exception with details to throw
|
||||
*/
|
||||
private void handleError(final HttpResponse response) throws VaultConnectorException {
|
||||
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().isEmpty() && er.getErrors().get(0).equals("permission denied"))
|
||||
throw new PermissionDeniedException();
|
||||
throw new InvalidResponseException(Error.RESPONSE_CODE,
|
||||
response.getStatusLine().getStatusCode(), er.toString());
|
||||
} catch (IOException ignored) {
|
||||
// Exception ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to bundle common error messages.
|
||||
*/
|
||||
private static final class Error {
|
||||
private static final String READ_RESPONSE = "Unable to read response";
|
||||
private static final String PARSE_RESPONSE = "Unable to parse response";
|
||||
private static final String UNEXPECTED_RESPONSE = "Received response where none was expected";
|
||||
private static final String URI_FORMAT = "Invalid URI format";
|
||||
private static final String RESPONSE_CODE = "Invalid response code";
|
||||
|
||||
/**
|
||||
* Constructor hidden, this class should not be instantiated.
|
||||
*/
|
||||
private Error() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,10 @@ import de.stklcode.jvault.connector.exception.VaultConnectorException;
|
||||
import de.stklcode.jvault.connector.model.*;
|
||||
import de.stklcode.jvault.connector.model.response.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Vault Connector interface.
|
||||
@ -31,6 +34,9 @@ import java.util.*;
|
||||
* @since 0.1
|
||||
*/
|
||||
public interface VaultConnector extends AutoCloseable {
|
||||
/**
|
||||
* Default sub-path for Vault secrets.
|
||||
*/
|
||||
String PATH_SECRET = "secret";
|
||||
|
||||
/**
|
||||
@ -42,35 +48,47 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* Retrieve status of vault seal.
|
||||
*
|
||||
* @return Seal status
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
SealResponse sealStatus();
|
||||
SealResponse sealStatus() throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Seal vault.
|
||||
*
|
||||
* @return TRUE on success
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
boolean seal();
|
||||
void seal() throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Unseal vault.
|
||||
*
|
||||
* @param key A single master share key
|
||||
* @param reset Discard previously provided keys (optional)
|
||||
* @return TRUE on success
|
||||
* @return Response with seal status
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
SealResponse unseal(final String key, final Boolean reset);
|
||||
SealResponse unseal(final String key, final Boolean reset) throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Unseal vault.
|
||||
*
|
||||
* @param key A single master share key
|
||||
* @return TRUE on success
|
||||
* @return Response with seal status
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
default SealResponse unseal(final String key) {
|
||||
default SealResponse unseal(final String key) throws VaultConnectorException {
|
||||
return unseal(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query server health information.
|
||||
*
|
||||
* @return Health information.
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.7.0
|
||||
*/
|
||||
HealthResponse getHealth() throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Get all availale authentication backends.
|
||||
*
|
||||
@ -103,7 +121,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
*
|
||||
* @param appID The App ID
|
||||
* @param userID The User ID
|
||||
* @return TRUE on success
|
||||
* @return The {@link AuthResponse}
|
||||
* @throws VaultConnectorException on error
|
||||
* @deprecated As of Vault 0.6.1 App-ID is superseded by AppRole. Consider using {@link #authAppRole} instead.
|
||||
*/
|
||||
@ -114,7 +132,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* Authorize to Vault using AppRole method without secret ID.
|
||||
*
|
||||
* @param roleID The role ID
|
||||
* @return TRUE on success
|
||||
* @return The {@link AuthResponse}
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -127,7 +145,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
*
|
||||
* @param roleID The role ID
|
||||
* @param secretID The secret ID
|
||||
* @return TRUE on success
|
||||
* @return The {@link AuthResponse}
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -139,7 +157,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* @param appID The unique App-ID
|
||||
* @param policy The policy to associate with
|
||||
* @param displayName Arbitrary name to display
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @deprecated As of Vault 0.6.1 App-ID is superseded by AppRole. Consider using {@link #createAppRole} instead.
|
||||
*/
|
||||
@ -151,7 +169,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* Register a new AppRole role from given metamodel.
|
||||
*
|
||||
* @param role The role
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -161,7 +179,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* Register new AppRole role with default policy.
|
||||
*
|
||||
* @param roleName The role name
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -174,7 +192,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
*
|
||||
* @param roleName The role name
|
||||
* @param policies The policies to associate with
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -187,7 +205,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
*
|
||||
* @param roleName The role name
|
||||
* @param roleID A custom role ID
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -201,7 +219,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* @param roleName The role name
|
||||
* @param policies The policies to associate with
|
||||
* @param roleID A custom role ID
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -214,7 +232,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* Delete AppRole role from Vault.
|
||||
*
|
||||
* @param roleName The role anme
|
||||
* @return TRUE on succevss
|
||||
* @return {@code true} on succevss
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
boolean deleteAppRole(final String roleName) throws VaultConnectorException;
|
||||
@ -244,7 +262,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
*
|
||||
* @param roleName The role name
|
||||
* @param roleID The role ID
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
@ -326,14 +344,14 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* @return List of roles
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
List<String> listAppRoleSecretss(final String roleName) throws VaultConnectorException;
|
||||
List<String> listAppRoleSecrets(final String roleName) throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Register User-ID with App-ID.
|
||||
*
|
||||
* @param appID The App-ID
|
||||
* @param userID The User-ID
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @deprecated As of Vault 0.6.1 App-ID is superseded by AppRole.
|
||||
* Consider using {@link #createAppRoleSecret} instead.
|
||||
@ -348,7 +366,7 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* @param policy The policy to associate with
|
||||
* @param displayName Arbitrary name to display
|
||||
* @param userID The User-ID
|
||||
* @return TRUE on success
|
||||
* @return {@code true} on success
|
||||
* @throws VaultConnectorException on error
|
||||
* @deprecated As of Vault 0.6.1 App-ID is superseded by AppRole.
|
||||
*/
|
||||
@ -459,8 +477,9 @@ public interface VaultConnector extends AutoCloseable {
|
||||
* @since 0.5.0
|
||||
*/
|
||||
default void writeSecret(final String key, final Map<String, Object> data) throws VaultConnectorException {
|
||||
if (key == null || key.isEmpty())
|
||||
if (key == null || key.isEmpty()) {
|
||||
throw new InvalidRequestException("Secret path must not be empty.");
|
||||
}
|
||||
write(PATH_SECRET + "/" + key, data);
|
||||
}
|
||||
|
||||
|
@ -24,69 +24,146 @@ package de.stklcode.jvault.connector.exception;
|
||||
* @since 0.1
|
||||
*/
|
||||
public final class InvalidResponseException extends VaultConnectorException {
|
||||
private Integer statusCode;
|
||||
private String response;
|
||||
private final Integer statusCode;
|
||||
private final String response;
|
||||
|
||||
/**
|
||||
* Constructs a new empty exception.
|
||||
*/
|
||||
public InvalidResponseException() {
|
||||
this.statusCode = null;
|
||||
this.response = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message.
|
||||
*
|
||||
* @param message the detail message
|
||||
* @param message The detail message
|
||||
*/
|
||||
public InvalidResponseException(final String message) {
|
||||
super(message);
|
||||
this.statusCode = null;
|
||||
this.response = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified cause.
|
||||
*
|
||||
* @param cause the cause
|
||||
* @param cause The cause
|
||||
*/
|
||||
public InvalidResponseException(final Throwable cause) {
|
||||
super(cause);
|
||||
this.statusCode = null;
|
||||
this.response = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and cause.
|
||||
*
|
||||
* @param message the detail message
|
||||
* @param cause the cause
|
||||
* @param message The detail message
|
||||
* @param cause The cause
|
||||
*/
|
||||
public InvalidResponseException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
this.statusCode = null;
|
||||
this.response = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and status code.
|
||||
* <p>
|
||||
* The HTTP status code can be retrieved by {@link #getStatusCode()} later.
|
||||
*
|
||||
* @param message The detail message
|
||||
* @param statusCode Status code of the HTTP response
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public InvalidResponseException(final String message, final Integer statusCode) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.response = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message, cause and status code.
|
||||
* <p>
|
||||
* The HTTP status code can be retrieved by {@link #getStatusCode()} later.
|
||||
*
|
||||
* @param message The detail message
|
||||
* @param statusCode Status code of the HTTP response
|
||||
* @param cause The cause
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public InvalidResponseException(final String message, final Integer statusCode, final Throwable cause) {
|
||||
this(message, statusCode, null, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message, cause and status code.
|
||||
* <p>
|
||||
* The HTTP status code can be retrieved by {@link #getStatusCode()} later.
|
||||
*
|
||||
* @param message The detail message
|
||||
* @param statusCode Status code of the HTTP response
|
||||
* @param response HTTP response string
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public InvalidResponseException(final String message,
|
||||
final Integer statusCode,
|
||||
final String response) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message, cause and status code.
|
||||
* <p>
|
||||
* The HTTP status code can be retrieved by {@link #getStatusCode()} later.
|
||||
*
|
||||
* @param message The detail message
|
||||
* @param statusCode Status code of the HTTP response
|
||||
* @param response HTTP response string
|
||||
* @param cause The cause
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public InvalidResponseException(final String message,
|
||||
final Integer statusCode,
|
||||
final String response,
|
||||
final Throwable cause) {
|
||||
super(message, cause);
|
||||
this.statusCode = statusCode;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the HTTP status code. Can be retrieved by {@link #getStatusCode()} later.
|
||||
*
|
||||
* @param statusCode the status code
|
||||
* @param statusCode The status code
|
||||
* @return self
|
||||
* @deprecated as of 0.6.2, use constructor with status code argument instead
|
||||
*/
|
||||
@Deprecated
|
||||
public InvalidResponseException withStatusCode(final Integer statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
return this;
|
||||
return new InvalidResponseException(getMessage(), statusCode, getResponse(), getCause());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the response string. Can be retrieved by {@link #getResponse()} later.
|
||||
*
|
||||
* @param response response text
|
||||
* @param response Response text
|
||||
* @return self
|
||||
* @deprecated use constructor with response argument instead
|
||||
*/
|
||||
@Deprecated
|
||||
public InvalidResponseException withResponse(final String response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
return new InvalidResponseException(getMessage(), getStatusCode(), response, getCause());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the HTTP status code.
|
||||
*
|
||||
* @return the status code or {@code null} if none specified.
|
||||
* @return The status code or {@code null} if none specified.
|
||||
*/
|
||||
public Integer getStatusCode() {
|
||||
return statusCode;
|
||||
@ -95,7 +172,7 @@ public final class InvalidResponseException extends VaultConnectorException {
|
||||
/**
|
||||
* Retrieve the response text.
|
||||
*
|
||||
* @return the response text or {@code null} if none specified.
|
||||
* @return The response text or {@code null} if none specified.
|
||||
*/
|
||||
public String getResponse() {
|
||||
return response;
|
||||
|
@ -201,6 +201,7 @@ public final class HTTPVaultConnectorFactory extends VaultConnectorFactory {
|
||||
try {
|
||||
numberOfRetries = Integer.parseInt(System.getenv(ENV_VAULT_MAX_RETRIES));
|
||||
} catch (NumberFormatException ignored) {
|
||||
/* Ignore malformed values. */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ public enum AuthBackend {
|
||||
APPID("app-id"),
|
||||
APPROLE("approle"),
|
||||
USERPASS("userpass"),
|
||||
GITHUB("github"), // Not supported yet.
|
||||
UNKNOWN("");
|
||||
|
||||
private final String type;
|
||||
|
@ -46,8 +46,7 @@ public final class AppRoleResponse extends VaultDataResponse {
|
||||
});
|
||||
this.role = mapper.readValue(mapper.writeValueAsString(filteredData), AppRole.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new InvalidResponseException();
|
||||
throw new InvalidResponseException("Failed deserializing response", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,7 @@ public final class AppRoleSecretResponse extends VaultDataResponse {
|
||||
});
|
||||
this.secret = mapper.readValue(mapper.writeValueAsString(filteredData), AppRoleSecret.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new InvalidResponseException();
|
||||
throw new InvalidResponseException("Failed deserializing response", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,11 +45,10 @@ public final class AuthMethodsResponse extends VaultDataResponse {
|
||||
@Override
|
||||
public void setData(final Map<String, Object> data) throws InvalidResponseException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
for (String path : data.keySet()) {
|
||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||
try {
|
||||
this.supportedMethods.put(
|
||||
path, mapper.readValue(mapper.writeValueAsString(data.get(path)),
|
||||
AuthMethod.class));
|
||||
this.supportedMethods.put(entry.getKey(),
|
||||
mapper.readValue(mapper.writeValueAsString(entry.getValue()), AuthMethod.class));
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException();
|
||||
}
|
||||
|
@ -49,8 +49,7 @@ public final class AuthResponse extends VaultDataResponse {
|
||||
try {
|
||||
this.auth = mapper.readValue(mapper.writeValueAsString(auth), AuthData.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new InvalidResponseException();
|
||||
throw new InvalidResponseException("Failed deserializing response", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,4 +38,13 @@ public final class ErrorResponse implements VaultResponse {
|
||||
public List<String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (errors == null || errors.isEmpty()) {
|
||||
return "error response";
|
||||
} else {
|
||||
return errors.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2016-2017 Stefan Kalscheuer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Vault response for health query.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.7.0
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class HealthResponse implements VaultResponse {
|
||||
@JsonProperty("cluster_id")
|
||||
private String clusterID;
|
||||
|
||||
@JsonProperty("cluster_name")
|
||||
private String clusterName;
|
||||
|
||||
@JsonProperty("version")
|
||||
private String version;
|
||||
|
||||
@JsonProperty("server_time_utc")
|
||||
private Long serverTimeUTC;
|
||||
|
||||
@JsonProperty("standby")
|
||||
private Boolean standby;
|
||||
|
||||
@JsonProperty("sealed")
|
||||
private Boolean sealed;
|
||||
|
||||
@JsonProperty("initialized")
|
||||
private Boolean initialized;
|
||||
|
||||
/**
|
||||
* @return The Cluster ID.
|
||||
*/
|
||||
public String getClusterID() {
|
||||
return clusterID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Cluster name.
|
||||
*/
|
||||
public String getClusterName() {
|
||||
return clusterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Vault version.
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Server time UTC (timestamp).
|
||||
*/
|
||||
public Long getServerTimeUTC() {
|
||||
return serverTimeUTC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Server standby status.
|
||||
*/
|
||||
public Boolean isStandby() {
|
||||
return standby;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Server seal status.
|
||||
*/
|
||||
public Boolean isSealed() {
|
||||
return sealed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Server initialization status.
|
||||
*/
|
||||
public Boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@ public class SecretResponse extends VaultDataResponse {
|
||||
* Get a single value for given key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the value or NULL if absent
|
||||
* @return the value or {@code null} if absent
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public final Object get(final String key) {
|
||||
@ -73,9 +73,10 @@ public class SecretResponse extends VaultDataResponse {
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getValue() {
|
||||
if (get("value") == null)
|
||||
Object value = get("value");
|
||||
if (value == null)
|
||||
return null;
|
||||
return get("value").toString();
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,13 +100,16 @@ public class SecretResponse extends VaultDataResponse {
|
||||
* @param key the key
|
||||
* @param type Class to parse response
|
||||
* @param <T> Class to parse response
|
||||
* @return Parsed object
|
||||
* @return Parsed object or {@code null} if absent
|
||||
* @throws InvalidResponseException on parsing error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public final <T> T get(final String key, final Class<T> type) throws InvalidResponseException {
|
||||
try {
|
||||
return new ObjectMapper().readValue(get(key).toString(), type);
|
||||
Object rawValue = get(key);
|
||||
if (rawValue == null)
|
||||
return null;
|
||||
return new ObjectMapper().readValue(rawValue.toString(), type);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidResponseException("Unable to parse response payload: " + e.getMessage());
|
||||
}
|
||||
|
@ -50,8 +50,7 @@ public final class TokenResponse extends VaultDataResponse {
|
||||
try {
|
||||
this.data = mapper.readValue(mapper.writeValueAsString(data), TokenData.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new InvalidResponseException();
|
||||
throw new InvalidResponseException("Failed deserializing response", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,466 @@
|
||||
/*
|
||||
* Copyright 2016-2017 Stefan Kalscheuer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package de.stklcode.jvault.connector;
|
||||
|
||||
import de.stklcode.jvault.connector.exception.InvalidRequestException;
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import de.stklcode.jvault.connector.exception.PermissionDeniedException;
|
||||
import de.stklcode.jvault.connector.exception.VaultConnectorException;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.powermock.api.mockito.PowerMockito.*;
|
||||
|
||||
/**
|
||||
* JUnit test for HTTP Vault connector.
|
||||
* This test suite contains tests that do not require connection to an actual Vault instance.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.7.0
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({HttpClientBuilder.class})
|
||||
@PowerMockIgnore({"javax.net.ssl.*"})
|
||||
public class HTTPVaultConnectorOfflineTest {
|
||||
private static final String INVALID_URL = "foo:/\\1nv4l1d_UrL";
|
||||
|
||||
@Mock
|
||||
private HttpClientBuilder httpMockBuilder;
|
||||
|
||||
@Mock
|
||||
private CloseableHttpClient httpMock;
|
||||
|
||||
@Mock
|
||||
private CloseableHttpResponse responseMock;
|
||||
|
||||
/**
|
||||
* Test exceptions thrown during request.
|
||||
*/
|
||||
@Test
|
||||
public void requestExceptionTest() throws IOException {
|
||||
HTTPVaultConnector connector = new HTTPVaultConnector("http://127.0.0.1", null, 0, 250);
|
||||
|
||||
// Test invalid response code.
|
||||
initHttpMock();
|
||||
final int responseCode = 400;
|
||||
mockResponse(responseCode, "", ContentType.APPLICATION_JSON);
|
||||
try {
|
||||
connector.getHealth();
|
||||
fail("Querying health status succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Invalid response code"));
|
||||
assertThat("Unexpected status code in exception", ((InvalidResponseException) e).getStatusCode(), is(responseCode));
|
||||
assertThat("Response message where none was expected", ((InvalidResponseException) e).getResponse(), is(nullValue()));
|
||||
}
|
||||
|
||||
// Simulate permission denied response.
|
||||
mockResponse(responseCode, "{\"errors\":[\"permission denied\"]}", ContentType.APPLICATION_JSON);
|
||||
try {
|
||||
connector.getHealth();
|
||||
fail("Querying health status succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(PermissionDeniedException.class));
|
||||
}
|
||||
|
||||
// Test exception thrown during request.
|
||||
when(httpMock.execute(any())).thenThrow(new IOException("Test Exception"));
|
||||
try {
|
||||
connector.getHealth();
|
||||
fail("Querying health status succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Unable to read response"));
|
||||
assertThat("Unexpected cause", e.getCause(), instanceOf(IOException.class));
|
||||
}
|
||||
|
||||
// Now simulate a failing request that succeeds on second try.
|
||||
connector = new HTTPVaultConnector("https://127.0.0.1", null, 1, 250);
|
||||
doReturn(responseMock).doReturn(responseMock).when(httpMock).execute(any());
|
||||
doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, ""))
|
||||
.doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, ""))
|
||||
.doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, ""))
|
||||
.doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, ""))
|
||||
.when(responseMock).getStatusLine();
|
||||
when(responseMock.getEntity()).thenReturn(new StringEntity("{}", ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
connector.getHealth();
|
||||
} catch (Exception e) {
|
||||
fail("Request failed unexpectedly: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constductors of the {@link HTTPVaultConnector} class.
|
||||
*/
|
||||
@Test
|
||||
public void constructorTest() throws NoSuchAlgorithmException {
|
||||
final String url = "https://vault.example.net/test/";
|
||||
final String hostname = "vault.example.com";
|
||||
final Integer port = 1337;
|
||||
final String prefix = "/custom/prefix/";
|
||||
final Integer retries = 42;
|
||||
final String expectedNoTls = "http://" + hostname + "/v1/";
|
||||
final String expectedCustomPort = "https://" + hostname + ":" + port + "/v1/";
|
||||
final String expectedCustomPrefix = "https://" + hostname + ":" + port + prefix;
|
||||
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
|
||||
// Most basic constructor expects complete URL.
|
||||
HTTPVaultConnector connector = new HTTPVaultConnector(url);
|
||||
assertThat("Unexpected base URL", getPrivate(connector, "baseURL"), is(url));
|
||||
|
||||
// Now override TLS usage.
|
||||
connector = new HTTPVaultConnector(hostname, false);
|
||||
assertThat("Unexpected base URL with TLS disabled", getPrivate(connector, "baseURL"), is(expectedNoTls));
|
||||
|
||||
// Specify custom port.
|
||||
connector = new HTTPVaultConnector(hostname, true, port);
|
||||
assertThat("Unexpected base URL with custom port", getPrivate(connector, "baseURL"), is(expectedCustomPort));
|
||||
|
||||
// Specify custom prefix.
|
||||
connector = new HTTPVaultConnector(hostname, true, port, prefix);
|
||||
assertThat("Unexpected base URL with custom prefix", getPrivate(connector, "baseURL"), is(expectedCustomPrefix));
|
||||
assertThat("SSL context set, but not specified", getPrivate(connector, "sslContext"), is(nullValue()));
|
||||
|
||||
// Provide custom SSL context.
|
||||
connector = new HTTPVaultConnector(hostname, true, port, prefix, sslContext);
|
||||
assertThat("Unexpected base URL with custom prefix", getPrivate(connector, "baseURL"), is(expectedCustomPrefix));
|
||||
assertThat("SSL context not filled correctly", getPrivate(connector, "sslContext"), is(sslContext));
|
||||
|
||||
// Specify number of retries.
|
||||
connector = new HTTPVaultConnector(url, sslContext, retries);
|
||||
assertThat("Number of retries not set correctly", getPrivate(connector, "retries"), is(retries));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable.
|
||||
*/
|
||||
@Test
|
||||
public void sealExceptionTest() throws IOException {
|
||||
HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL);
|
||||
try {
|
||||
connector.sealStatus();
|
||||
fail("Querying seal status succeeded on invalid URL");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidRequestException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format"));
|
||||
}
|
||||
|
||||
connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250);
|
||||
|
||||
// Simulate NULL response (mock not supplied with data).
|
||||
initHttpMock();
|
||||
try {
|
||||
connector.sealStatus();
|
||||
fail("Querying seal status succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Response unavailable"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable.
|
||||
*/
|
||||
@Test
|
||||
public void healthExceptionTest() throws IOException {
|
||||
HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL);
|
||||
try {
|
||||
connector.getHealth();
|
||||
fail("Querying health status succeeded on invalid URL");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidRequestException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format"));
|
||||
}
|
||||
|
||||
connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250);
|
||||
|
||||
// Simulate NULL response (mock not supplied with data).
|
||||
initHttpMock();
|
||||
try {
|
||||
connector.getHealth();
|
||||
fail("Querying health status succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Response unavailable"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test behavior on unparsable responses.
|
||||
*/
|
||||
@Test
|
||||
public void parseExceptionTest() throws IOException {
|
||||
HTTPVaultConnector connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250);
|
||||
// Mock authorization.
|
||||
setPrivate(connector, "authorized", true);
|
||||
// Mock response.
|
||||
initHttpMock();
|
||||
mockResponse(200, "invalid", ContentType.APPLICATION_JSON);
|
||||
|
||||
// Now test the methods.
|
||||
try {
|
||||
connector.sealStatus();
|
||||
fail("sealStatus() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.unseal("key");
|
||||
fail("unseal() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.getHealth();
|
||||
fail("getHealth() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.getAuthBackends();
|
||||
fail("getAuthBackends() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.authToken("token");
|
||||
fail("authToken() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.lookupAppRole("roleName");
|
||||
fail("lookupAppRole() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.getAppRoleID("roleName");
|
||||
fail("getAppRoleID() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.createAppRoleSecret("roleName");
|
||||
fail("createAppRoleSecret() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.lookupAppRoleSecret("roleName", "secretID");
|
||||
fail("lookupAppRoleSecret() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.listAppRoles();
|
||||
fail("listAppRoles() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.listAppRoleSecrets("roleName");
|
||||
fail("listAppRoleSecrets() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.read("key");
|
||||
fail("read() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.list("path");
|
||||
fail("list() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.renew("leaseID");
|
||||
fail("renew() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
connector.lookupToken("token");
|
||||
fail("lookupToken() succeeded on invalid instance");
|
||||
} catch (Exception e) {
|
||||
assertParseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertParseError(Exception e) {
|
||||
assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class));
|
||||
assertThat("Unexpected exception message", e.getMessage(), is("Unable to parse response"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test requests that expect an empty response with code 204, but receive a 200 body.
|
||||
*/
|
||||
@Test
|
||||
public void nonEmpty204ResponseTest() throws IOException {
|
||||
HTTPVaultConnector connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250);
|
||||
// Mock authorization.
|
||||
setPrivate(connector, "authorized", true);
|
||||
// Mock response.
|
||||
initHttpMock();
|
||||
mockResponse(200, "{}", ContentType.APPLICATION_JSON);
|
||||
|
||||
// Now test the methods expecting a 204.
|
||||
try {
|
||||
connector.registerAppId("appID", "policy", "displayName");
|
||||
fail("registerAppId() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.registerUserId("appID", "userID");
|
||||
fail("registerUserId() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.createAppRole("appID", Collections.singletonList("policy"));
|
||||
fail("createAppRole() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.deleteAppRole("roleName");
|
||||
fail("deleteAppRole() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.setAppRoleID("roleName", "roleID");
|
||||
fail("setAppRoleID() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.destroyAppRoleSecret("roleName", "secretID");
|
||||
fail("destroyAppRoleSecret() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.destroyAppRoleSecret("roleName", "secretUD");
|
||||
fail("destroyAppRoleSecret() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.delete("key");
|
||||
fail("delete() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.revoke("leaseID");
|
||||
fail("destroyAppRoleSecret() with 200 response succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Unexpected exception type", e, instanceOf(InvalidResponseException.class));
|
||||
}
|
||||
}
|
||||
|
||||
private Object getPrivate(Object target, String fieldName) {
|
||||
try {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
if (field.isAccessible())
|
||||
return field.get(target);
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(target);
|
||||
field.setAccessible(false);
|
||||
return value;
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPrivate(Object target, String fieldName, Object value) {
|
||||
try {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
boolean accessible =field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
field.setAccessible(accessible);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
// Should not occur, to be taken care of in test code.
|
||||
}
|
||||
}
|
||||
|
||||
private void initHttpMock() {
|
||||
mockStatic(HttpClientBuilder.class);
|
||||
when(HttpClientBuilder.create()).thenReturn(httpMockBuilder);
|
||||
when(httpMockBuilder.setSSLContext(null)).thenReturn(httpMockBuilder);
|
||||
when(httpMockBuilder.build()).thenReturn(httpMock);
|
||||
}
|
||||
|
||||
private void mockResponse(int status, String body, ContentType type) throws IOException {
|
||||
when(httpMock.execute(any())).thenReturn(responseMock);
|
||||
when(responseMock.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), status, ""));
|
||||
when(responseMock.getEntity()).thenReturn(new StringEntity(body, type));
|
||||
}
|
||||
}
|
@ -16,21 +16,19 @@
|
||||
|
||||
package de.stklcode.jvault.connector;
|
||||
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import de.stklcode.jvault.connector.exception.*;
|
||||
import de.stklcode.jvault.connector.model.*;
|
||||
import de.stklcode.jvault.connector.model.response.*;
|
||||
import de.stklcode.jvault.connector.factory.HTTPVaultConnectorFactory;
|
||||
import de.stklcode.jvault.connector.test.Credentials;
|
||||
import de.stklcode.jvault.connector.test.VaultConfiguration;
|
||||
import de.stklcode.jvault.connector.exception.InvalidRequestException;
|
||||
import de.stklcode.jvault.connector.exception.PermissionDeniedException;
|
||||
import de.stklcode.jvault.connector.exception.VaultConnectorException;
|
||||
import de.stklcode.jvault.connector.factory.VaultConnectorFactory;
|
||||
import org.junit.*;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
@ -50,21 +48,24 @@ import static org.junit.Assume.*;
|
||||
* @since 0.1
|
||||
*/
|
||||
public class HTTPVaultConnectorTest {
|
||||
private static String KEY = "81011a8061e5c028bd0d9503eeba40bd9054b9af0408d080cb24f57405c27a61";
|
||||
private static String TOKEN_ROOT = "d1bd50e2-587b-6e68-d80b-a9a507625cb7";
|
||||
private static String USER_VALID = "validUser";
|
||||
private static String PASS_VALID = "validPass";
|
||||
private static String APP_ID = "152AEA38-85FB-47A8-9CBD-612D645BFACA";
|
||||
private static String USER_ID = "5ADF8218-D7FB-4089-9E38-287465DBF37E";
|
||||
private static String APPROLE_ROLE_NAME = "testrole1"; // role with secret ID
|
||||
private static String APPROLE_ROLE = "627b6400-90c3-a239-49a9-af65a448ca10";
|
||||
private static String APPROLE_SECRET = "5e8b0e99-d906-27f5-f043-ccb9bb53b5e8";
|
||||
private static String APPROLE_ROLE2 = "35b7bf43-9644-588a-e68f-2e8313bb23b7"; // role with CIDR subnet
|
||||
private static String SECRET_PATH = "userstore";
|
||||
private static String SECRET_KEY = "foo";
|
||||
private static String SECRET_VALUE = "bar";
|
||||
private static String SECRET_KEY_JSON = "json";
|
||||
private static String SECRET_KEY_COMPLEX = "complex";
|
||||
private static final String VAULT_VERISON = "0.8.3"; // the vault version this test is supposed to run against
|
||||
private static final String KEY = "81011a8061e5c028bd0d9503eeba40bd9054b9af0408d080cb24f57405c27a61";
|
||||
private static final String TOKEN_ROOT = "d1bd50e2-587b-6e68-d80b-a9a507625cb7";
|
||||
private static final String USER_VALID = "validUser";
|
||||
private static final String PASS_VALID = "validPass";
|
||||
private static final String APP_ID = "152AEA38-85FB-47A8-9CBD-612D645BFACA";
|
||||
private static final String USER_ID = "5ADF8218-D7FB-4089-9E38-287465DBF37E";
|
||||
private static final String APPROLE_ROLE_NAME = "testrole1"; // role with secret ID
|
||||
private static final String APPROLE_ROLE = "627b6400-90c3-a239-49a9-af65a448ca10";
|
||||
private static final String APPROLE_SECRET = "5e8b0e99-d906-27f5-f043-ccb9bb53b5e8";
|
||||
private static final String APPROLE_SECRET_ACCESSOR = "071e2e9d-742a-fc3c-3fd3-1f4004b0420a";
|
||||
private static final String APPROLE_ROLE2_NAME = "testrole2"; // role with CIDR subnet
|
||||
private static final String APPROLE_ROLE2 = "35b7bf43-9644-588a-e68f-2e8313bb23b7";
|
||||
private static final String SECRET_PATH = "userstore";
|
||||
private static final String SECRET_KEY = "foo";
|
||||
private static final String SECRET_VALUE = "bar";
|
||||
private static final String SECRET_KEY_JSON = "json";
|
||||
private static final String SECRET_KEY_COMPLEX = "complex";
|
||||
|
||||
private Process vaultProcess;
|
||||
private VaultConnector connector;
|
||||
@ -113,6 +114,70 @@ public class HTTPVaultConnectorTest {
|
||||
vaultProcess.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sealing and unsealing Vault.
|
||||
*/
|
||||
@Test
|
||||
public void sealTest() throws VaultConnectorException {
|
||||
SealResponse sealStatus = connector.sealStatus();
|
||||
assumeFalse(sealStatus.isSealed());
|
||||
|
||||
/* Unauthorized sealing should fail */
|
||||
try {
|
||||
connector.seal();
|
||||
fail("Unauthorized sealing succeeded");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat("Vault sealed, although sealing failed", sealStatus.isSealed(), is(false));
|
||||
}
|
||||
|
||||
/* Root user should be able to seal */
|
||||
authRoot();
|
||||
assumeTrue(connector.isAuthorized());
|
||||
try {
|
||||
connector.seal();
|
||||
sealStatus = connector.sealStatus();
|
||||
assertThat("Vault not sealed", sealStatus.isSealed(), is(true));
|
||||
sealStatus = connector.unseal(KEY);
|
||||
assertThat("Vault not unsealed", sealStatus.isSealed(), is(false));
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Sealing failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test health status
|
||||
*/
|
||||
@Test
|
||||
public void healthTest() {
|
||||
HealthResponse res = null;
|
||||
try {
|
||||
res = connector.getHealth();
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Retrieving health status failed: " + e.getMessage());
|
||||
}
|
||||
assertThat("Health response should be set", res, is(notNullValue()));
|
||||
assertThat("Unexpected version", res.getVersion(), is(VAULT_VERISON));
|
||||
assertThat("Unexpected init status", res.isInitialized(), is(true));
|
||||
assertThat("Unexpected seal status", res.isSealed(), is(false));
|
||||
assertThat("Unexpected standby status", res.isStandby(), is(false));
|
||||
|
||||
// No seal vault and verify correct status.
|
||||
authRoot();
|
||||
try {
|
||||
connector.seal();
|
||||
assumeTrue(connector.sealStatus().isSealed());
|
||||
connector.resetAuth(); // SHould work unauthenticated
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Unexpected exception on sealing: " + e.getMessage());
|
||||
}
|
||||
try {
|
||||
res = connector.getHealth();
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Retrieving health status failed when sealed: " + e.getMessage());
|
||||
}
|
||||
assertThat("Unexpected seal status", res.isSealed(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test listing of authentication backends
|
||||
*/
|
||||
@ -191,6 +256,23 @@ public class HTTPVaultConnectorTest {
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void authAppIdTest() {
|
||||
/* Try unauthorized access first. */
|
||||
assumeFalse(connector.isAuthorized());
|
||||
|
||||
try {
|
||||
connector.registerAppId("", "", "");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
try {
|
||||
connector.registerUserId("", "");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
/* Authorize. */
|
||||
authRoot();
|
||||
assumeTrue(connector.isAuthorized());
|
||||
|
||||
@ -285,6 +367,65 @@ public class HTTPVaultConnectorTest {
|
||||
*/
|
||||
@Test
|
||||
public void createAppRoleTest() {
|
||||
/* Try unauthorized access first. */
|
||||
assumeFalse(connector.isAuthorized());
|
||||
try {
|
||||
connector.createAppRole(new AppRole());
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.lookupAppRole("");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.deleteAppRole("");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.getAppRoleID("");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.setAppRoleID("", "");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.createAppRoleSecret("", "");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.lookupAppRoleSecret("", "");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.destroyAppRoleSecret("", "");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
/* Authorize. */
|
||||
authRoot();
|
||||
assumeTrue(connector.isAuthorized());
|
||||
|
||||
@ -310,15 +451,16 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
|
||||
/* Lookup role ID */
|
||||
String roleID = "";
|
||||
try {
|
||||
String res = connector.getAppRoleID(roleName);
|
||||
assertThat("Role ID lookup returned empty ID.", res, is(not(emptyString())));
|
||||
roleID = connector.getAppRoleID(roleName);
|
||||
assertThat("Role ID lookup returned empty ID.", roleID, is(not(emptyString())));
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Role ID lookup failed.");
|
||||
}
|
||||
|
||||
/* Set custom role ID */
|
||||
String roleID = "custom-role-id";
|
||||
roleID = "custom-role-id";
|
||||
try {
|
||||
connector.setAppRoleID(roleName, roleID);
|
||||
} catch (VaultConnectorException e) {
|
||||
@ -377,7 +519,8 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
try {
|
||||
AppRoleResponse res = connector.lookupAppRole(roleName);
|
||||
assertThat("Role lookuo returned wrong policy count", res.getRole().getPolicies(), hasSize(2));
|
||||
// Note: As of Vault 0.8.3 default policy is not added automatically, so this test should return 1, not 2.
|
||||
assertThat("Role lookuo returned wrong policy count (before Vault 0.8.3 is should be 2)", res.getRole().getPolicies(), hasSize(1));
|
||||
assertThat("Role lookuo returned wrong policies", res.getRole().getPolicies(), hasItem("testpolicy"));
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Creation of role by name failed.");
|
||||
@ -444,6 +587,51 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test listing of AppRole roles and secrets.
|
||||
*/
|
||||
@Test
|
||||
public void listAppRoleTest() {
|
||||
/* Try unauthorized access first. */
|
||||
assumeFalse(connector.isAuthorized());
|
||||
|
||||
try {
|
||||
connector.listAppRoles();
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
try {
|
||||
connector.listAppRoleSecrets("");
|
||||
fail("Expected exception not thrown");
|
||||
} catch (Exception e) {
|
||||
assertThat("Unexpected exception class", e, is(instanceOf(AuthorizationRequiredException.class)));
|
||||
}
|
||||
|
||||
/* Authorize. */
|
||||
authRoot();
|
||||
assumeTrue(connector.isAuthorized());
|
||||
|
||||
/* Verify pre-existing rules */
|
||||
try {
|
||||
List<String> res = connector.listAppRoles();
|
||||
assertThat("Unexpected number of AppRoles", res, hasSize(2));
|
||||
assertThat("Pre-configured roles not listed", res, containsInAnyOrder(APPROLE_ROLE_NAME, APPROLE_ROLE2_NAME));
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Role listing failed.");
|
||||
}
|
||||
|
||||
/* Check secret IDs */
|
||||
try {
|
||||
List<String> res = connector.listAppRoleSecrets(APPROLE_ROLE_NAME);
|
||||
assertThat("Unexpected number of AppRole secrets", res, hasSize(1));
|
||||
assertThat("Pre-configured AppRole secret not listed", res, contains(APPROLE_SECRET_ACCESSOR));
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("AppRole secret listing failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reading of secrets.
|
||||
*/
|
||||
@ -697,24 +885,24 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
|
||||
/* Overwrite token should fail as of Vault 0.8.0 */
|
||||
// token = new TokenBuilder()
|
||||
// .withId("test-id2")
|
||||
// .withDisplayName("test name 3")
|
||||
// .withPolicies(Arrays.asList("pol1", "pol2"))
|
||||
// .withDefaultPolicy()
|
||||
// .withMeta("test", "success")
|
||||
// .withMeta("key", "value")
|
||||
// .withTtl(1234)
|
||||
// .build();
|
||||
// try {
|
||||
// connector.createToken(token);
|
||||
// fail("Overwriting token should fail as of Vault 0.8.0");
|
||||
// } catch (VaultConnectorException e) {
|
||||
// assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
// assertThat(((InvalidResponseException)e).getStatusCode(), is(400));
|
||||
// /* Assert that the exception does not reveal token ID */
|
||||
// assertThat(stackTrace(e), not(stringContainsInOrder(token.getId())));
|
||||
// }
|
||||
token = new TokenBuilder()
|
||||
.withId("test-id2")
|
||||
.withDisplayName("test name 3")
|
||||
.withPolicies(Arrays.asList("pol1", "pol2"))
|
||||
.withDefaultPolicy()
|
||||
.withMeta("test", "success")
|
||||
.withMeta("key", "value")
|
||||
.withTtl(1234)
|
||||
.build();
|
||||
try {
|
||||
connector.createToken(token);
|
||||
fail("Overwriting token should fail as of Vault 0.8.0");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
assertThat(((InvalidResponseException)e).getStatusCode(), is(400));
|
||||
/* Assert that the exception does not reveal token ID */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(token.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -738,6 +926,27 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test closing the connector.
|
||||
*/
|
||||
@Test
|
||||
public void closeTest() {
|
||||
authUser();
|
||||
assumeTrue(connector.isAuthorized());
|
||||
|
||||
try {
|
||||
connector.close();
|
||||
assertThat("Not unauthorized after close().", connector.isAuthorized(), is(false));
|
||||
|
||||
/* Verify that (private) token has indeed been removed */
|
||||
Field tokenField = HTTPVaultConnector.class.getDeclaredField("token");
|
||||
tokenField.setAccessible(true);
|
||||
assertThat("Token not removed after close().", tokenField.get(connector), is(nullValue()));
|
||||
} catch (Exception e) {
|
||||
fail("Closing the connector failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Vault with resource datastore and generated configuration.
|
||||
*
|
||||
@ -770,8 +979,7 @@ public class HTTPVaultConnectorTest {
|
||||
bw = new BufferedWriter(new FileWriter(configFile));
|
||||
bw.write(config.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new IllegalStateException("Unable to generate config file.");
|
||||
throw new IllegalStateException("Unable to generate config file.", e);
|
||||
} finally {
|
||||
try {
|
||||
if (bw != null)
|
||||
@ -785,8 +993,7 @@ public class HTTPVaultConnectorTest {
|
||||
try {
|
||||
vaultProcess = Runtime.getRuntime().exec("vault server -config " + configFile.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new IllegalStateException("Unable to start vault. Make sure vault binary is in your executable path.");
|
||||
throw new IllegalStateException("Unable to start vault. Make sure vault binary is in your executable path.", e);
|
||||
}
|
||||
|
||||
return config;
|
||||
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2016-2017 Stefan Kalscheuer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package de.stklcode.jvault.connector.exception;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Common JUnit test for Exceptions extending {@link VaultConnectorException}.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public class VaultConnectorExceptionTest {
|
||||
private static final String MSG = "This is a test exception!";
|
||||
private static final Throwable CAUSE = new Exception("Test-Cause");
|
||||
private static final Integer STATUS_CODE = 1337;
|
||||
private static final String RESPONSE = "Dummy response";
|
||||
|
||||
@Test
|
||||
public void authorizationRequiredExceptionTest() {
|
||||
assertEmptyConstructor(new AuthorizationRequiredException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectionExceptionTest() {
|
||||
assertEmptyConstructor(new ConnectionException());
|
||||
assertMsgConstructor(new ConnectionException(MSG));
|
||||
assertCauseConstructor(new ConnectionException(CAUSE));
|
||||
assertMsgCauseConstructor(new ConnectionException(MSG, CAUSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidRequestExceptionTest() {
|
||||
assertEmptyConstructor(new InvalidRequestException());
|
||||
assertMsgConstructor(new InvalidRequestException(MSG));
|
||||
assertCauseConstructor(new InvalidRequestException(CAUSE));
|
||||
assertMsgCauseConstructor(new InvalidRequestException(MSG, CAUSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidResponseExceptionTest() {
|
||||
assertEmptyConstructor(new InvalidResponseException());
|
||||
assertMsgConstructor(new InvalidResponseException(MSG));
|
||||
assertCauseConstructor(new InvalidResponseException(CAUSE));
|
||||
assertMsgCauseConstructor(new InvalidResponseException(MSG, CAUSE));
|
||||
|
||||
// Constructor with message and status code.
|
||||
InvalidResponseException e = new InvalidResponseException(MSG, STATUS_CODE);
|
||||
assertThat(e.getMessage(), is(MSG));
|
||||
assertThat(e.getCause(), is(nullValue()));
|
||||
assertThat(e.getStatusCode(), is(STATUS_CODE));
|
||||
assertThat(e.getResponse(), is(nullValue()));
|
||||
|
||||
// Constructor with message, status code and cause.
|
||||
e = new InvalidResponseException(MSG, STATUS_CODE, CAUSE);
|
||||
assertThat(e.getMessage(), is(MSG));
|
||||
assertThat(e.getCause(), is(CAUSE));
|
||||
assertThat(e.getStatusCode(), is(STATUS_CODE));
|
||||
assertThat(e.getResponse(), is(nullValue()));
|
||||
|
||||
// Constructor with message, status code and response.
|
||||
e = new InvalidResponseException(MSG, STATUS_CODE, RESPONSE);
|
||||
assertThat(e.getMessage(), is(MSG));
|
||||
assertThat(e.getCause(), is(nullValue()));
|
||||
assertThat(e.getStatusCode(), is(STATUS_CODE));
|
||||
assertThat(e.getResponse(), is(RESPONSE));
|
||||
|
||||
// Constructor with message, status code, response and cause.
|
||||
e = new InvalidResponseException(MSG, STATUS_CODE, RESPONSE, CAUSE);
|
||||
assertThat(e.getMessage(), is(MSG));
|
||||
assertThat(e.getCause(), is(CAUSE));
|
||||
assertThat(e.getStatusCode(), is(STATUS_CODE));
|
||||
assertThat(e.getResponse(), is(RESPONSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void permissionDeniedExceptionTest() {
|
||||
// Default message overwritten.
|
||||
PermissionDeniedException e = new PermissionDeniedException();
|
||||
assertThat(e, is(instanceOf(VaultConnectorException.class)));
|
||||
assertThat(e, is(instanceOf(Exception.class)));
|
||||
assertThat(e, is(instanceOf(Throwable.class)));
|
||||
assertThat(e.getMessage(), is("Permission denied"));
|
||||
assertThat(e.getCause(), is(nullValue()));
|
||||
|
||||
assertMsgConstructor(new PermissionDeniedException(MSG));
|
||||
assertCauseConstructor(new PermissionDeniedException(CAUSE));
|
||||
assertMsgCauseConstructor(new PermissionDeniedException(MSG, CAUSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tlsExceptionTest() {
|
||||
assertEmptyConstructor(new TlsException());
|
||||
assertMsgConstructor(new TlsException(MSG));
|
||||
assertCauseConstructor(new TlsException(CAUSE));
|
||||
assertMsgCauseConstructor(new TlsException(MSG, CAUSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assertions for empty constructor.
|
||||
*
|
||||
* @param e the exception
|
||||
*/
|
||||
private void assertEmptyConstructor(VaultConnectorException e) {
|
||||
assertThat(e, is(instanceOf(VaultConnectorException.class)));
|
||||
assertThat(e, is(instanceOf(Exception.class)));
|
||||
assertThat(e, is(instanceOf(Throwable.class)));
|
||||
assertThat(e.getMessage(), is(nullValue()));
|
||||
assertThat(e.getCause(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assertions for constructor with message.
|
||||
*
|
||||
* @param e the exception
|
||||
*/
|
||||
private void assertMsgConstructor(VaultConnectorException e) {
|
||||
assertThat(e.getMessage(), is(MSG));
|
||||
assertThat(e.getCause(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assertions for constructor with cause.
|
||||
*
|
||||
* @param e the exception
|
||||
*/
|
||||
private void assertCauseConstructor(VaultConnectorException e) {
|
||||
assertThat(e.getMessage(), is(CAUSE.toString()));
|
||||
assertThat(e.getCause(), is(CAUSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assertions for constructor with message and cause.
|
||||
*
|
||||
* @param e the exception
|
||||
*/
|
||||
private void assertMsgCauseConstructor(VaultConnectorException e) {
|
||||
assertThat(e.getMessage(), is(MSG));
|
||||
assertThat(e.getCause(), is(CAUSE));
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ public class AuthBackendTest {
|
||||
assertThat(AuthBackend.forType("token"), is(AuthBackend.TOKEN));
|
||||
assertThat(AuthBackend.forType("app-id"), is(AuthBackend.APPID));
|
||||
assertThat(AuthBackend.forType("userpass"), is(AuthBackend.USERPASS));
|
||||
assertThat(AuthBackend.forType("github"), is(AuthBackend.GITHUB));
|
||||
assertThat(AuthBackend.forType(""), is(AuthBackend.UNKNOWN));
|
||||
assertThat(AuthBackend.forType("foobar"), is(AuthBackend.UNKNOWN));
|
||||
}
|
||||
|
@ -0,0 +1,101 @@
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import de.stklcode.jvault.connector.model.AppRole;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* JUnit Test for {@link AppRoleResponse} model.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public class AppRoleResponseTest {
|
||||
private static final Integer ROLE_TOKEN_TTL = 1200;
|
||||
private static final Integer ROLE_TOKEN_MAX_TTL = 1800;
|
||||
private static final Integer ROLE_SECRET_TTL = 600;
|
||||
private static final Integer ROLE_SECRET_NUM_USES = 40;
|
||||
private static final String ROLE_POLICY = "default";
|
||||
private static final Integer ROLE_PERIOD = 0;
|
||||
private static final Boolean ROLE_BIND_SECRET = true;
|
||||
|
||||
private static final String RES_JSON = "{\n" +
|
||||
" \"auth\": null,\n" +
|
||||
" \"warnings\": null,\n" +
|
||||
" \"wrap_info\": null,\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"token_ttl\": " + ROLE_TOKEN_TTL + ",\n" +
|
||||
" \"token_max_ttl\": " + ROLE_TOKEN_MAX_TTL + ",\n" +
|
||||
" \"secret_id_ttl\": " + ROLE_SECRET_TTL + ",\n" +
|
||||
" \"secret_id_num_uses\": " + ROLE_SECRET_NUM_USES + ",\n" +
|
||||
" \"policies\": [\n" +
|
||||
" \"" + ROLE_POLICY + "\"\n" +
|
||||
" ],\n" +
|
||||
" \"period\": " + ROLE_PERIOD + ",\n" +
|
||||
" \"bind_secret_id\": " + ROLE_BIND_SECRET + ",\n" +
|
||||
" \"bound_cidr_list\": \"\"\n" +
|
||||
" },\n" +
|
||||
" \"lease_duration\": 0,\n" +
|
||||
" \"renewable\": false,\n" +
|
||||
" \"lease_id\": \"\"\n" +
|
||||
"}";
|
||||
|
||||
private static final Map<String, Object> INVALID_DATA = new HashMap<>();
|
||||
|
||||
static {
|
||||
INVALID_DATA.put("policies", "fancy-policy");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getter, setter and get-methods for response data.
|
||||
*/
|
||||
@Test
|
||||
public void getDataRoundtrip() {
|
||||
// Create empty Object.
|
||||
AppRoleResponse res = new AppRoleResponse();
|
||||
assertThat("Initial data should be empty", res.getRole(), is(nullValue()));
|
||||
|
||||
// Parsing invalid auth data map should fail.
|
||||
try {
|
||||
res.setData(INVALID_DATA);
|
||||
fail("Parsing invalid data succeeded");
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||
*/
|
||||
@Test
|
||||
public void jsonRoundtrip() {
|
||||
try {
|
||||
AppRoleResponse res = new ObjectMapper().readValue(RES_JSON, AppRoleResponse.class);
|
||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||
// Extract role data.
|
||||
AppRole role = res.getRole();
|
||||
assertThat("Role data is NULL", role, is(notNullValue()));
|
||||
assertThat("Incorrect token TTL", role.getTokenTtl(), is(ROLE_TOKEN_TTL));
|
||||
assertThat("Incorrect token max TTL", role.getTokenMaxTtl(), is(ROLE_TOKEN_MAX_TTL));
|
||||
assertThat("Incorrect secret ID TTL", role.getSecretIdTtl(), is(ROLE_SECRET_TTL));
|
||||
assertThat("Incorrect secret ID umber of uses", role.getSecretIdNumUses(), is(ROLE_SECRET_NUM_USES));
|
||||
assertThat("Incorrect number of policies", role.getPolicies(), hasSize(1));
|
||||
assertThat("Incorrect role policies", role.getPolicies(), contains(ROLE_POLICY));
|
||||
assertThat("Incorrect role period", role.getPeriod(), is(ROLE_PERIOD));
|
||||
assertThat("Incorrect role bind secret ID flag", role.getBindSecretId(), is(ROLE_BIND_SECRET));
|
||||
assertThat("Incorrect biund CIDR list", role.getBoundCidrList(), is(nullValue()));
|
||||
assertThat("Incorrect biund CIDR list string", role.getBoundCidrListString(), is(emptyString()));
|
||||
} catch (IOException e) {
|
||||
fail("AuthResponse deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import de.stklcode.jvault.connector.model.AuthBackend;
|
||||
import de.stklcode.jvault.connector.model.response.embedded.AuthData;
|
||||
import de.stklcode.jvault.connector.model.response.embedded.AuthMethod;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* JUnit Test for {@link AuthMethodsResponse} model.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public class AuthMethodsResponseTest {
|
||||
private static final String GH_PATH = "github/";
|
||||
private static final String GH_TYPE = "github";
|
||||
private static final String GH_DESCR = "GitHub auth";
|
||||
private static final String TK_PATH = "token/";
|
||||
private static final String TK_TYPE = "token";
|
||||
private static final String TK_DESCR = "token based credentials";
|
||||
private static final Integer TK_LEASE_TTL = 0;
|
||||
private static final Integer TK_MAX_LEASE_TTL = 0;
|
||||
|
||||
private static final String RES_JSON = "{\n" +
|
||||
" \"data\": {" +
|
||||
" \"" + GH_PATH + "\": {\n" +
|
||||
" \"type\": \"" + GH_TYPE + "\",\n" +
|
||||
" \"description\": \"" + GH_DESCR + "\"\n" +
|
||||
" },\n" +
|
||||
" \"" + TK_PATH + "\": {\n" +
|
||||
" \"config\": {\n" +
|
||||
" \"default_lease_ttl\": " + TK_LEASE_TTL + ",\n" +
|
||||
" \"max_lease_ttl\": " + TK_MAX_LEASE_TTL + "\n" +
|
||||
" },\n" +
|
||||
" \"description\": \"" + TK_DESCR + "\",\n" +
|
||||
" \"type\": \"" + TK_TYPE + "\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final Map<String, Object> INVALID_DATA = new HashMap<>();
|
||||
|
||||
static {
|
||||
INVALID_DATA.put("dummy/", new Dummy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getter, setter and get-methods for response data.
|
||||
*/
|
||||
@Test
|
||||
public void getDataRoundtrip() {
|
||||
// Create empty Object.
|
||||
AuthMethodsResponse res = new AuthMethodsResponse();
|
||||
assertThat("Initial method map should be empty", res.getSupportedMethods(), is(anEmptyMap()));
|
||||
|
||||
// Parsing invalid data map should fail.
|
||||
try {
|
||||
res.setData(INVALID_DATA);
|
||||
fail("Parsing invalid data succeeded");
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||
*/
|
||||
@Test
|
||||
public void jsonRoundtrip() {
|
||||
try {
|
||||
AuthMethodsResponse res = new ObjectMapper().readValue(RES_JSON, AuthMethodsResponse.class);
|
||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||
// Extract auth data.
|
||||
Map<String, AuthMethod> supported = res.getSupportedMethods();
|
||||
assertThat("Auth data is NULL", supported, is(notNullValue()));
|
||||
assertThat("Incorrect number of supported methods", supported.entrySet(), hasSize(2));
|
||||
assertThat("Incorrect method paths", supported.keySet(), containsInAnyOrder(GH_PATH, TK_PATH));
|
||||
|
||||
// Verify first method.
|
||||
AuthMethod method = supported.get(GH_PATH);
|
||||
assertThat("Incorrect raw type for GitHub", method.getRawType(), is(GH_TYPE));
|
||||
assertThat("Incorrect parsed type for GitHub", method.getType(), is(AuthBackend.GITHUB));
|
||||
assertThat("Incorrect description for GitHub", method.getDescription(), is(GH_DESCR));
|
||||
assertThat("Unexpected config for GitHub", method.getConfig(), is(nullValue()));
|
||||
|
||||
// Verify first method.
|
||||
method = supported.get(TK_PATH);
|
||||
assertThat("Incorrect raw type for Token", method.getRawType(), is(TK_TYPE));
|
||||
assertThat("Incorrect parsed type for Token", method.getType(), is(AuthBackend.TOKEN));
|
||||
assertThat("Incorrect description for Token", method.getDescription(), is(TK_DESCR));
|
||||
assertThat("Missing config for Token", method.getConfig(), is(notNullValue()));
|
||||
assertThat("Unexpected config size for Token", method.getConfig().keySet(), hasSize(2));
|
||||
assertThat("Incorrect lease TTL config", method.getConfig().get("default_lease_ttl"), is(TK_LEASE_TTL.toString()));
|
||||
assertThat("Incorrect max lease TTL config", method.getConfig().get("max_lease_ttl"), is(TK_MAX_LEASE_TTL.toString()));
|
||||
} catch (IOException e) {
|
||||
fail("AuthResponse deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class Dummy {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import de.stklcode.jvault.connector.model.response.embedded.AuthData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* JUnit Test for {@link AuthResponse} model.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public class AuthResponseTest {
|
||||
private static final String AUTH_ACCESSOR = "2c84f488-2133-4ced-87b0-570f93a76830";
|
||||
private static final String AUTH_CLIENT_TOKEN = "ABCD";
|
||||
private static final String AUTH_POLICY_1 = "web";
|
||||
private static final String AUTH_POLICY_2 = "stage";
|
||||
private static final String AUTH_META_KEY = "user";
|
||||
private static final String AUTH_META_VALUE = "armon";
|
||||
private static final Integer AUTH_LEASE_DURATION = 3600;
|
||||
private static final Boolean AUTH_RENEWABLE = true;
|
||||
|
||||
private static final String RES_JSON = "{\n" +
|
||||
" \"auth\": {\n" +
|
||||
" \"accessor\": \"" + AUTH_ACCESSOR + "\",\n" +
|
||||
" \"client_token\": \"" + AUTH_CLIENT_TOKEN + "\",\n" +
|
||||
" \"policies\": [\n" +
|
||||
" \"" + AUTH_POLICY_1 + "\", \n" +
|
||||
" \"" + AUTH_POLICY_2 + "\"\n" +
|
||||
" ],\n" +
|
||||
" \"metadata\": {\n" +
|
||||
" \"" + AUTH_META_KEY + "\": \"" + AUTH_META_VALUE + "\"\n" +
|
||||
" },\n" +
|
||||
" \"lease_duration\": " + AUTH_LEASE_DURATION + ",\n" +
|
||||
" \"renewable\": " + AUTH_RENEWABLE + "\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final Map<String, Object> INVALID_AUTH_DATA = new HashMap<>();
|
||||
|
||||
static {
|
||||
INVALID_AUTH_DATA.put("policies", "fancy-policy");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getter, setter and get-methods for response data.
|
||||
*/
|
||||
@Test
|
||||
public void getDataRoundtrip() {
|
||||
// Create empty Object.
|
||||
AuthResponse res = new AuthResponse();
|
||||
assertThat("Initial data should be empty", res.getData(), is(nullValue()));
|
||||
|
||||
// Parsing invalid auth data map should fail.
|
||||
try {
|
||||
res.setAuth(INVALID_AUTH_DATA);
|
||||
fail("Parsing invalid auth data succeeded");
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
}
|
||||
|
||||
// Data method should be agnostic.
|
||||
res.setData(INVALID_AUTH_DATA);
|
||||
assertThat("Data not passed through", res.getData(), is(INVALID_AUTH_DATA));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||
*/
|
||||
@Test
|
||||
public void jsonRoundtrip() {
|
||||
try {
|
||||
AuthResponse res = new ObjectMapper().readValue(RES_JSON, AuthResponse.class);
|
||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||
// Extract auth data.
|
||||
AuthData data = res.getAuth();
|
||||
assertThat("Auth data is NULL", data, is(notNullValue()));
|
||||
assertThat("Incorrect auth accessor", data.getAccessor(), is(AUTH_ACCESSOR));
|
||||
assertThat("Incorrect auth client token", data.getClientToken(), is(AUTH_CLIENT_TOKEN));
|
||||
assertThat("Incorrect auth lease duration", data.getLeaseDuration(), is(AUTH_LEASE_DURATION));
|
||||
assertThat("Incorrect auth renewable flag", data.isRenewable(), is(AUTH_RENEWABLE));
|
||||
assertThat("Incorrect number of policies", data.getPolicies(), hasSize(2));
|
||||
assertThat("Incorrect auth policies", data.getPolicies(), containsInAnyOrder(AUTH_POLICY_1, AUTH_POLICY_2));
|
||||
} catch (IOException e) {
|
||||
fail("AuthResponse deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* JUnit Test for {@link AuthResponse} model.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public class HealthResponseTest {
|
||||
private static final String CLUSTER_ID = "c9abceea-4f46-4dab-a688-5ce55f89e228";
|
||||
private static final String CLUSTER_NAME = "vault-cluster-5515c810";
|
||||
private static final String VERSION = "0.6.2";
|
||||
private static final Long SERVER_TIME_UTC = 1469555798L;
|
||||
private static final Boolean STANDBY = false;
|
||||
private static final Boolean SEALED = false;
|
||||
private static final Boolean INITIALIZED = true;
|
||||
|
||||
private static final String RES_JSON = "{\n" +
|
||||
" \"cluster_id\": \"" + CLUSTER_ID + "\",\n" +
|
||||
" \"cluster_name\": \"" + CLUSTER_NAME + "\",\n" +
|
||||
" \"version\": \"" + VERSION + "\",\n" +
|
||||
" \"server_time_utc\": " + SERVER_TIME_UTC + ",\n" +
|
||||
" \"standby\": " + STANDBY + ",\n" +
|
||||
" \"sealed\": " + SEALED + ",\n" +
|
||||
" \"initialized\": " + INITIALIZED + "\n" +
|
||||
"}";
|
||||
/**
|
||||
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||
*/
|
||||
@Test
|
||||
public void jsonRoundtrip() {
|
||||
try {
|
||||
HealthResponse res = new ObjectMapper().readValue(RES_JSON, HealthResponse.class);
|
||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||
assertThat("Incorrect cluster ID", res.getClusterID(), is(CLUSTER_ID));
|
||||
assertThat("Incorrect cluster name", res.getClusterName(), is(CLUSTER_NAME));
|
||||
assertThat("Incorrect version", res.getVersion(), is(VERSION));
|
||||
assertThat("Incorrect server time", res.getServerTimeUTC(), is(SERVER_TIME_UTC));
|
||||
assertThat("Incorrect standby state", res.isStandby(), is(STANDBY));
|
||||
assertThat("Incorrect seal state", res.isSealed(), is(SEALED));
|
||||
assertThat("Incorrect initialization state", res.isInitialized(), is(INITIALIZED));
|
||||
} catch (IOException e) {
|
||||
fail("Health deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* JUnit Test for {@link SecretResponse} model.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public class SecretResponseTest {
|
||||
private static final Map<String, Object> DATA = new HashMap<>();
|
||||
private static final String KEY_UNKNOWN = "unknown";
|
||||
private static final String KEY_STRING = "test1";
|
||||
private static final String VAL_STRING = "testvalue";
|
||||
private static final String KEY_INTEGER = "test2";
|
||||
private static final Integer VAL_INTEGER = 42;
|
||||
private static final String KEY_LIST = "list";
|
||||
private static final String VAL_LIST = "[\"first\",\"second\"]";
|
||||
|
||||
private static final String SECRET_REQUEST_ID = "68315073-6658-e3ff-2da7-67939fb91bbd";
|
||||
private static final String SECRET_LEASE_ID = "";
|
||||
private static final Integer SECRET_LEASE_DURATION = 2764800;
|
||||
private static final boolean SECRET_RENEWABLE = false;
|
||||
private static final String SECRET_DATA_K1 = "excited";
|
||||
private static final String SECRET_DATA_V1 = "yes";
|
||||
private static final String SECRET_DATA_K2 = "value";
|
||||
private static final String SECRET_DATA_V2 = "world";
|
||||
private static final List<String> SECRET_WARNINGS = null;
|
||||
private static final String SECRET_JSON = "{\n" +
|
||||
" \"request_id\": \"" + SECRET_REQUEST_ID + "\",\n" +
|
||||
" \"lease_id\": \"" + SECRET_LEASE_ID + "\",\n" +
|
||||
" \"lease_duration\": " + SECRET_LEASE_DURATION + ",\n" +
|
||||
" \"renewable\": " + SECRET_RENEWABLE + ",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"" + SECRET_DATA_K1 + "\": \"" + SECRET_DATA_V1 + "\",\n" +
|
||||
" \"" + SECRET_DATA_K2 + "\": \"" + SECRET_DATA_V2 + "\"\n" +
|
||||
" },\n" +
|
||||
" \"warnings\": " + SECRET_WARNINGS + "\n" +
|
||||
"}";
|
||||
|
||||
|
||||
static {
|
||||
DATA.put(KEY_STRING, VAL_STRING);
|
||||
DATA.put(KEY_INTEGER, VAL_INTEGER);
|
||||
DATA.put(KEY_LIST, VAL_LIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getter, setter and get-methods for response data.
|
||||
*
|
||||
* @throws InvalidResponseException Should not occur
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getDataRoundtrip() throws InvalidResponseException {
|
||||
// Create empty Object.
|
||||
SecretResponse res = new SecretResponse();
|
||||
assertThat("Initial data should be Map", res.getData(), is(instanceOf(Map.class)));
|
||||
assertThat("Initial data should be empty", res.getData().entrySet(), empty());
|
||||
assertThat("Getter should return NULL on empty data map", res.get(KEY_STRING), is(nullValue()));
|
||||
|
||||
// Fill data map.
|
||||
res.setData(DATA);
|
||||
assertThat("Data setter/getter not transparent", res.getData(), is(DATA));
|
||||
assertThat("Data size modified", res.getData().keySet(), hasSize(DATA.size()));
|
||||
assertThat("Data keys not passed correctly", res.getData().keySet(), containsInAnyOrder(KEY_STRING, KEY_INTEGER, KEY_LIST));
|
||||
assertThat("Data values not passed correctly", res.get(KEY_STRING), is(VAL_STRING));
|
||||
assertThat("Data values not passed correctly", res.get(KEY_INTEGER), is(VAL_INTEGER));
|
||||
assertThat("Non-Null returned on unknown key", res.get(KEY_UNKNOWN), is(nullValue()));
|
||||
|
||||
// Try explicit JSON conversion.
|
||||
final List list = res.get(KEY_LIST, List.class);
|
||||
assertThat("JSON parsing of list failed", list, is(notNullValue()));
|
||||
assertThat("JSON parsing of list returned incorrect size", list.size(), is(2));
|
||||
assertThat("JSON parsing of list returned incorrect elements", (List<Object>)list, contains("first", "second"));
|
||||
assertThat("Non-Null returned on unknown key", res.get(KEY_UNKNOWN, Object.class), is(nullValue()));
|
||||
|
||||
// Requesting invalid class should result in Exception.
|
||||
try {
|
||||
res.get(KEY_LIST, Double.class);
|
||||
fail("JSON parsing to incorrect type succeeded.");
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||
*/
|
||||
@Test
|
||||
public void jsonRoundtrip() {
|
||||
try {
|
||||
SecretResponse res = new ObjectMapper().readValue(SECRET_JSON, SecretResponse.class);
|
||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||
assertThat("Incorrect lease ID", res.getLeaseId(), is(SECRET_LEASE_ID));
|
||||
assertThat("Incorrect lease duration", res.getLeaseDuration(), is(SECRET_LEASE_DURATION));
|
||||
assertThat("Incorrect renewable status", res.isRenewable(), is(SECRET_RENEWABLE));
|
||||
assertThat("Incorrect warnings", res.getWarnings(), is(SECRET_WARNINGS));
|
||||
assertThat("Response does not contain correct data", res.get(SECRET_DATA_K1), is(SECRET_DATA_V1));
|
||||
assertThat("Response does not contain correct data", res.get(SECRET_DATA_K2), is(SECRET_DATA_V2));
|
||||
} catch (IOException e) {
|
||||
fail("SecretResponse deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package de.stklcode.jvault.connector.model.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||
import de.stklcode.jvault.connector.model.response.embedded.TokenData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* JUnit Test for {@link TokenResponse} model.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.6.2
|
||||
*/
|
||||
public class TokenResponseTest {
|
||||
private static final Integer TOKEN_CREATION_TIME = 1457533232;
|
||||
private static final Integer TOKEN_TTL = 2764800;
|
||||
private static final String TOKEN_DISPLAY_NAME = "token";
|
||||
private static final Integer TOKEN_NUM_USES = 0;
|
||||
private static final Boolean TOKEN_ORPHAN = false;
|
||||
private static final String TOKEN_PATH = "auth/token/create";
|
||||
private static final String TOKEN_POLICY_1 = "default";
|
||||
private static final String TOKEN_POLICY_2 = "web";
|
||||
private static final Boolean RES_RENEWABLE = false;
|
||||
private static final Integer RES_TTL = 2591976;
|
||||
private static final Integer RES_LEASE_DURATION = 0;
|
||||
|
||||
private static final String RES_JSON = "{\n" +
|
||||
" \"lease_id\": \"\",\n" +
|
||||
" \"renewable\": " + RES_RENEWABLE + ",\n" +
|
||||
" \"lease_duration\": " + RES_LEASE_DURATION + ",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"creation_time\": " + TOKEN_CREATION_TIME + ",\n" +
|
||||
" \"creation_ttl\": " + TOKEN_TTL + ",\n" +
|
||||
" \"display_name\": \"" + TOKEN_DISPLAY_NAME + "\",\n" +
|
||||
" \"meta\": null,\n" +
|
||||
" \"num_uses\": " + TOKEN_NUM_USES + ",\n" +
|
||||
" \"orphan\": " + TOKEN_ORPHAN + ",\n" +
|
||||
" \"path\": \"" + TOKEN_PATH + "\",\n" +
|
||||
" \"policies\": [\n" +
|
||||
" \"" + TOKEN_POLICY_1 + "\", \n" +
|
||||
" \"" + TOKEN_POLICY_2 + "\"\n" +
|
||||
" ],\n" +
|
||||
" \"ttl\": " + RES_TTL + "\n" +
|
||||
" },\n" +
|
||||
" \"warnings\": null,\n" +
|
||||
" \"auth\": null\n" +
|
||||
"}";
|
||||
|
||||
private static final Map<String, Object> INVALID_TOKEN_DATA = new HashMap<>();
|
||||
|
||||
static {
|
||||
INVALID_TOKEN_DATA.put("num_uses", "fourtytwo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getter, setter and get-methods for response data.
|
||||
*/
|
||||
@Test
|
||||
public void getDataRoundtrip() {
|
||||
// Create empty Object.
|
||||
TokenResponse res = new TokenResponse();
|
||||
assertThat("Initial data should be empty", res.getData(), is(nullValue()));
|
||||
|
||||
// Parsing invalid data map should fail.
|
||||
try {
|
||||
res.setData(INVALID_TOKEN_DATA);
|
||||
fail("Parsing invalid token data succeeded");
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||
*/
|
||||
@Test
|
||||
public void jsonRoundtrip() {
|
||||
try {
|
||||
TokenResponse res = new ObjectMapper().readValue(RES_JSON, TokenResponse.class);
|
||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||
assertThat("Incorrect lease duration", res.getLeaseDuration(), is(RES_LEASE_DURATION));
|
||||
assertThat("Incorrect renewable status", res.isRenewable(), is(RES_RENEWABLE));
|
||||
// Extract token data.
|
||||
TokenData data = res.getData();
|
||||
assertThat("Token data is NULL", data, is(notNullValue()));
|
||||
assertThat("Incorrect token creation time", data.getCreationTime(), is(TOKEN_CREATION_TIME));
|
||||
assertThat("Incorrect token creation TTL", data.getCreationTtl(), is(TOKEN_TTL));
|
||||
assertThat("Incorrect token display name", data.getName(), is(TOKEN_DISPLAY_NAME));
|
||||
assertThat("Incorrect token number of uses", data.getNumUses(), is(TOKEN_NUM_USES));
|
||||
assertThat("Incorrect token orphan flag", data.isOrphan(), is(TOKEN_ORPHAN));
|
||||
assertThat("Incorrect token path", data.getPath(), is(TOKEN_PATH));
|
||||
assertThat("Incorrect response renewable flag", res.isRenewable(), is(RES_RENEWABLE));
|
||||
assertThat("Incorrect response TTL", data.getTtl(), is(RES_TTL));
|
||||
assertThat("Incorrect response lease duration", res.getLeaseDuration(), is(RES_LEASE_DURATION));
|
||||
} catch (IOException e) {
|
||||
fail("TokenResponse deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user