37 Commits

Author SHA1 Message Date
ed2b9d62a3 Preparations for 0.7.0 release 2017-10-03 17:39:03 +02:00
007b523295 Extracted nested try-blocks from response handling into new methods. 2017-10-03 17:29:11 +02:00
061c1e9743 Minor CleanUp
Reworked some JavaDoc comments and optimized imports.
2017-10-03 17:12:03 +02:00
6904ed6817 Added tests for parse exceptions. 2017-09-27 20:23:14 +02:00
1ed5d8d992 Added tests for unexpected 200 responses.
Some methods do expect code 204 (successful without result), those are now covered by tested.
2017-09-27 20:07:12 +02:00
f70fc084be Override toString() on ErrorResponse
Partially reverts last commit that added first error message to exception
without checking for presence of such.
2017-09-25 20:48:36 +02:00
4b14ab3f4b Offline tests with mocked Vault server
Added some tests that do not require an actual Vault server to test constructors and exceptional behavior.
2017-09-25 20:39:13 +02:00
29776f459e Fixed Exception class on invalid response during sealStatus() 2017-09-25 20:35:37 +02:00
4ca8aa56d2 Test constructors and exceptions on sealStatus 2017-09-24 13:23:13 +02:00
32ab9f4bb1 Updated dependencies 2017-09-24 11:39:26 +02:00
e002fc749a Adaptation to Vault 0.8 endpoints for renew and revoke leases (#11)
Breaking backwards compatibility with Vault 0.7 and below.
2017-09-24 11:08:00 +02:00
b9ad2d1551 Test against 0.8.3
Minor test adaptation, because default policy is no longer added to AppRole by default.
2017-09-23 14:46:41 +02:00
35a8c2e0fa Test constants made constant 2017-09-09 20:32:56 +02:00
89f7581d17 Test against 0.8.2 2017-09-08 19:21:41 +02:00
43511dc20b AppRole path generation with pre-formatted String 2017-08-29 09:04:04 +02:00
05b44759c0 Bundled common error messages in static inner class 2017-08-29 08:56:30 +02:00
ba17286ab3 Add Slack notification to Travis 2017-08-29 08:25:31 +02:00
51e505313a Removed deprecated method listAppRoleSecretss (#14)
Has been deprecated because of a typo in the method name in v0.6.2.
As of 0.7 it is now removed. Please use listAppRoleSecrets() instead.
2017-08-29 08:18:10 +02:00
a1784245a3 Refactored (un)seal methods to throw Exception instead of catching it (#12) 2017-08-28 19:24:52 +02:00
df7de5dd73 Health status query and response model implemented (#15) 2017-08-28 17:50:24 +02:00
23419e94f1 Preparations for 0.6.2 release 2017-08-19 12:27:20 +02:00
5b34cfcc27 Refactored extraction of auth backends
Changed iteration over keys ot iteration over entries.
Implemented unit test as part of this process.
2017-08-19 12:16:57 +02:00
b1c78b50d2 Added unit tests for response classes with embedded functionality
AppRoleResponse, AuthResponse, SecretResponse, TokenResponse
2017-08-19 11:28:04 +02:00
238ac8bd10 Do not print stacktrace in response models (#13) 2017-08-18 21:09:40 +02:00
c8a2e0784f Do not print stacktrace on PUT request (#13) 2017-08-18 20:58:57 +02:00
23f98f190b Fix typo in method name listAppRoleSecrets (#14) 2017-08-18 20:45:47 +02:00
745ab7a24c More connector unit tests
Listing of AppRole roles and secrets, seal-unseal roundtrip, closing
2017-08-18 20:27:06 +02:00
71f68f088f Unit tests for custom exceptions
... for the sake of coverage.
2017-08-18 18:28:36 +02:00
52a1abfb87 Fields of InvalidResposneException made final
Deprecated pseudo-builder methods withStatusCode() and withResponse() in
favor of additional constructor arguments to enforce fixed state.
2017-08-18 17:47:55 +02:00
4bf9df5f73 Quality Gate badge in ReadMe 2017-08-18 17:07:22 +02:00
8ae4dccdd6 Throw more speaking Exception when parsing body failed
Removed the fall-through behavior in code 200 exception.
2017-08-18 17:01:30 +02:00
7ac550230f Prevent potential NPE on SecretResponse getter 2017-08-18 16:58:33 +02:00
c1d519826c Add SonarCloud to Travis 2017-08-18 13:57:10 +02:00
a727ed960b Test against Vault 0.8.1 2017-08-16 19:41:38 +02:00
c685ec82a0 Test against Vault 0.8.0 2017-08-11 16:16:05 +02:00
934628f382 Test against Vault 0.8.0-rc1 2017-08-04 08:46:33 +02:00
ce90f9fba2 Readme 0.6.1 2017-08-04 08:37:03 +02:00
27 changed files with 1979 additions and 211 deletions

View File

@ -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="

View File

@ -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

View File

@ -1,6 +1,7 @@
# Java Vault Connector
[![Build Status](https://travis-ci.org/stklcode/jvaultconnector.svg?branch=master)](https://travis-ci.org/stklcode/jvaultconnector)
[![Quality Gate](https://sonarcloud.io/api/badges/gate?key=de.stklcode.jvault%3Aconnector)](https://sonarcloud.io/dashboard?id=de.stklcode.jvault%3Aconnector)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/stklcode/jvaultconnector/blob/master/LICENSE.txt)
[![Maven Central](https://img.shields.io/maven-central/v/de.stklcode.jvault/connector.svg)](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
View File

@ -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>

View File

@ -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 {
public final void seal() throws VaultConnectorException {
requestPut(PATH_SEAL, new HashMap<>());
return true;
} catch (VaultConnectorException e) {
e.printStackTrace();
return false;
}
}
@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());
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(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().size() > 0 && er.getErrors().get(0).equals("permission denied"))
if (!er.getErrors().isEmpty() && er.getErrors().get(0).equals("permission denied"))
throw new PermissionDeniedException();
throw ex.withResponse(er.toString());
} catch (IOException ignored) {
}
}
throw ex;
}
}
} catch (IOException e) {
throw new InvalidResponseException("Unable to read response", e);
} finally {
if (response != null && response.getEntity() != null)
try {
EntityUtils.consume(response.getEntity());
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() {
}
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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. */
}
}

View File

@ -27,6 +27,7 @@ public enum AuthBackend {
APPID("app-id"),
APPROLE("approle"),
USERPASS("userpass"),
GITHUB("github"), // Not supported yet.
UNKNOWN("");
private final String type;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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));
}

View File

@ -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());
}
}
}

View File

@ -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 {
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}