From c3f66d6c4d1debe4389490d9e247662bfac9bfa1 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 25 Aug 2026 15:31:26 +0200 Subject: [PATCH 1/9] fix: make actuator endpoints authenticated Signed-off-by: Pablo Carle --- .../org/zowe/apiml/WebSecurityConfig.java | 7 ++--- .../apiml/acceptance/ActuatorConfigTest.java | 28 +++++++++++++++---- .../apiml/acceptance/AttlsConfigTest.java | 6 ++++ .../apiml/gateway/config/WebSecurity.java | 4 --- .../acceptance/ActuatorConfigTest.java | 28 +++++++++++++++---- .../gateway/acceptance/AttlsConfigTest.java | 1 + .../gateway/acceptance/RequestLimitTest.java | 6 ++++ 7 files changed, 62 insertions(+), 18 deletions(-) diff --git a/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java b/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java index 99357e1481..8d471514c9 100644 --- a/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java +++ b/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java @@ -123,13 +123,12 @@ public class WebSecurityConfig { private static final List UNAUTHENTICATED_PATTERNS = List.of( "/application/", - "/application/version", "/eureka/css/**", "/eureka/js/**", "/eureka/fonts/**", "/eureka/images/**", - APPLICATION_INFO, - "/favicon.ico"); + "/favicon.ico" + ); private final ServerWebExchangeMatcher discoveryPortMatcher = exchange -> exchange.getRequest().getURI().getPort() == internalDiscoveryPort ? MatchResult.match() : MatchResult.notMatch(); private final ServerWebExchangeMatcher isInUnauthenticatedPaths = pathMatchers(UNAUTHENTICATED_PATTERNS.toArray(new String[]{})); @@ -332,7 +331,7 @@ SafAuthorizationManager actuatorAuthorizationManager(SafRe /** * Security filter chain that protects all endpoints under the path "/application/**", - * except for "/application/health" - which is handled separately based on the configuration - and "/application/info". + * except for "/application/health" - which is handled separately based on the configuration. *

* This chain requires that all incoming requests to the matched paths are authenticated, * either via Basic Authentication or Bearer JWT token. diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java index 281e9d4345..acd582ba9e 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java @@ -126,7 +126,10 @@ void setUp() { @ParameterizedTest @CsvSource({ "/application/loggers", - "/application/gateway" + "/application/gateway", + "/application/version", + "/application/health", + "/application/info" }) void whenAccessDangerousActuatorWithoutCredentials_thenBlock(String endpoint) { given() @@ -136,6 +139,21 @@ void whenAccessDangerousActuatorWithoutCredentials_thenBlock(String endpoint) { .statusCode(SC_UNAUTHORIZED); } + @ParameterizedTest + @CsvSource({ + "/application/version", + "/application/health", + "/application/info" + }) + void whenAccessInfoActuatorWithCredentials_thenAllow(String endpoint) { + given() + .cookie(AUTH_COOKIE, login(USER)) + .when() + .get(basePath + endpoint) + .then() + .statusCode(SC_OK); + } + @ParameterizedTest @CsvSource({ "/application/loggers", @@ -161,10 +179,10 @@ void whenAccessDangerousActuatorWithCredentials_thenBlock(String endpoint) { "server.ssl.trustStore=../keystore/service/service.truststore.p12", "apiml.security.auth.provider=dummy", "logging.level.reactor.netty=ERROR", - "org.springframework.http.server.reactive=DEBUG", - "org.springframework.security=DEBUG", - "org.springframework.web.reactive=DEBUG", - "org.springframework.web.reactive.socket=DEBUG" + "logging.level.org.springframework.http.server.reactive=DEBUG", + "logging.level.org.springframework.security=DEBUG", + "logging.level.org.springframework.web.reactive=DEBUG", + "logging.level.org.springframework.web.reactive.socket=DEBUG" } ) @DirtiesContext diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/AttlsConfigTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/AttlsConfigTest.java index 993f6e4523..40f9092258 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/AttlsConfigTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/AttlsConfigTest.java @@ -87,6 +87,11 @@ private String getGatewayUrlWithPath(String hostname, int port, String scheme, S webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT ) @TestInstance(Lifecycle.PER_CLASS) + @TestPropertySource( + properties = { + "apiml.health.protected=false" + } + ) class GivenAttlsProfile { @LocalServerPort @@ -157,6 +162,7 @@ void requestFailsWithAttlsReasonWithHttp() { @Nested @TestPropertySource( properties = { + "apiml.health.protected=false", "server.ssl.keyStoreType=", "server.ssl.keyStorePassword=", "server.ssl.keyPassword=", diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java index 824c65c132..80f2062efe 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java @@ -467,10 +467,6 @@ SecurityWebFilterChain securityWebFilterChain( authorizeExchangeSpec .pathMatchers("/application/info", "/application/version", "/application/health") .permitAll(); - } else { - authorizeExchangeSpec - .pathMatchers("/application/info", "/application/version") - .permitAll(); } } ) diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java index 69212605a3..066803ddd2 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java @@ -117,7 +117,10 @@ void setUp() { @ParameterizedTest @CsvSource({ "/application/loggers", - "/application/gateway" + "/application/gateway", + "/application/version", + "/application/health", + "/application/info" }) void whenAccessDangerousActuatorWithoutCredentials_thenBlock(String endpoint) { given() @@ -127,6 +130,21 @@ void whenAccessDangerousActuatorWithoutCredentials_thenBlock(String endpoint) { .statusCode(SC_UNAUTHORIZED); } + @ParameterizedTest + @CsvSource({ + "/application/version", + "/application/health", + "/application/info" + }) + void whenAccessInfoActuatorWithCredentials_thenAllow(String endpoint) { + given() + .cookie(AUTH_COOKIE, login(USER)) + .when() + .get(basePath + "/application/info") + .then() + .statusCode(SC_OK); + } + @ParameterizedTest @CsvSource({ "/application/loggers", @@ -241,10 +259,10 @@ void whenAccessDangerousActuator_thenAllowRead(String endpoint) { "server.ssl.trustStore=../keystore/service/service.truststore.p12", "apiml.security.auth.provider=dummy", "logging.level.reactor.netty=ERROR", - "org.springframework.http.server.reactive=DEBUG", - "org.springframework.security=DEBUG", - "org.springframework.web.reactive=DEBUG", - "org.springframework.web.reactive.socket=DEBUG" + "logging.level.org.springframework.http.server.reactive=DEBUG", + "logging.level.org.springframework.security=DEBUG", + "logging.level.org.springframework.web.reactive=DEBUG", + "logging.level.org.springframework.web.reactive.socket=DEBUG" } ) @MicroservicesAcceptanceTest diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/AttlsConfigTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/AttlsConfigTest.java index a4cdc2f4ba..be6c98aa15 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/AttlsConfigTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/AttlsConfigTest.java @@ -126,6 +126,7 @@ void requestFailsWithAttlsReasonWithHttp() { @Nested @TestPropertySource( properties = { + "apiml.health.protected=false", "server.ssl.keyStoreType=", "server.ssl.keyStorePassword=", "server.ssl.keyPassword=", diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RequestLimitTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RequestLimitTest.java index 5dd042aa3b..2367f240a8 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RequestLimitTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RequestLimitTest.java @@ -13,6 +13,7 @@ import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.springframework.test.context.TestPropertySource; import org.zowe.apiml.gateway.acceptance.common.AcceptanceTestWithBasePath; import org.zowe.apiml.gateway.acceptance.common.MicroservicesAcceptanceTest; @@ -25,6 +26,11 @@ */ @MicroservicesAcceptanceTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestPropertySource( + properties = { + "apiml.health.protected=false" + } +) class RequestLimitTest extends AcceptanceTestWithBasePath { private static final String HEADER_10KB = StringUtils.repeat("1", 10_000); From fc93fa04fc629d7c7c169416d55c55f6e3f0ca78 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 25 Aug 2026 16:38:24 +0200 Subject: [PATCH 2/9] update tests Signed-off-by: Pablo Carle --- .../apiml/acceptance/ActuatorConfigTest.java | 4 +- .../apiml/acceptance/AvailabilityTest.java | 8 +++- .../acceptance/ActuatorConfigTest.java | 4 +- .../apiml/functional/gateway/VersionTest.java | 39 +++++++++++++++++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java index acd582ba9e..71c4ce60df 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/ActuatorConfigTest.java @@ -129,7 +129,9 @@ void setUp() { "/application/gateway", "/application/version", "/application/health", - "/application/info" + "/application/info", + "/gateway/version", + "/gateway/api/v1/version" }) void whenAccessDangerousActuatorWithoutCredentials_thenBlock(String endpoint) { given() diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java index 989b86d2f6..3a676d4599 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java @@ -17,6 +17,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; import org.springframework.web.server.i18n.FixedLocaleContextResolver; import org.springframework.web.server.i18n.LocaleContextResolver; @@ -32,6 +33,11 @@ @AcceptanceTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ActiveProfiles({ "test", "ApimlModulithAcceptanceTest", "AvailabilityTest" }) +@TestPropertySource( + properties = { + "apiml.health.protected=false", + } +) class AvailabilityTest extends AcceptanceTestWithBasePath { @ParameterizedTest(name = "{0} is available at port {1} with status {2}") @@ -47,7 +53,7 @@ void serviceIsAvailable(String serviceName, int servicePort, int expectedStatus) .untilAsserted(() -> given() .when() - .get("https://localhost:" + actualPort) + .get("https://localhost:" + actualPort + "/application/info") .then() .statusCode(expectedStatus) .header("Strict-Transport-Security", notNullValue()) diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java index 066803ddd2..39030ef126 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/ActuatorConfigTest.java @@ -120,7 +120,9 @@ void setUp() { "/application/gateway", "/application/version", "/application/health", - "/application/info" + "/application/info", + "/gateway/version", + "/gateway/api/v1/version" }) void whenAccessDangerousActuatorWithoutCredentials_thenBlock(String endpoint) { given() diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java index 17729c8869..8d0c01e4a9 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java @@ -15,9 +15,12 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.zowe.apiml.util.config.ConfigReader; +import org.zowe.apiml.util.config.Credentials; import org.zowe.apiml.util.http.HttpRequestUtils; import static io.restassured.RestAssured.given; +import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; import static org.apache.http.HttpStatus.SC_OK; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -49,15 +52,45 @@ class WhenRequestingVersion { void returnValidVersion(String endpoint) { // Gateway request to url given() - .when() + .when() .get(HttpRequestUtils.getUriFromGateway(endpoint)) - .then() + .then() + .statusCode(SC_UNAUTHORIZED) + .body("apiml.version", is(not(nullValue()))) + .body("apiml.buildNumber", is(not(nullValue()))) + .body("apiml.commitHash", is(not(nullValue()))); + } + + } + + } + + @Nested + class GivenAuthentication { + + // TODO login + private static final Credentials CREDENTIALS = ConfigReader.environmentConfiguration().getCredentials(); + + @Nested + class WhenRequestingVersion { + + @ParameterizedTest(name = "ReturnValidVersion {index} {0} ") + @MethodSource("org.zowe.apiml.functional.gateway.VersionTest#versionUrls") + void returnValidVersion(String endpoint) { + // Gateway request to url + given() + .auth().basic(CREDENTIALS.getUser(), CREDENTIALS.getPassword()) + .when() + .get(HttpRequestUtils.getUriFromGateway(endpoint)) + .then() .statusCode(SC_OK) .body("apiml.version", is(not(nullValue()))) .body("apiml.buildNumber", is(not(nullValue()))) .body("apiml.commitHash", is(not(nullValue()))); } + } + } -} +} From 8a1169d9a4b3280ddb7b0616479080f0b44f356d Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 25 Aug 2026 16:49:20 +0200 Subject: [PATCH 3/9] fix compilation issue Signed-off-by: Pablo Carle --- .../java/org/zowe/apiml/functional/gateway/VersionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java index 8d0c01e4a9..d73ef3d222 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java @@ -20,8 +20,8 @@ import org.zowe.apiml.util.http.HttpRequestUtils; import static io.restassured.RestAssured.given; -import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; import static org.apache.http.HttpStatus.SC_OK; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; From 3e1c352e0f68e911f1e6108b6a48404dbb8a5137 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 26 Aug 2026 10:37:06 +0200 Subject: [PATCH 4/9] fix gateway tests Signed-off-by: Pablo Carle --- .../org/zowe/apiml/gateway/config/WebSecurity.java | 12 +++++++++--- .../acceptance/corsTests/GatewayCorsTest.java | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java index 80f2062efe..5b77cdea6f 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java @@ -129,12 +129,16 @@ @EnableConfigurationProperties(SafSecurityConfigurationProperties.class) public class WebSecurity { - private static final String APPLICATION = "/application/**"; public static final String CONTEXT_PATH = "/" + CoreService.GATEWAY.getServiceId(); public static final String REGISTRY_PATH = CONTEXT_PATH + "/api/v1/registry"; public static final String COOKIE_NONCE = "oidc_nonce"; public static final String COOKIE_STATE = "oidc_state"; public static final String COOKIE_RETURN_URL = "oidc_return_url"; + + private static final String APPLICATION = "/application/**"; + private static final String APPLICATION_VERSION_GW = "/gateway/version"; + private static final String APPLICATION_VERSION_GW_ROUTE = "/gateway/api/v1/version"; + private static final Pattern CLIENT_REG_ID = Pattern.compile("^" + CONTEXT_PATH + "/login/oauth2/code/([^/]+)$"); private static final Predicate HAS_NO_VALUE = cookie -> cookie == null || StringUtils.isEmpty(cookie.getValue()); private static final List COOKIES = Arrays.asList(COOKIE_NONCE, COOKIE_STATE, COOKIE_RETURN_URL); @@ -460,12 +464,14 @@ SecurityWebFilterChain securityWebFilterChain( SERVICES_SHORT_URL + "/**", SERVICES_FULL_URL, SERVICES_FULL_URL + "/**", - APPLICATION + APPLICATION, + APPLICATION_VERSION_GW, + APPLICATION_VERSION_GW_ROUTE )) .authorizeExchange(authorizeExchangeSpec -> { if (!isHealthEndpointProtected) { authorizeExchangeSpec - .pathMatchers("/application/info", "/application/version", "/application/health") + .pathMatchers("/application/info", "/application/version", "/application/health", "/gateway/version", "/gateway/api/v1/version") .permitAll(); } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/corsTests/GatewayCorsTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/corsTests/GatewayCorsTest.java index 856651f31f..7279ad3476 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/corsTests/GatewayCorsTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/corsTests/GatewayCorsTest.java @@ -47,7 +47,8 @@ class GatewayCorsTest { @ActiveProfiles({"GatewayCorsEnabledWithProvidedDefaultTest"}) @TestPropertySource(properties = { "apiml.service.corsDefaultAllowedOrigins=https://foo.bar.org", - "apiml.service.corsEnabled=true" + "apiml.service.corsEnabled=true", + "apiml.health.protected=false" }) class GatewayCorsEnabledWithProvidedDefaultTest extends AcceptanceTestWithMockServices { From 5db4a31851e752db1ce75fb18033ef4fc8f3dcf6 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 26 Aug 2026 11:55:51 +0200 Subject: [PATCH 5/9] fixes Signed-off-by: Pablo Carle --- .../org/zowe/apiml/WebSecurityConfig.java | 20 ++++++++++++++----- .../apiml/acceptance/AvailabilityTest.java | 2 +- .../acceptance/corsTests/GatewayCorsTest.java | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java b/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java index 8d471514c9..7fe04c734f 100644 --- a/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java +++ b/apiml/src/main/java/org/zowe/apiml/WebSecurityConfig.java @@ -49,6 +49,7 @@ import org.zowe.apiml.security.common.auth.saf.SafAuthorizationManager; import org.zowe.apiml.security.common.auth.saf.SafResourceAccessVerifying; import org.zowe.apiml.security.common.config.AuthConfigurationProperties; +import org.zowe.apiml.security.common.config.CustomHstsServerHttpHeadersWriter; import org.zowe.apiml.security.common.filter.CategorizeCertsWebFilter; import org.zowe.apiml.security.common.token.OIDCProvider; import org.zowe.apiml.security.common.util.X509Util; @@ -81,6 +82,9 @@ public class WebSecurityConfig { private static final String APPLICATION = "/application/**"; private static final String APPLICATION_HEALTH = "/application/health"; private static final String APPLICATION_INFO = "/application/info"; + private static final String APPLICATION_VERSION = "/application/version"; + private static final String APPLICATION_GW_VERSION = "/gateway/version"; + private static final String APPLICATION_GW_VERSION_ROUTE = "/gateway/api/v1/version"; private final CompoundAuthProvider compoundAuthProvider; private final X509AuthenticationProvider x509AuthenticationProvider; @@ -122,7 +126,6 @@ public class WebSecurityConfig { private boolean isOidcEnabled; private static final List UNAUTHENTICATED_PATTERNS = List.of( - "/application/", "/eureka/css/**", "/eureka/js/**", "/eureka/fonts/**", @@ -271,6 +274,10 @@ private ServerHttpSecurity addOidcFilterIfEnabled(ServerHttpSecurity http, AuthC @Order(9) SecurityWebFilterChain discoveryAllowedEndpoints(ServerHttpSecurity http) { http + .headers(headers -> headers + .hsts(ServerHttpSecurity.HeaderSpec.HstsSpec::disable) + .writer(new CustomHstsServerHttpHeadersWriter()) + ) .securityMatcher(new AndServerWebExchangeMatcher( discoveryPortMatcher, isInUnauthenticatedPaths @@ -307,11 +314,15 @@ SecurityWebFilterChain healthEndpointFilterChain(ServerHttpSecurity http, AuthConfigurationProperties authConfigurationProperties, AuthExceptionHandlerReactive authExceptionHandlerReactive) { http - .securityMatcher(pathMatchers(APPLICATION_HEALTH)) + .headers(headers -> headers + .hsts(ServerHttpSecurity.HeaderSpec.HstsSpec::disable) + .writer(new CustomHstsServerHttpHeadersWriter()) + ) + .securityMatcher(pathMatchers(APPLICATION_HEALTH, APPLICATION_GW_VERSION, APPLICATION_GW_VERSION_ROUTE, APPLICATION_INFO, APPLICATION_VERSION)) .csrf(ServerHttpSecurity.CsrfSpec::disable) .authorizeExchange(exchange -> { if (!isHealthEndpointProtected) { - exchange.pathMatchers(APPLICATION_HEALTH).permitAll(); + exchange.anyExchange().permitAll(); } else { exchange.anyExchange().authenticated(); } @@ -356,7 +367,7 @@ SecurityWebFilterChain applicationEndpointsProtected(ServerHttpSecurity http, return http .securityMatcher(new AndServerWebExchangeMatcher( pathMatchers(APPLICATION), - new NegatedServerWebExchangeMatcher(pathMatchers(APPLICATION_HEALTH, APPLICATION_INFO, "/application/version")) + new NegatedServerWebExchangeMatcher(pathMatchers(APPLICATION_HEALTH, APPLICATION_INFO, APPLICATION_VERSION)) )) .csrf(ServerHttpSecurity.CsrfSpec::disable) .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) @@ -373,7 +384,6 @@ SecurityWebFilterChain applicationEndpointsProtected(ServerHttpSecurity http, .addFilterAfter(new TokenAuthFilter(localTokenProvider, authConfigurationProperties, authExceptionHandlerReactive), SecurityWebFiltersOrder.AUTHENTICATION) .addFilterAfter(new BasicLoginFilter(compoundAuthProvider, failedAuthenticationWebHandler), SecurityWebFiltersOrder.AUTHENTICATION) .build(); - } /** diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java index 3a676d4599..d0c7a4ff72 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java @@ -43,7 +43,7 @@ class AvailabilityTest extends AcceptanceTestWithBasePath { @ParameterizedTest(name = "{0} is available at port {1} with status {2}") @CsvSource({ "Gateway, 0, 200", - "Discovery, 10011, 401" + "Discovery, 10011, 200" }) void serviceIsAvailable(String serviceName, int servicePort, int expectedStatus) { int actualPort = servicePort == 0 ? port : servicePort; diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/corsTests/GatewayCorsTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/corsTests/GatewayCorsTest.java index 8ffa2e581b..3783192dfc 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/corsTests/GatewayCorsTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/corsTests/GatewayCorsTest.java @@ -47,6 +47,7 @@ class GatewayCorsTest { @Nested @AcceptanceTest @TestPropertySource(properties = { + "apiml.health.protected=false", "apiml.service.corsDefaultAllowedOrigins=https://foo.bar.org", "apiml.service.corsEnabled=true" }) From a08ac2d9be138c95a827ed6ed074afbf2e4c5386 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 26 Aug 2026 11:56:36 +0200 Subject: [PATCH 6/9] remove TODO Signed-off-by: Pablo Carle --- .../test/java/org/zowe/apiml/functional/gateway/VersionTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java index d73ef3d222..0761bd57dc 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java @@ -68,7 +68,6 @@ void returnValidVersion(String endpoint) { @Nested class GivenAuthentication { - // TODO login private static final Credentials CREDENTIALS = ConfigReader.environmentConfiguration().getCredentials(); @Nested From f72bc248737707c853d85f6b66c9a15478ceef11 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 26 Aug 2026 14:12:55 +0200 Subject: [PATCH 7/9] fix integration tests Signed-off-by: Pablo Carle --- .../apiml/functional/gateway/VersionTest.java | 5 +-- .../integration/proxy/GatewayProxyTest.java | 38 +++++++++++++------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java index 0761bd57dc..41b8aaebfd 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/VersionTest.java @@ -55,10 +55,7 @@ void returnValidVersion(String endpoint) { .when() .get(HttpRequestUtils.getUriFromGateway(endpoint)) .then() - .statusCode(SC_UNAUTHORIZED) - .body("apiml.version", is(not(nullValue()))) - .body("apiml.buildNumber", is(not(nullValue()))) - .body("apiml.commitHash", is(not(nullValue()))); + .statusCode(SC_UNAUTHORIZED); } } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java index c44dad7735..10f1765db2 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java @@ -35,11 +35,14 @@ @Tag("GatewayProxyTest") class GatewayProxyTest { + private static final int SECOND = 1000; private static final int DEFAULT_TIMEOUT = 7 * SECOND; private static final String HEADER_X_FORWARD_TO = "X-Forward-To"; + private static final Credentials CREDENTIALS = ConfigReader.environmentConfiguration().getCredentials(); + static ServiceConfiguration conf; @BeforeAll @@ -63,8 +66,20 @@ void givenRequestHeader_thenRouteToProvidedHost() throws URISyntaxException { void givenBasePath_thenRouteToProvidedHost() throws URISyntaxException { String scgUrl1 = String.format("%s://%s:%s/%s", conf.getScheme(), conf.getHost(), conf.getPort(), "apiml1/gateway/version"); String scgUrl2 = String.format("%s://%s:%s/%s", conf.getScheme(), conf.getHost(), conf.getPort(), "apiml2/gateway/version"); - given().get(new URI(scgUrl1)).then().statusCode(200).onFailMessage("Accessing " + scgUrl1); - given().get(new URI(scgUrl2)).then().statusCode(200).onFailMessage("Accessing " + scgUrl2); + given() + .auth().preemptive().basic(CREDENTIALS.getUser(), CREDENTIALS.getPassword()) + .when() + .get(new URI(scgUrl1)) + .then() + .statusCode(200) + .onFailMessage("Accessing " + scgUrl1); + given() + .auth().preemptive().basic(CREDENTIALS.getUser(), CREDENTIALS.getPassword()) + .when() + .get(new URI(scgUrl2)) + .then() + .statusCode(200) + .onFailMessage("Accessing " + scgUrl2); } @Test @@ -73,9 +88,9 @@ void givenRequestTimeoutIsReached_thenDropConnection() { assertTimeout(Duration.ofMillis(DEFAULT_TIMEOUT * 3), () -> { given() .header(HEADER_X_FORWARD_TO, "discoverableclient") - .when() + .when() .get(scgUrl) - .then() + .then() .statusCode(HttpStatus.SC_GATEWAY_TIMEOUT); }); } @@ -89,9 +104,9 @@ void givenRequestHeader_thenCertPassedToDomainGateway() { given() .config(SslContext.clientCertValid) .header(HEADER_X_FORWARD_TO, "apiml1") - .when() + .when() .get(scgUrl) - .then() + .then() .statusCode(HttpStatus.SC_OK) .body("dn", startsWith("CN=APIMTST")) .body("cn", is("APIMTST")) @@ -103,21 +118,21 @@ void givenBasePath_thenCertPassedToDomainGateway() { String scgUrl = String.format("%s://%s:%s/%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), "apiml1", X509_ENDPOINT); given() .config(SslContext.clientCertValid) - .when() + .when() .get(scgUrl) - .then() + .then() .statusCode(HttpStatus.SC_OK) .body("dn", startsWith("CN=APIMTST")) .body("cn", is("APIMTST")) .onFailMessage("Accessing " + scgUrl); } + } @Nested class GivenGatewayCertificatesRequest { private final String trustedCerts; - { TlsConfiguration tlsConf = ConfigReader.environmentConfiguration().getTlsConfiguration(); HttpsConfig httpsConf = HttpsConfig.builder() @@ -149,9 +164,9 @@ void thenCertificatesChainProvided() throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), CLOUD_GATEWAY_CERTIFICATES); String response = given() - .when() + .when() .get(new URI(scgUrl)) - .then() + .then() .statusCode(HttpStatus.SC_OK) .extract().body().asString(); @@ -160,4 +175,5 @@ void thenCertificatesChainProvided() throws URISyntaxException { } } + } From c3f5d35983835f1ae1683c8debeeb834e3e48c62 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 26 Aug 2026 14:32:18 +0200 Subject: [PATCH 8/9] fix remaining integration test Signed-off-by: Pablo Carle --- .../integration/proxy/GatewayProxyTest.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java index 10f1765db2..32f2a6e9fc 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/GatewayProxyTest.java @@ -55,11 +55,22 @@ static void init() throws Exception { @Test void givenRequestHeader_thenRouteToProvidedHost() throws URISyntaxException { - String scgUrl = String.format("%s://%s:%s/%s", conf.getScheme(), conf.getHost(), conf.getPort(), "gateway/version"); - given().header(HEADER_X_FORWARD_TO, "apiml1") - .get(new URI(scgUrl)).then().statusCode(200); - given().header(HEADER_X_FORWARD_TO, "apiml2") - .get(new URI(scgUrl)).then().statusCode(200); + var scgUrl = String.format("%s://%s:%s/%s", conf.getScheme(), conf.getHost(), conf.getPort(), "gateway/version"); + given() + .auth().preemptive().basic(CREDENTIALS.getUser(), CREDENTIALS.getPassword()) + .header(HEADER_X_FORWARD_TO, "apiml1") + .when() + .get(new URI(scgUrl)) + .then() + .statusCode(200); + + given() + .auth().preemptive().basic(CREDENTIALS.getUser(), CREDENTIALS.getPassword()) + .header(HEADER_X_FORWARD_TO, "apiml2") + .when() + .get(new URI(scgUrl)) + .then() + .statusCode(200); } @Test From 1722a482cdec4f366026766db373b57d9934e9e8 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 26 Aug 2026 15:13:43 +0200 Subject: [PATCH 9/9] sonar issues Signed-off-by: Pablo Carle --- .../main/java/org/zowe/apiml/gateway/config/WebSecurity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java index 5b77cdea6f..4bca07d9f2 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/WebSecurity.java @@ -471,7 +471,7 @@ SecurityWebFilterChain securityWebFilterChain( .authorizeExchange(authorizeExchangeSpec -> { if (!isHealthEndpointProtected) { authorizeExchangeSpec - .pathMatchers("/application/info", "/application/version", "/application/health", "/gateway/version", "/gateway/api/v1/version") + .pathMatchers("/application/info", "/application/version", "/application/health", APPLICATION_VERSION_GW, APPLICATION_VERSION_GW_ROUTE) .permitAll(); } }