diff --git a/README.adoc b/README.adoc index 85d029f7..50b8916f 100644 --- a/README.adoc +++ b/README.adoc @@ -198,6 +198,12 @@ https://github.com/spring-cloud-incubator/spring-cloud-vault-config/blob/master/ chapters in the https://github.com/spring-cloud-incubator/spring-cloud-vault-config/blob/master/docs/src/main/asciidoc/spring-cloud-vault-config.adoc[reference guide]. +If the application imports the `spring-boot-starter-actuator` project, the +status of the vault server will be available via the `\health` endpoint. + +The vault health indicator can be enabled or disabled through the +property `health.vault.enabled` (default `true`) + == Building ==== Build requirements for Vault @@ -206,6 +212,20 @@ Spring Cloud Vault Config requires SSL certificates and a running Vault instance listening on `localhost:8200`. Certificates and the Vault setup are scripted, the scripts are located in `src/test/bash`. +The following scripts need to be run prior to building the project for the tests to pass. + + $ ./src/test/bash/install_vault.sh + $ ./src/test/bash/create_certificates.sh + $ ./src/test/bash/local_run_vault.sh + +Changes to the documentation should be made to the adocs found under `docs/src/main/asciidoc/` + +README.adoc can be re-generated via the following + + $ ./docs/src/main/ruby/generate_readme.sh > README.adoc + +This script requires ruby and the asciidoctor gem installed (`gem install asciidoctor`) + :jdkversion: 1.7 === Basic Compile and Test diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index bc9e3941..ba183f35 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -25,6 +25,20 @@ Spring Cloud Vault Config requires SSL certificates and a running Vault instance listening on `localhost:8200`. Certificates and the Vault setup are scripted, the scripts are located in `src/test/bash`. +The following scripts need to be run prior to building the project for the tests to pass. + + $ ./src/test/bash/install_vault.sh + $ ./src/test/bash/create_certificates.sh + $ ./src/test/bash/local_run_vault.sh + +Changes to the documentation should be made to the adocs found under `docs/src/main/asciidoc/` + +README.adoc can be re-generated via the following + + $ ./docs/src/main/ruby/generate_readme.sh > README.adoc + +This script requires ruby and the asciidoctor gem installed (`gem install asciidoctor`) + include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building.adoc[] == Contributing diff --git a/docs/src/main/asciidoc/quickstart.adoc b/docs/src/main/asciidoc/quickstart.adoc index a3fbad1f..b59c3a6f 100644 --- a/docs/src/main/asciidoc/quickstart.adoc +++ b/docs/src/main/asciidoc/quickstart.adoc @@ -178,3 +178,9 @@ https://github.com/spring-cloud-incubator/spring-cloud-vault-config/blob/master/ https://github.com/spring-cloud-incubator/spring-cloud-vault-config/blob/master/docs/src/main/asciidoc/spring-cloud-vault-config.adoc#token-authentication[authentication]. Checkout these chapters in the https://github.com/spring-cloud-incubator/spring-cloud-vault-config/blob/master/docs/src/main/asciidoc/spring-cloud-vault-config.adoc[reference guide]. + +If the application imports the `spring-boot-starter-actuator` project, the +status of the vault server will be available via the `\health` endpoint. + +The vault health indicator can be enabled or disabled through the +property `health.vault.enabled` (default `true`) diff --git a/spring-cloud-vault-config/pom.xml b/spring-cloud-vault-config/pom.xml index b58d5fdd..edd34532 100644 --- a/spring-cloud-vault-config/pom.xml +++ b/spring-cloud-vault-config/pom.xml @@ -21,6 +21,11 @@ spring-cloud-vault-core + + org.springframework.boot + spring-boot-starter-actuator + + org.springframework.cloud spring-cloud-vault-core diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapHealthIndicator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapHealthIndicator.java new file mode 100644 index 00000000..501136c7 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigBootstrapHealthIndicator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 the original author or authors. + * + * 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 org.springframework.cloud.vault.config; + +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.vault.VaultBootstrapConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Stuart Ingram + */ +@Configuration +@EnableConfigurationProperties +@ConditionalOnBean(VaultBootstrapConfiguration.class) +@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) +@ConditionalOnExpression("${health.vault.enabled:true}") +@AutoConfigureBefore({ EndpointAutoConfiguration.class }) +@AutoConfigureAfter({ HealthIndicatorAutoConfiguration.class }) +public class VaultConfigBootstrapHealthIndicator{ + @Bean + @ConditionalOnMissingBean(name = "vaultHealthIndicator") + public HealthIndicator vaultHealthIndicator() { + return new VaultHealthIndicator(); + } + +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java new file mode 100644 index 00000000..a4cb888e --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 the original author or authors. + * + * 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 org.springframework.cloud.vault.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.cloud.vault.VaultHealthResponse; + +/** + * @author Stuart Ingram + */ +public class VaultHealthIndicator implements HealthIndicator { + @Autowired + private VaultTemplate vaultTemplate; + + @Override + public Health health() { + try { + VaultHealthResponse vaultHealthResponse = vaultTemplate.health(); + if(!vaultHealthResponse.isInitialized()) { + return Health.down().withDetail("Vault uninitialized",null).build(); + } else if (vaultHealthResponse.isSealed()) { + return Health.down().withDetail("Vault sealed",null).build(); + } else if (vaultHealthResponse.isStandby()) { + return Health.outOfService().withDetail("Vault in standby",null).build(); + } else { + return Health.up().build(); + } + } + catch(Exception e) { + return Health.down().build(); + } + + } + +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java index bd41cf86..95e009a4 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultTemplate.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.cloud.vault.ClientAuthentication; import org.springframework.cloud.vault.VaultClient; import org.springframework.cloud.vault.VaultClientResponse; +import org.springframework.cloud.vault.VaultHealthResponse; import org.springframework.cloud.vault.VaultProperties; import org.springframework.cloud.vault.VaultToken; import org.springframework.util.Assert; @@ -112,4 +113,30 @@ public class VaultTemplate implements InitializingBean, VaultOperations { URI uri = client.buildUri(properties, pathTemplate, variables); return sessionCallback.doWithVault(uri, vaultSession); } + + private final static String HEALTH_URL_TEMPLATE = "sys/health"; + + /** + * Query the current Vault service for it's health status + * + * @return A {@link VaultHealthResponse} containing the current service status. + */ + public VaultHealthResponse health() { + URI uri = client.buildUri(properties, HEALTH_URL_TEMPLATE); + return client.health(uri); + } + + /** + * Check whether Vault is available (vault created and unsealed). + * + * @return + */ + public boolean isAvailable() { + try{ + VaultHealthResponse health = health(); + return health.isInitialized() && !health.isSealed(); + } catch(Exception e) { + return false; + } + } } diff --git a/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories b/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories index 64084bbb..c042b3b4 100644 --- a/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ # Bootstrap Configuration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -org.springframework.cloud.vault.config.VaultConfigBootstrapConfiguration \ No newline at end of file +org.springframework.cloud.vault.config.VaultConfigBootstrapConfiguration,\ +org.springframework.cloud.vault.config.VaultConfigBootstrapHealthIndicator \ No newline at end of file diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java index 35fd2400..e1b799de 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultClient.java @@ -15,6 +15,8 @@ */ package org.springframework.cloud.vault; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.net.URI; import java.util.Map; @@ -26,6 +28,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import lombok.Getter; @@ -102,6 +105,29 @@ public class VaultClient { createHeaders(vaultToken))); } + /** + * Query the current Vault service for it's health status + * + * @param uri must not be {@literal null}. + * @return A {@link VaultHealthResponse} containing the current service status. + */ + public VaultHealthResponse health(URI uri) { + try { + ResponseEntity healthResponse = this.restTemplate.exchange( + uri, HttpMethod.GET, null, + VaultHealthResponse.class); + return healthResponse.getBody(); + } catch (HttpStatusCodeException responseError) { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(responseError.getResponseBodyAsString(), VaultHealthResponse.class); + } + catch (Exception jsonError) { + throw responseError; + } + } + } + private VaultClientResponse exchange(URI uri, HttpMethod httpMethod, HttpEntity httpEntity) { @@ -143,7 +169,7 @@ public class VaultClient { * Build the Vault {@link URI} based on the given {@link VaultProperties} and * {@code pathTemplate}. URI template variables will be expanded using * {@code uriVariables}. - * + * * @param properties must not be {@literal null}. * @param pathTemplate must not be empty or {@literal null}. * @param uriVariables must not be {@literal null}. diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultHealthResponse.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultHealthResponse.java new file mode 100644 index 00000000..8ce90c2b --- /dev/null +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultHealthResponse.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 the original author or authors. + * + * 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 org.springframework.cloud.vault; + +import lombok.Data; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Value object to bind HTTP API responses for sys/health + * + * @author Stuart Ingram + * @author Bill Koch + */ +@Data +public class VaultHealthResponse { + private boolean initialized; + private boolean sealed; + private boolean standby; + @JsonProperty("server_time_utc") + private int serverTimeUtc; +}