From df7de5dd73d65f545df31b04d2e8b7a0b88594ba Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Mon, 28 Aug 2017 17:50:24 +0200 Subject: [PATCH] Health status query and response model implemented (#15) --- CHANGELOG.md | 3 + pom.xml | 2 +- .../jvault/connector/HTTPVaultConnector.java | 20 ++++ .../jvault/connector/VaultConnector.java | 9 ++ .../model/response/HealthResponse.java | 99 +++++++++++++++++++ .../connector/HTTPVaultConnectorTest.java | 31 ++++++ .../model/response/HealthResponseTest.java | 56 +++++++++++ 7 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/stklcode/jvault/connector/model/response/HealthResponse.java create mode 100644 src/test/java/de/stklcode/jvault/connector/model/response/HealthResponseTest.java 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()); + } + } +}