diff --git a/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java b/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java index a4cfecc686..a331b89d75 100644 --- a/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java +++ b/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java @@ -40,6 +40,18 @@ public class SslContext { private static AtomicBoolean isInitialized = new AtomicBoolean(false); private static AtomicReference configurer = new AtomicReference<>(); + public synchronized static void reset() { + clientCertValid = null; + clientCertApiml = null; + clientCertUser = null; + clientCertUnknownUser = null; + apimlRootCert = null; + selfSignedUntrusted = null; + tlsWithoutCert = null; + configurer.set(null); + isInitialized.set(false); + } + public synchronized static void prepareSslAuthentication(SslContextConfigurer providedConfigurer) throws Exception { if (configurer.get() != null && !configurer.get().equals(providedConfigurer)) { diff --git a/build.gradle b/build.gradle index 745d76579a..796b81ece5 100644 --- a/build.gradle +++ b/build.gradle @@ -108,6 +108,7 @@ configure(subprojects.findAll {it.name != 'platform'}) { jvmArgs '--add-opens=java.base/java.io=ALL-UNNAMED' jvmArgs '--add-opens=java.base/java.util=ALL-UNNAMED' jvmArgs '--add-opens=java.base/java.util.concurrent=ALL-UNNAMED' + jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED' jvmArgs '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED' jvmArgs '--add-opens=java.base/java.lang.reflect=ALL-UNNAMED' jvmArgs '--add-opens=java.base/javax.net.ssl=ALL-UNNAMED' diff --git a/discovery-service/src/main/java/org/zowe/apiml/discovery/config/DiscoveryErrorController.java b/discovery-service/src/main/java/org/zowe/apiml/discovery/config/DiscoveryErrorController.java new file mode 100644 index 0000000000..4b49270fc2 --- /dev/null +++ b/discovery-service/src/main/java/org/zowe/apiml/discovery/config/DiscoveryErrorController.java @@ -0,0 +1,48 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ + +package org.zowe.apiml.discovery.config; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.List; +import java.util.Map; + +@Primary +@Controller +@RequestMapping(value = "${server.error.path:${error.path:/error}}") +public class DiscoveryErrorController extends BasicErrorController { + + public DiscoveryErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List errorViewResolvers) { + super(errorAttributes, serverProperties.getError(), errorViewResolvers); + } + + @Override + @RequestMapping // NOSONAR - No security risk, the controller cleans body for specified endpoint patterns + public ResponseEntity> error(HttpServletRequest request) { + HttpStatus status = getStatus(request); + String originalUrl = String.valueOf(request.getAttribute("jakarta.servlet.error.request_uri")); + if ((status == HttpStatus.NOT_FOUND) && StringUtils.startsWith(originalUrl, "/eureka/apps/")) { + return new ResponseEntity<>(status); + } + return super.error(request); + } + +} diff --git a/discovery-service/src/test/java/org/zowe/apiml/discovery/eureka/EurekaEndpointTest.java b/discovery-service/src/test/java/org/zowe/apiml/discovery/eureka/EurekaEndpointTest.java new file mode 100644 index 0000000000..f28388bc9c --- /dev/null +++ b/discovery-service/src/test/java/org/zowe/apiml/discovery/eureka/EurekaEndpointTest.java @@ -0,0 +1,75 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ + +package org.zowe.apiml.discovery.eureka; + +import io.restassured.http.ContentType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import org.zowe.apiml.discovery.functional.DiscoveryFunctionalTest; +import org.zowe.apiml.util.config.SslContext; +import org.zowe.apiml.util.config.SslContextConfigurer; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.core.IsEqual.equalTo; + +@ActiveProfiles("https") +@TestInstance(Lifecycle.PER_CLASS) +public class EurekaEndpointTest extends DiscoveryFunctionalTest { + + @Value("${server.ssl.keyPassword}") + char[] password; + @Value("${server.ssl.keyStore}") + String client_cert_keystore; + @Value("${server.ssl.keyStore}") + String keystore; + + @BeforeEach + void setup() throws Exception { + SslContextConfigurer configurer = new SslContextConfigurer(password, client_cert_keystore, keystore); + SslContext.prepareSslAuthentication(configurer); + } + + @BeforeAll + void init() { + SslContext.reset(); + } + + @AfterAll + void tearDown() { + SslContext.reset(); + } + + @Override + protected String getProtocol() { + return "https"; + } + + @Test + void givenInvalidService_whenRenewInstance_thenReturnEmptyBody() { + given() + .config(SslContext.clientCertApiml) + .contentType(ContentType.JSON) + .log().all() + .when() + .put(getDiscoveryUriWithPath("/eureka/apps/unknown-service-id/unknown-instance-id")) + .then() + .statusCode(HttpStatus.NOT_FOUND.value()) + .body(equalTo("")); + } + +}