Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cors enabled in default mode with AT-TLS profile #3221

Merged
merged 16 commits into from
Dec 1, 2023
14 changes: 13 additions & 1 deletion api-catalog-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,20 @@ LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/default
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/j9vm
LIBPATH="$LIBPATH":"${LIBRARY_PATH}"

ATTLS_ENABLED="false"
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
ATTLS_ENABLED="true"
fi

if [ "${ZWE_configs_server_ssl_enabled:-true}" = "true" -o "$ATTLS_ENABLED" = "true" ]; then
httpProtocol="https"
else
httpProtocol="http"
fi

# Verify discovery service URL in case AT-TLS is enabled, assumes outgoing rules are in place
ZWE_DISCOVERY_SERVICES_LIST=${ZWE_DISCOVERY_SERVICES_LIST:-"https://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_discovery_port:-7553}/eureka/"}
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
if [ "$ATTLS_ENABLED" = "true" ]; then
ZWE_DISCOVERY_SERVICES_LIST=$(echo "${ZWE_DISCOVERY_SERVICES_LIST=}" | sed -e 's|https://|http://|g')
fi

Expand Down Expand Up @@ -157,6 +168,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${CATALOG_CODE} java \
-Dapiml.service.discoveryServiceUrls=${ZWE_DISCOVERY_SERVICES_LIST} \
-Dapiml.service.gatewayHostname=${ZWE_GATEWAY_HOST:-${ZWE_haInstance_hostname:-localhost}} \
-Dapiml.logs.location=${ZWE_zowe_logDirectory} \
-Dapiml.service.externalUrl="${httpProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" \
-Dapiml.discovery.staticApiDefinitionsDirectories=${ZWE_STATIC_DEFINITIONS_DIR} \
-Dapiml.security.ssl.verifySslCertificatesOfServices=${verifySslCertificatesOfServices:-false} \
-Dapiml.security.ssl.nonStrictVerifySslCertificatesOfServices=${nonStrictVerifySslCertificatesOfServices:-false} \
Expand Down
1 change: 1 addition & 0 deletions api-catalog-services/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ eureka:
metadata-map:
apiml:
corsEnabled: true
corsAllowedOrigins: https://${apiml.service.hostname}:${apiml.service.port},${apiml.service.externalUrl}
pablocarle marked this conversation as resolved.
Show resolved Hide resolved
apiInfo:
- apiId: zowe.apiml.apicatalog
version: 1.0.0
Expand Down
7 changes: 6 additions & 1 deletion caching-service-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,14 @@ LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/default
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/j9vm
LIBPATH="$LIBPATH":"${LIBRARY_PATH}"

ATTLS_ENABLED="false"
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
ATTLS_ENABLED="true"
fi

# Verify discovery service URL in case AT-TLS is enabled, assumes outgoing rules are in place
ZWE_DISCOVERY_SERVICES_LIST=${ZWE_DISCOVERY_SERVICES_LIST:-"https://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_discovery_port:-7553}/eureka/"}
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
if [ "$ATTLS_ENABLED" = "true" ]; then
ZWE_DISCOVERY_SERVICES_LIST=$(echo "${ZWE_DISCOVERY_SERVICES_LIST=}" | sed -e 's|https://|http://|g')
fi

Expand Down
7 changes: 6 additions & 1 deletion cloud-gateway-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,14 @@ LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/default
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/j9vm
LIBPATH="$LIBPATH":"${LIBRARY_PATH}"

ATTLS_ENABLED="false"
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
ATTLS_ENABLED="true"
fi

# Verify discovery service URL in case AT-TLS is enabled, assumes outgoing rules are in place
ZWE_DISCOVERY_SERVICES_LIST=${ZWE_DISCOVERY_SERVICES_LIST:-"https://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_discovery_port:-7553}/eureka/"}
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
if [ "$ATTLS_ENABLED" = "true" ]; then
ZWE_DISCOVERY_SERVICES_LIST=$(echo "${ZWE_DISCOVERY_SERVICES_LIST=}" | sed -e 's|https://|http://|g')
fi

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import javax.annotation.PostConstruct;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;

import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -322,7 +323,7 @@ public CorsConfigurationSource corsConfigurationSource(RoutePredicateHandlerMapp

@Bean
public CorsUtils corsUtils() {
return new CorsUtils(corsEnabled);
return new CorsUtils(corsEnabled, null);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
public class CorsUtils {
private static final List<String> allowedCorsHttpMethods;
private final boolean corsEnabled;
private final List<String> allowedOrigins;
private static final Pattern gatewayRoutesPattern = Pattern.compile("apiml\\.routes.*.gateway\\S*");

private static final List<String> CORS_ENABLED_ENDPOINTS = Arrays.asList("/*/*/gateway/**", "/gateway/*/*/**", "/gateway/version");
Expand Down Expand Up @@ -68,13 +69,17 @@ private CorsConfiguration setAllowedOriginsForService(Map<String, String> metada
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
config.setAllowedMethods(allowedCorsHttpMethods);
} else {
config.setAllowedOrigins(allowedOrigins);
}
return config;
}

public void registerDefaultCorsConfiguration(BiConsumer<String, CorsConfiguration> pathMapper) {
final CorsConfiguration config = new CorsConfiguration();
List<String> pathsToEnable;

config.setAllowedOrigins(allowedOrigins);
if (corsEnabled) {
config.setAllowCredentials(true);
config.addAllowedOriginPattern(CorsConfiguration.ALL); //NOSONAR this is a replication of existing code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.web.cors.CorsConfiguration;

import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.function.BiConsumer;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

class CorsUtilsTest {

Expand All @@ -31,7 +35,7 @@ void setup() {

@Nested
class GivenCorsEnabled {
CorsUtils corsUtils = new CorsUtils(true);
CorsUtils corsUtils = new CorsUtils(true, Collections.emptyList());

@Test
void registerDefaultConfig() {
Expand Down Expand Up @@ -86,7 +90,7 @@ void registerConfigForServiceWithCustomOrigins() {

@Nested
class GivenCorsDisabled {
CorsUtils corsUtils = new CorsUtils(false);
CorsUtils corsUtils = new CorsUtils(false, Collections.emptyList());

@Test
void registerEmptyDefaultConfig() {
Expand All @@ -106,4 +110,24 @@ void registerEmptyConfigForService() {
);
}
}

@Nested
class Attls {

@Test
void setAllowedOrigins() {
List<String> allowedOrigins = Arrays.asList("a");
CorsUtils corsUtils = new CorsUtils(true, allowedOrigins);
BiConsumer<String, CorsConfiguration> pathMapper = mock(BiConsumer.class);
corsUtils.registerDefaultCorsConfiguration(pathMapper);

ArgumentCaptor<CorsConfiguration> corsConfigurationCaptor = ArgumentCaptor.forClass(CorsConfiguration.class);

verify(pathMapper, times(3)).accept(any(), corsConfigurationCaptor.capture());
assertEquals(1, corsConfigurationCaptor.getValue().getAllowedOrigins().size());
assertEquals("a", corsConfigurationCaptor.getValue().getAllowedOrigins().get(0));
}

}

}
7 changes: 6 additions & 1 deletion discovery-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,14 @@ if [ "$(uname)" = "OS/390" ]; then
QUICK_START="-Xquickstart"
fi

ATTLS_ENABLED="false"
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
ATTLS_ENABLED="true"
fi

# Verify discovery service URL in case AT-TLS is enabled, assumes outgoing rules are in place
ZWE_DISCOVERY_SERVICES_LIST=${ZWE_DISCOVERY_SERVICES_LIST:-"https://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_discovery_port:-7553}/eureka/"}
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
if [ "$ATTLS_ENABLED" = "true" ]; then
ZWE_DISCOVERY_SERVICES_LIST=$(echo "${ZWE_DISCOVERY_SERVICES_LIST=}" | sed -e 's|https://|http://|g')
fi

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
title: z/OSMF
description: IBM z/OS Management Facility REST API service
instanceBaseUrls:
- ${ZOSMF_SCHEME:-https}://${ZOSMF_HOST}:${ZOSMF_PORT}/zosmf/
- ${ZWE_zOSMF_scheme:-https}://${ZOSMF_HOST}:${ZOSMF_PORT}/zosmf/
homePageRelativeUrl: # Home page is at the same URL
routedServices:
- gatewayUrl: api/v1
Expand All @@ -29,7 +29,7 @@ services:
description: 'IBM z/OS Management Facility REST API service. Once configured you can access z/OSMF via the API gateway: https://${ZOWE_EXPLORER_HOST}:${GATEWAY_PORT}/ibmzosmf/api/v1/info'
catalogUiTileId: zosmf
instanceBaseUrls:
- ${ZOSMF_SCHEME:-https}://${ZOSMF_HOST}:${ZOSMF_PORT}/
- ${ZWE_zOSMF_scheme:-https}://${ZOSMF_HOST}:${ZOSMF_PORT}/
homePageRelativeUrl: # Home page is at the same URL
routedServices:
- gatewayUrl: api/v1
Expand All @@ -40,7 +40,7 @@ services:
- apiId: ibm.zosmf
gatewayUrl: api/v1
documentationUrl: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.4.0/com.ibm.zos.v2r4.izua700/IZUHPINFO_RESTServices.htm
swaggerUrl: ${ZOSMF_SCHEME:-https}://${ZOSMF_HOST}:${ZOSMF_PORT}/zosmf/api/docs
swaggerUrl: ${ZWE_zOSMF_scheme:-https}://${ZOSMF_HOST}:${ZOSMF_PORT}/zosmf/api/docs
pablocarle marked this conversation as resolved.
Show resolved Hide resolved
customMetadata:
apiml:
enableUrlEncodedCharacters: true
Expand Down
19 changes: 12 additions & 7 deletions gateway-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,6 @@ else
nonStrictVerifySslCertificatesOfServices=false
fi

if [ "${ZWE_configs_server_ssl_enabled:-true}" = "true" ]; then
httpProtocol="https"
else
httpProtocol="http"
fi

if [ -z "${ZWE_configs_apiml_catalog_serviceId}" ]
then
APIML_GATEWAY_CATALOG_ID="apicatalog"
Expand All @@ -161,9 +155,20 @@ else
GATEWAY_LOADER_PATH=${COMMON_LIB}
fi

ATTLS_ENABLED="false"
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
ATTLS_ENABLED="true"
fi

if [ "${ZWE_configs_server_ssl_enabled:-true}" = "true" -o "$ATTLS_ENABLED" = "true" ]; then
httpProtocol="https"
else
httpProtocol="http"
fi

# Verify discovery service URL in case AT-TLS is enabled, assumes outgoing rules are in place
ZWE_DISCOVERY_SERVICES_LIST=${ZWE_DISCOVERY_SERVICES_LIST:-"https://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_discovery_port:-7553}/eureka/"}
if [ -n "$(echo ${ZWE_configs_spring_profiles_active:-} | awk '/^(.*,)?attls(,.*)?$/')" ]; then
if [ "$ATTLS_ENABLED" = "true" ]; then
ZWE_DISCOVERY_SERVICES_LIST=$(echo "${ZWE_DISCOVERY_SERVICES_LIST=}" | sed -e 's|https://|http://|g')
fi

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@
package org.zowe.apiml.gateway.security.config;

import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.zowe.apiml.util.CorsUtils;

import java.util.Arrays;
import java.util.HashSet;
import java.net.URISyntaxException;
import java.util.*;

/**
* Externalized configuration of CORS behavior
Expand Down Expand Up @@ -54,8 +57,37 @@ private void addCorsRelatedIgnoredHeaders() {
));
}

List<String> getDefaultAllowedOrigins( // TODO: this method is a hotfix for AT-TLS, but it could be a breaking change, verify no-ATTLS configuration in v3
Environment environment,
String externalUrl,
String hostname,
int port
) throws URISyntaxException {
boolean isAttls = Arrays.asList(environment.getActiveProfiles()).contains("attls");
if (corsEnabled || !isAttls) {
return null; // NOSONAR
}

Set<String> gatewayOrigins = new HashSet<>();
if (StringUtils.isNotBlank(externalUrl)) {
gatewayOrigins.add(externalUrl);
}
gatewayOrigins.add(new URIBuilder()
.setScheme("https")
.setHost(hostname)
.setPort(port)
.build().toString()
);

return new ArrayList<>(gatewayOrigins);
}
@Bean
CorsUtils corsUtils() {
return new CorsUtils(corsEnabled);
CorsUtils corsUtils(
Environment environment,
@Value("${apiml.service.externalUrl:}") String externalUrl,
@Value("${server.hostname:${apiml.service.hostname}}") String hostname,
@Value("${server.port}") int port
) throws URISyntaxException {
return new CorsUtils(corsEnabled, getDefaultAllowedOrigins(environment, externalUrl, hostname, port));
}
}
1 change: 1 addition & 0 deletions gateway-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ server:
scheme: http
apiml:
service:
corsEnabled: true
scheme: http
pablocarle marked this conversation as resolved.
Show resolved Hide resolved
eureka:
instance:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicStatusLine;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInstance;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -40,11 +41,18 @@
* Verify that the behavior configured for the routing chooses for the same user the same service instance.
*/
@AcceptanceTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DeterministicUserBasedRoutingTest extends AcceptanceTestWithTwoServices {
@Autowired
protected LoadBalancerCache cache;

@Override
public void prepareApplications() {
// to stop updating before each method
}

@BeforeAll
public void prepareApplicationsAll() {
cache.getLocalCache().clear();
applicationRegistry.clearApplications();
MetadataBuilder defaultBuilder = MetadataBuilder.defaultInstance();
Expand All @@ -62,13 +70,7 @@ class GivenAuthenticatedUserAndMoreInstancesOfService {
class WhenCallingToServiceMultipleTimes {

@RepeatedTest(3)
void thenCallTheSameInstance(RepetitionInfo repetitionInfo) throws IOException {

// initialize the cache and registry only once on first repetition
if (repetitionInfo.getCurrentRepetition() == 1) {
prepareApplications();
}

void thenCallTheSameInstance() throws IOException {
Cookie token = securityRequests.validJwtToken();

applicationRegistry.setCurrentApplication(serviceWithCustomConfiguration.getId());
Expand All @@ -77,8 +79,14 @@ void thenCallTheSameInstance(RepetitionInfo repetitionInfo) throws IOException {
URI selectedInSecondCall = routeToService(token, SC_OK);
URI selectedInThirdCall = routeToService(token, SC_OK);

assertThat(selectedInFirstCall.compareTo(selectedInSecondCall), is(0));
assertThat(selectedInFirstCall.compareTo(selectedInThirdCall), is(0));
String message = String.format("URLs of the same calls are not the same: `%s`, `%s`, `%s`",
selectedInFirstCall,
selectedInSecondCall,
selectedInThirdCall
);

assertThat(message, selectedInFirstCall.compareTo(selectedInSecondCall), is(0));
assertThat(message, selectedInFirstCall.compareTo(selectedInThirdCall), is(0));
}
}

Expand Down
Loading
Loading