Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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