From 278c8a6d1f0518dc9d0875dbec84614b19800d5d Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Thu, 7 Jan 2021 20:03:54 +0100 Subject: switch from custom monitoring to Spring-Actuator healthchecks --- .../connector/controller/MonitoringController.java | 279 --------------------- .../health/EidasNodeMetadataHealthIndicator.java | 69 +++++ .../health/Saml2MetadataHealthIndicator.java | 44 ++++ .../tasks/GenerateCountrySelectionFrameTask.java | 11 +- .../EidasCacheTransactionStoreDecorator.java | 32 ++- 5 files changed, 144 insertions(+), 291 deletions(-) delete mode 100644 connector/src/main/java/at/asitplus/eidas/specific/connector/controller/MonitoringController.java create mode 100644 connector/src/main/java/at/asitplus/eidas/specific/connector/health/EidasNodeMetadataHealthIndicator.java create mode 100644 connector/src/main/java/at/asitplus/eidas/specific/connector/health/Saml2MetadataHealthIndicator.java (limited to 'connector/src/main/java/at') diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/MonitoringController.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/MonitoringController.java deleted file mode 100644 index f2d9fc8c..00000000 --- a/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/MonitoringController.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2018 A-SIT Plus GmbH - * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, - * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. - * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by - * the European Commission - subsequent versions of the EUPL (the "License"); - * You may not use this work except in compliance with the License. - * You may obtain a copy of the License at: - * https://joinup.ec.europa.eu/news/understanding-eupl-v12 - * - * 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. - * - * This product combines work with different licenses. See the "NOTICE" text - * file for details on the various modules and licenses. - * The "NOTICE" text file is part of the distribution. Any derivative works - * that you distribute must include a readable copy of the "NOTICE" text file. -*/ - -package at.asitplus.eidas.specific.connector.controller; - -import java.io.IOException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.transform.TransformerFactoryConfigurationError; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.CloseableHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; -import at.gv.egiz.eaaf.core.api.data.EaafConstants; -import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; -import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; -import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; -import at.gv.egiz.eaaf.core.impl.utils.DomUtils; -import at.gv.egiz.eaaf.core.impl.utils.Random; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataConfigurationFactory; -import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpMetadataBuilder; -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; - -@Controller -public class MonitoringController { - private static final Logger log = LoggerFactory.getLogger(MonitoringController.class); - - private static final String MESSAGE_OK = "OK"; - private static final String MESSAGE_ERROR = "ERROR"; - private static final String MESSAGE_SKIPPED = "SKIPPED"; - - private static final String TEST_STORAGE = "Storage: "; - private static final String TEST_CONFIG = "Config: "; - private static final String TEST_PVPMETADATA = "PVP_metadata: "; - private static final String TEST_EIDASNODEMETADATA = "eIDASNode_metadata: "; - - @Autowired - private ITransactionStorage storage; - @Autowired - private IConfigurationWithSP config; - - @Autowired private IHttpClientFactory httpClientFactory; - - @Autowired - private PvpMetadataBuilder metadatabuilder; - @Autowired - private IPvpMetadataConfigurationFactory configFactory; - private AbstractCredentialProvider pvpIdpCredentials; - - /** - * Sets a specific credential provider for PVP S-Profile IDP component. - * - * @param pvpIdpCredentials credential provider - */ - public void setPvpIdpCredentials(AbstractCredentialProvider pvpIdpCredentials) { - this.pvpIdpCredentials = pvpIdpCredentials; - - } - - /** - * Generic exception handling that wrote an error-message to html response. - * - * @param resp Http response object - * @param exception Error - * @throws IOException In case of a html response error. - */ - @ExceptionHandler({ Throwable.class }) - public void genericExceptionHandler(HttpServletResponse resp, Exception exception) throws IOException { - log.error("Monitoring Servlet receives an error.", exception); - resp.setContentType(EaafConstants.CONTENTTYPE_HTML_UTF8); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.getWriter().write("Reason: " - + StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(exception.getMessage()))); - - } - - /** - * MS-Connector status-monitoring end-point. - * - * @param req http request - * @param resp http response - * @throws IOException In case of a general processing error - */ - @RequestMapping(value = { MsEidasNodeConstants.ENDPOINT_MONITORING_MONITOR }, - method = { RequestMethod.GET }) - public void startFullTest(HttpServletRequest req, HttpServletResponse resp) throws IOException { - resp.setContentType(EaafConstants.CONTENTTYPE_HTML_UTF8); - - try { - testConfig(); - testStorage(); - testPvpMetadata(); - testEidasNodeMetadata(); - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().write(MESSAGE_OK); - - } catch (final Exception e) { - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.getWriter().write(MESSAGE_ERROR); - - } - - } - - /** - * MS-Connector internal verify monitoring end-point. - * - * @param req http request object - * @param resp http response object - * @throws IOException In case of an internal processing error - */ - @RequestMapping(value = { MsEidasNodeConstants.ENDPOINT_MONITORING_VERIFY }, - method = { RequestMethod.GET }) - - public void startSingleTests(HttpServletRequest req, HttpServletResponse resp) throws IOException { - String result = StringUtils.EMPTY; - try { - result += testConfig() + "
"; - } catch (final Exception e) { - result += e.getMessage() + "
"; - } - - try { - result += testStorage() + "
"; - } catch (final Exception e) { - result += e.getMessage() + "
"; - } - - try { - result += testPvpMetadata() + "
"; - } catch (final Exception e) { - result += e.getMessage() + "
"; - } - - try { - result += testEidasNodeMetadata() + "
"; - } catch (final Exception e) { - result += e.getMessage() + "
"; - } - - resp.setContentType(EaafConstants.CONTENTTYPE_HTML_UTF8); - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().write(result); - - } - - private String testStorage() throws Exception { - try { - final String key = Random.nextHexRandom16(); - final String value = Random.nextHexRandom16(); - - storage.put(key, value, -1); - final String result = storage.get(key, String.class); - storage.remove(key); - - if (result != null && result.equals(value)) { - return TEST_STORAGE + MESSAGE_OK; - } else { - log.warn("Montioring: TestValue: " + value + " does NOT match in Storage test"); - } - - } catch (final EaafException e) { - log.warn("Montioring: Can not read/write to storage.", e); - - } - - throw new Exception(TEST_STORAGE + MESSAGE_ERROR); - - } - - private String testConfig() throws Exception { - try { - if (config.getBasicConfigurationWithPrefix(MsEidasNodeConstants.PROP_CONFIG_SP_LIST_PREFIX) != null - && config.getBasicConfigurationWithPrefix(MsEidasNodeConstants.PROP_CONFIG_SP_LIST_PREFIX) - .size() > 0) { - return TEST_CONFIG + MESSAGE_OK; - } else { - log.warn("Montioring: Can not read from configuration file."); - } - - } catch (final Exception e) { - log.warn("Montioring: Can not read from configuration file.", e); - } - - throw new Exception(TEST_CONFIG + MESSAGE_ERROR); - - } - - private String testPvpMetadata() throws Exception { - try { - // build metadata - final IPvpMetadataBuilderConfiguration metadataConfig = - configFactory.generateMetadataBuilderConfiguration( - "http://localhost/monitoring", - pvpIdpCredentials); - metadatabuilder.buildPvpMetadata(metadataConfig); - return TEST_PVPMETADATA + MESSAGE_OK; - - } catch (Exception | TransformerFactoryConfigurationError e) { - log.warn("Monitoring: Has an error in '" + TEST_PVPMETADATA + "': " + e.getMessage(), e); - throw new Exception(TEST_PVPMETADATA + MESSAGE_ERROR, e); - - } - - } - - private String testEidasNodeMetadata() throws Exception { - try { - final String urlString = config.getBasicConfiguration( - MsEidasNodeConstants.PROP_CONFIG_MONITORING_EIDASNODE_METADATAURL); - if (StringUtils.isEmpty(urlString)) { - log.debug("No eIDASNode metadata URL. Skipping test ... "); - return TEST_EIDASNODEMETADATA + MESSAGE_SKIPPED; - - } - - // create HTTP client - CloseableHttpClient httpClient = httpClientFactory.getHttpClient(); - URIBuilder uriBuilder = new URIBuilder(urlString); - HttpUriRequest request = new HttpGet(uriBuilder.build()); - - final CloseableHttpResponse respCode = httpClient.execute(request); - if (respCode.getStatusLine().getStatusCode() != 200) { - log.warn("Monitoring: Has an error in '" + TEST_EIDASNODEMETADATA + "': " + " HTTP responsecode: " - + respCode); - throw new Exception(TEST_EIDASNODEMETADATA + MESSAGE_ERROR); - - } - - // parse metadata - DomUtils.parseXmlNonValidating(respCode.getEntity().getContent()); - - return TEST_EIDASNODEMETADATA + MESSAGE_OK; - - } catch (Exception | TransformerFactoryConfigurationError e) { - log.warn("Monitoring: Has an error in '" + TEST_EIDASNODEMETADATA + "': " + e.getMessage(), e); - throw new Exception(TEST_EIDASNODEMETADATA + MESSAGE_ERROR, e); - - } - - } - -} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/health/EidasNodeMetadataHealthIndicator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/health/EidasNodeMetadataHealthIndicator.java new file mode 100644 index 00000000..f160916c --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/health/EidasNodeMetadataHealthIndicator.java @@ -0,0 +1,69 @@ +package at.asitplus.eidas.specific.connector.health; + +import java.io.ByteArrayInputStream; + +import javax.xml.transform.TransformerFactoryConfigurationError; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; + +import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import at.gv.egiz.eaaf.core.impl.http.HttpUtils; +import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class EidasNodeMetadataHealthIndicator implements HealthIndicator { + + @Autowired IConfiguration config; + @Autowired IHttpClientFactory httpClientFactory; + + @Override + public Health health() { + try { + final String urlString = config.getBasicConfiguration( + MsEidasNodeConstants.PROP_CONFIG_MONITORING_EIDASNODE_METADATAURL); + if (StringUtils.isEmpty(urlString)) { + log.trace("No eIDASNode metadata URL. Skipping test ... "); + return Health.unknown().build(); + + } + + // create HTTP client + CloseableHttpClient httpClient = httpClientFactory.getHttpClient(); + URIBuilder uriBuilder = new URIBuilder(urlString); + HttpUriRequest request = new HttpGet(uriBuilder.build()); + + final Triple respCode = httpClient.execute(request, + HttpUtils.bodyStatusCodeResponseHandler()); + if (respCode.getFirst().getStatusCode() != 200) { + log.warn("Monitoring: Get http StatusCode: {} from eIDAS-Node Metadata endpoint", + respCode.getFirst().getStatusCode()); + return Health.down().withDetail("http StatusCode", respCode.getFirst().getStatusCode()).build(); + + } + + // parse metadata + DomUtils.parseXmlNonValidating(respCode.getSecond()); + + return Health.up().build(); + + } catch (Exception | TransformerFactoryConfigurationError e) { + log.warn("Monitoring: Can not read SAML2 metadata from eIDAS-Node", e); + return Health.down().down(e).build(); + + } + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/health/Saml2MetadataHealthIndicator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/health/Saml2MetadataHealthIndicator.java new file mode 100644 index 00000000..592231b0 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/health/Saml2MetadataHealthIndicator.java @@ -0,0 +1,44 @@ +package at.asitplus.eidas.specific.connector.health; + +import javax.xml.transform.TransformerFactoryConfigurationError; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; + +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataConfigurationFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpMetadataBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Saml2MetadataHealthIndicator implements HealthIndicator { + + @Autowired + private PvpMetadataBuilder metadatabuilder; + @Autowired + private IPvpMetadataConfigurationFactory configFactory; + + @Setter + private AbstractCredentialProvider pvpIdpCredentials; + + @Override + public Health health() { + try { + // build metadata + final IPvpMetadataBuilderConfiguration metadataConfig = + configFactory.generateMetadataBuilderConfiguration( + "http://localhost/monitoring", + pvpIdpCredentials); + metadatabuilder.buildPvpMetadata(metadataConfig); + return Health.up().build(); + + } catch (Exception | TransformerFactoryConfigurationError e) { + return Health.down().down(e).build(); + + } + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java index 86808f01..d3b8116a 100644 --- a/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java @@ -26,8 +26,6 @@ package at.asitplus.eidas.specific.connector.processes.tasks; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -39,7 +37,6 @@ import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.core.exceptions.GuiBuildException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; @@ -51,7 +48,6 @@ import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; */ @Component("GenerateCountrySelectionFrameTask") public class GenerateCountrySelectionFrameTask extends AbstractAuthServletTask { - private static final Logger log = LoggerFactory.getLogger(GenerateCountrySelectionFrameTask.class); @Autowired ISpringMvcGuiFormBuilder guiBuilder; @@ -77,16 +73,11 @@ public class GenerateCountrySelectionFrameTask extends AbstractAuthServletTask { guiBuilder.build(request, response, config, "BKU-Selection form"); - } catch (final GuiBuildException e) { - log.warn("Can not build GUI:'BKU-Selection'. Msg:" + e.getMessage()); + } catch (final Exception e) { throw new TaskExecutionException(pendingReq, "Can not build GUI. Msg:" + e.getMessage(), new EaafException("gui.00", new Object[] { e.getMessage() }, e)); - } catch (final Exception e) { - log.warn("FinalizeAuthenticationTask has an internal error", e); - throw new TaskExecutionException(pendingReq, e.getMessage(), e); - } } diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/EidasCacheTransactionStoreDecorator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/EidasCacheTransactionStoreDecorator.java index 557e245a..1ea5a280 100644 --- a/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/EidasCacheTransactionStoreDecorator.java +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/EidasCacheTransactionStoreDecorator.java @@ -30,17 +30,46 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; +import at.gv.egiz.eaaf.core.impl.utils.Random; -public class EidasCacheTransactionStoreDecorator implements ITransactionStorage { +public class EidasCacheTransactionStoreDecorator implements ITransactionStorage, HealthIndicator { private static final Logger log = LoggerFactory.getLogger(EidasCacheTransactionStoreDecorator.class); @Autowired(required = true) private CacheWithEidasBackend storage; + @Override + public Health health() { + try { + final String key = Random.nextHexRandom16(); + final String value = Random.nextHexRandom16(); + + this.put(key, value, -1); + final String result = this.get(key, String.class); + this.remove(key); + + if (result != null && result.equals(value)) { + return Health.up().build(); + + } else { + log.warn("Montioring: TestValue: " + value + " does NOT match in Storage test"); + return Health.down().build(); + + } + + } catch (final EaafException e) { + log.warn("Montioring: Can not read/write to storage.", e); + return Health.down().down(e).build(); + + } + } + @Override public void changeKey(String oldKey, String newKey, Object value) throws EaafException { if (containsKey(oldKey)) { @@ -148,5 +177,4 @@ public class EidasCacheTransactionStoreDecorator implements ITransactionStorage } } - } -- cgit v1.2.3