diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54d2526..6bfb8e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 0.7.0 [work in progress]
+* [feature] Retrieval of health status via `getHealth()`
+
## 0.6.2 [2017-08-19]
* [fix] Prevent potential NPE on SecretResponse getter
* [fix] Removed stack traces on PUT request and response deserialization (#13)
diff --git a/pom.xml b/pom.xml
index 9984ae2..fe163d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
de.stklcode.jvault
connector
- 0.6.2
+ 0.7.0-SNAPSHOT
jar
diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
index f1a7265..259be9b 100644
--- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
+++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
@@ -59,6 +59,7 @@ public class HTTPVaultConnector implements VaultConnector {
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_HEALTH = "sys/health";
private static final String HEADER_VAULT_TOKEN = "X-Vault-Token";
@@ -249,6 +250,25 @@ public class HTTPVaultConnector implements VaultConnector {
}
}
+ @Override
+ public HealthResponse getHealth() throws VaultConnectorException {
+ /* Force status code to be 200, so we don't need to modify the request sequence. */
+ Map 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("Unable to parse response", e);
+ } catch (URISyntaxException e) {
+ /* this should never occur and may leak sensible information */
+ throw new InvalidRequestException("Invalid URI format");
+ }
+ }
+
@Override
public final boolean isAuthorized() {
return authorized && (tokenTTL == 0 || tokenTTL >= System.currentTimeMillis());
diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
index a66c253..1a71cea 100644
--- a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
+++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
@@ -71,6 +71,15 @@ public interface VaultConnector extends AutoCloseable {
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.
*
diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/HealthResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/HealthResponse.java
new file mode 100644
index 0000000..3b5b791
--- /dev/null
+++ b/src/main/java/de/stklcode/jvault/connector/model/response/HealthResponse.java
@@ -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;
+ }
+}
diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java
index fec5d65..8ed531d 100644
--- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java
+++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java
@@ -48,6 +48,7 @@ import static org.junit.Assume.*;
* @since 0.1
*/
public class HTTPVaultConnectorTest {
+ private static String VAULT_VERISON = "0.8.1"; // the vault version this test is supposed to run against
private static String KEY = "81011a8061e5c028bd0d9503eeba40bd9054b9af0408d080cb24f57405c27a61";
private static String TOKEN_ROOT = "d1bd50e2-587b-6e68-d80b-a9a507625cb7";
private static String USER_VALID = "validUser";
@@ -135,6 +136,36 @@ public class HTTPVaultConnectorTest {
assertThat("Vault not unsealed", sealStatus.isSealed(), is(false));
}
+ /**
+ * 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();
+ connector.seal();
+ assumeTrue(connector.sealStatus().isSealed());
+ connector.resetAuth(); // SHould work unauthenticated
+ 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
*/
diff --git a/src/test/java/de/stklcode/jvault/connector/model/response/HealthResponseTest.java b/src/test/java/de/stklcode/jvault/connector/model/response/HealthResponseTest.java
new file mode 100644
index 0000000..d1b8707
--- /dev/null
+++ b/src/test/java/de/stklcode/jvault/connector/model/response/HealthResponseTest.java
@@ -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());
+ }
+ }
+}