Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apiml-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
# - ZWE_configs_apiml_security_x509_certificatesUrl
# - ZWE_configs_apiml_security_x509_enabled
# - ZWE_configs_apiml_security_x509_registry_allowedUsers
# - ZWE_configs_apiml_service_allowEncodedSlashes
# - ZWE_configs_apiml_service_corsEnabled
# - ZWE_configs_apiml_service_corsAllowedMethods
# - ZWE_configs_apiml_service_corsDefaultAllowedOrigins
Expand Down Expand Up @@ -317,7 +316,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \
-Dapiml.security.x509.externalMapperUser=${ZWE_components_gateway_apiml_security_x509_externalMapperUser:-${ZWE_configs_apiml_security_x509_externalMapperUser:-${ZWE_zowe_setup_security_users_zowe:-ZWESVUSR}}} \
-Dapiml.security.x509.registry.allowedUsers=${ZWE_components_gateway_apiml_security_x509_registry_allowedUsers:-${ZWE_configs_apiml_security_x509_registry_allowedUsers:-}} \
-Dapiml.security.zosmf.applid=${ZWE_zosmf_applId:-IZUDFLT} \
-Dapiml.service.allowEncodedSlashes=${ZWE_components_gateway_apiml_service_allowEncodedSlashes:-${ZWE_configs_apiml_service_allowEncodedSlashes:-true}} \
-Dapiml.service.apimlId=${ZWE_components_gateway_apimlId:-${ZWE_configs_apimlId:-}} \
-Dapiml.service.corsAllowedMethods=${ZWE_components_gateway_apiml_service_corsAllowedMethods:-${ZWE_configs_apiml_service_corsAllowedMethods:-GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS}} \
-Dapiml.service.corsEnabled=${ZWE_components_gateway_apiml_service_corsEnabled:-${ZWE_configs_apiml_service_corsEnabled:-false}} \
Expand Down
10 changes: 5 additions & 5 deletions apiml-package/src/main/resources/schemas/apiml-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@
"description": "Enables direct native calls to z/OS to query distributed identity mappings and client certificate mappings. Use only if APIML is running on z/OS.",
"default": true
},
"enableStrictUrlValidation": {
"type": "boolean",
"description": "When enabled, the Gateway strictly validates request URLs and rejects encoded characters such as encoded slashes, backslashes, and semicolons. When disabled, validation is relaxed for routed traffic, while Gateway-internal endpoints remain strictly validated.",
"default": true
},
"auth": {
"type": "object",
"description": "Detail configuration of authentication schemes.",
Expand Down Expand Up @@ -550,11 +555,6 @@
"type": "object",
"description": "General configuration of the Gateway.",
"properties": {
"allowEncodedSlashes": {
"type": "boolean",
"description": "When this parameter is set to true, the Gateway allows encoded characters to be part of URL requests redirected through the Gateway.",
"default": true
},
"corsEnabled": {
"type": "boolean",
"description": "Allow CORS on gateway.",
Expand Down
1 change: 0 additions & 1 deletion apiml/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ apiml:
apimlId: apiml1
corsEnabled: true
corsAllowedEndpoints: /gateway/**,/apicatalog/**,/cachingservice/**,/v3/api-docs/**
allowEncodedSlashes: true
discoveryServiceUrls: https://localhost:10011/eureka/
forwardClientCertEnabled: false
hostname: localhost
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* 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.acceptance;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.test.context.NestedTestConfiguration;
import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.zowe.apiml.gateway.MockService;

import java.util.List;
import java.util.stream.Stream;

import static io.restassured.RestAssured.given;
import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_OK;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.params.provider.Arguments.arguments;

/**
* Modulith acceptance tests validating the {@code apiml.security.enableStrictUrlValidation} option. The two nested
* classes exercise the two states of the option, differentiated by {@link TestPropertySource}:
* <ul>
* <li>{@link WhenStrictValidationEnabled} - the default, strict URL validation rejects requests containing
* any of the encoded characters controlled by the option, whether the target is a routed service or an
* internal endpoint.</li>
* <li>{@link WhenStrictValidationDisabled} - relaxed URL validation forwards such requests to the target
* service for routed traffic, but internal endpoints remain strictly validated.</li>
* </ul>
* The parameterized cases cover every character relaxed by the option: encoded slash, encoded double slash,
* backslash, encoded percent, encoded period, and semicolon.
*/
@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
class StrictUrlValidationTest {

private static final String SERVICE_ID = "serviceid1";

/**
* Path suffixes, each containing one of the special characters controlled by
* {@code apiml.security.enableStrictUrlValidation}.
*/
private static final List<Named<String>> SPECIAL_CHARACTERS = List.of(
Named.of("encoded slash", "encoded%2Fslash"),
Named.of("encoded double slash", "encoded%2F%2Fslash"),
Named.of("backslash", "encoded%5Cbackslash"),
Named.of("encoded percent", "encoded%25percent"),
Named.of("encoded period", "encoded%2Eperiod"),
Named.of("semicolon", "path;matrix")
);

/**
* Internal base paths, which are strictly validated even when the option is disabled. In the modulith the
* Gateway also fronts the API Catalog and Caching service, so this set is broader than in a standalone Gateway.
* Matches {@code BASE_PATHS_MODULITH} in {@code WebSecurity}.
*/
private static final List<String> INTERNAL_ENDPOINTS = List.of(
"/gateway", "/application", "/images", "/v3/api-docs", "/apicatalog", "/cachingservice"
);

static Stream<Arguments> specialCharacters() {
return SPECIAL_CHARACTERS.stream().map(Arguments::arguments);
}

static Stream<Arguments> internalEndpointsWithSpecialCharacters() {
return INTERNAL_ENDPOINTS.stream().flatMap(endpoint ->
SPECIAL_CHARACTERS.stream().map(character -> arguments(endpoint, character)));
}

@Nested
@AcceptanceTest
@TestPropertySource(properties = {
"apiml.security.enableStrictUrlValidation=true"
})
class WhenStrictValidationEnabled extends AcceptanceTestWithMockServices {

@BeforeAll
void setUp() {
mockService(SERVICE_ID).scope(MockService.Scope.CLASS)
.addEndpoint("/" + SERVICE_ID + "/test")
.and().start();
}

@ParameterizedTest
@MethodSource("org.zowe.apiml.acceptance.StrictUrlValidationTest#specialCharacters")
void whenRoutedRequestContainsSpecialCharacter_thenRejectedWithBadRequest(String pathSuffix) {
given()
.urlEncodingEnabled(false)
.when()
.get(basePath + "/" + SERVICE_ID + "/api/v1/test/" + pathSuffix)
.then()
.statusCode(is(SC_BAD_REQUEST));
}

@ParameterizedTest
@MethodSource("org.zowe.apiml.acceptance.StrictUrlValidationTest#internalEndpointsWithSpecialCharacters")
void whenInternalEndpointRequestContainsSpecialCharacter_thenRejectedWithBadRequest(String internalPath, String pathSuffix) {
given()
.urlEncodingEnabled(false)
.when()
.get(basePath + internalPath + "/" + pathSuffix)
.then()
.statusCode(is(SC_BAD_REQUEST));
}

}

@Nested
@AcceptanceTest
@TestPropertySource(properties = {
"apiml.security.enableStrictUrlValidation=false"
})
class WhenStrictValidationDisabled extends AcceptanceTestWithMockServices {

@BeforeAll
void setUp() {
mockService(SERVICE_ID).scope(MockService.Scope.CLASS)
.addEndpoint("/" + SERVICE_ID + "/test")
.and().start();
}

@ParameterizedTest
@MethodSource("org.zowe.apiml.acceptance.StrictUrlValidationTest#specialCharacters")
void whenRoutedRequestContainsSpecialCharacter_thenForwardedToService(String pathSuffix) {
given()
.urlEncodingEnabled(false)
.when()
.get(basePath + "/" + SERVICE_ID + "/api/v1/test/" + pathSuffix)
.then()
.statusCode(is(SC_OK));
}

@ParameterizedTest
@MethodSource("org.zowe.apiml.acceptance.StrictUrlValidationTest#internalEndpointsWithSpecialCharacters")
void whenInternalEndpointRequestContainsSpecialCharacter_thenStillRejectedWithBadRequest(String internalPath, String pathSuffix) {
given()
.urlEncodingEnabled(false)
.when()
.get(basePath + internalPath + "/" + pathSuffix)
.then()
.statusCode(is(SC_BAD_REQUEST));
}

}

}
1 change: 0 additions & 1 deletion apiml/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ apiml:
apimlId: apiml1
corsEnabled: true
corsAllowedEndpoints: /gateway/**,/apicatalog/**,/cachingservice/**,/v3/api-docs/**
allowEncodedSlashes: true
discoveryServiceUrls: https://localhost:10011/eureka/
forwardClientCertEnabled: false
hostname: localhost
Expand Down
1 change: 0 additions & 1 deletion config/zowe-api-dev/alternative_start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ _BPX_JOBNAME=${ZOWE_PREFIX}${GATEWAY_CODE} java \
-Dapiml.service.port=${GATEWAY_PORT} \
-Dapiml.service.discoveryServiceUrls=${ZWE_DISCOVERY_SERVICES_LIST} \
-Dapiml.service.preferIpAddress=${APIML_PREFER_IP_ADDRESS} \
-Dapiml.service.allowEncodedSlashes=${APIML_ALLOW_ENCODED_SLASHES} \
-Dapiml.service.corsEnabled=${APIML_CORS_ENABLED} \
-Dapiml.catalog.serviceId=${APIML_GATEWAY_CATALOG_ID} \
-Dapiml.cache.storage.location=${WORKSPACE_DIR}/api-mediation/ \
Expand Down
1 change: 0 additions & 1 deletion config/zowe-api-dev/run-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export PATH=$PATH:$JAVA_HOME/bin
# Variables required on shell:
# This list should be exhaustive, with variables that are not needed commented
# sorted alphabetically for easier maintenance where possible
export APIML_ALLOW_ENCODED_SLASHES=true
export APIML_CORS_ENABLED=true
export APIML_DEBUG_MODE_ENABLED=true
export APIML_DIAG_MODE_ENABLED=false
Expand Down
2 changes: 0 additions & 2 deletions gateway-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
# - ZWE_configs_apiml_security_x509_acceptForwardedCert
# - ZWE_configs_apiml_security_x509_certificatesUrl
# - ZWE_configs_apiml_security_x509_registry_allowedUsers
# - ZWE_configs_apiml_service_allowEncodedSlashes
# - ZWE_configs_apiml_service_corsEnabled
# - ZWE_configs_apiml_service_corsAllowedMethods
# - ZWE_configs_apiml_gateway_registry_enabled
Expand Down Expand Up @@ -194,7 +193,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${GATEWAY_CODE} ${JAVA_BIN_DIR}java \
-Dapiml.security.x509.certificatesUrls=${ZWE_configs_apiml_security_x509_certificatesUrls:-${ZWE_configs_apiml_security_x509_certificatesUrl:-}} \
-Dapiml.security.x509.enabled=${ZWE_configs_apiml_security_x509_enabled:-false} \
-Dapiml.security.x509.registry.allowedUsers=${ZWE_configs_apiml_security_x509_registry_allowedUsers:-} \
-Dapiml.service.allowEncodedSlashes=${ZWE_configs_apiml_service_allowEncodedSlashes:-true} \
-Dapiml.service.apimlId=${ZWE_configs_apimlId:-} \
-Dapiml.service.corsAllowedMethods=${ZWE_configs_apiml_service_corsAllowedMethods:-GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS} \
-Dapiml.service.corsDefaultAllowedHeaders=${ZWE_configs_apiml_service_corsDefaultAllowedHeaders:-} \
Expand Down
10 changes: 5 additions & 5 deletions gateway-package/src/main/resources/schemas/gateway-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@
"description": "Enables direct native calls to z/OS to query distributed identity mappings and client certificate mappings. Use only if APIML is running on z/OS.",
"default": true
},
"enableStrictUrlValidation": {
"type": "boolean",
"description": "When enabled, the Gateway strictly validates request URLs and rejects encoded characters such as encoded slashes, backslashes, and semicolons. When disabled, validation is relaxed for routed traffic, while Gateway-internal endpoints remain strictly validated.",
"default": true
},
"auth": {
"type": "object",
"description": "Detail configuration of authentication schemes.",
Expand Down Expand Up @@ -509,11 +514,6 @@
}
}
},
"allowEncodedSlashes": {
"type": "boolean",
"description": "When this parameter is set to true, the Gateway allows encoded characters to be part of URL requests redirected through the Gateway.",
"default": true
},
"corsEnabled": {
"type": "boolean",
"description": "Allow CORS on gateway.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ public class RoutingConfig {

@Value("${apiml.security.x509.acceptForwardedCert:false}")
private boolean acceptForwardedCert;
@Value("${apiml.service.allowEncodedSlashes:true}")
private boolean allowEncodedSlashes;

@Bean
public List<FilterDefinition> commonNoRetryFilters() {
Expand All @@ -41,12 +39,6 @@ public List<FilterDefinition> commonNoRetryFilters() {
filters.add(acceptForwardedClientCertFilter);
}

if (!allowEncodedSlashes) {
var encodedSlashesFilter = new FilterDefinition();
encodedSlashesFilter.setName("ForbidEncodedSlashesFilterFactory");
filters.add(encodedSlashesFilter);
}

var secureHeaders = new FilterDefinition();
secureHeaders.setName("SecureHeaders");
filters.add(secureHeaders);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.zowe.apiml.exception.MetadataValidationException;
import org.zowe.apiml.gateway.config.InvalidForwardException;
import org.zowe.apiml.gateway.filters.ForbidCharacterException;
import org.zowe.apiml.gateway.filters.ForbidSlashException;
import org.zowe.apiml.gateway.filters.ZaasInternalErrorException;
import org.zowe.apiml.message.core.Message;
import org.zowe.apiml.message.core.MessageService;
Expand Down Expand Up @@ -109,12 +108,6 @@ public Mono<Void> handleForbidCharacterException(ServerWebExchange exchange, For
return setBodyResponse(exchange, SC_BAD_REQUEST, "org.zowe.apiml.gateway.requestContainEncodedCharacter");
}

@ExceptionHandler(ForbidSlashException.class)
public Mono<Void> handleForbidSlashException(ServerWebExchange exchange, ForbidSlashException ex) {
log.debug("Forbidden slash in the URI {}: {}", exchange.getRequest().getURI(), ex.getMessage());
return setBodyResponse(exchange, SC_BAD_REQUEST, "org.zowe.apiml.gateway.requestContainEncodedSlash");
}

@ExceptionHandler({AuthenticationException.class, WebClientResponseException.Unauthorized.class})
public Mono<Void> handleAuthenticationException(ServerWebExchange exchange, Exception ex) {
log.debug("Unauthorized access on {}: {}", exchange.getRequest().getURI(), ex.getMessage());
Expand Down

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion gateway-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ apiml:
apimlId: apiml1
corsEnabled: true
corsAllowedEndpoints: /gateway/**
allowEncodedSlashes: true
discoveryServiceUrls: https://localhost:10011/eureka/
forwardClientCertEnabled: true
hostname: localhost
Expand Down
7 changes: 0 additions & 7 deletions gateway-service/src/main/resources/gateway-log-messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ messages:
reason: "The request that was issued to the Gateway contains an encoded character in the URL path. The service that the request was addressing does not allow this pattern."
action: "Contact the system administrator and request enablement of encoded characters in the service."

- key: org.zowe.apiml.gateway.requestContainEncodedSlash
number: ZWEAG702
type: ERROR
text: "Gateway does not allow encoded slashes in request: '%s'."
reason: "The request that was issued to the Gateway contains an encoded slash in the URL path. Gateway configuration does not allow this encoding in the URL."
action: "Contact the system administrator and request enablement of encoded slashes in the Gateway."

- key: org.zowe.apiml.gateway.verifier.nonConformant
number: ZWEAG719
type: INFO
Expand Down
Loading
Loading