diff --git a/apiml-security-common/src/main/java/org/zowe/apiml/security/common/verify/CertificateValidator.java b/apiml-security-common/src/main/java/org/zowe/apiml/security/common/verify/CertificateValidator.java index f5e716b164..9a5c08f46a 100644 --- a/apiml-security-common/src/main/java/org/zowe/apiml/security/common/verify/CertificateValidator.java +++ b/apiml-security-common/src/main/java/org/zowe/apiml/security/common/verify/CertificateValidator.java @@ -12,7 +12,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -22,6 +21,7 @@ import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -43,8 +43,8 @@ public class CertificateValidator { @Value("${apiml.security.x509.acceptForwardedCert:false}") private boolean forwardingEnabled; - @Value("${apiml.security.x509.certificatesUrl:}") - private String proxyCertificatesEndpoint; + @Value("${apiml.security.x509.certificatesUrls:${apiml.security.x509.certificatesUrl:}}") + private String[] proxyCertificatesEndpoints; private final Set publicKeyCertificatesBase64; @@ -62,11 +62,14 @@ public CertificateValidator(TrustedCertificatesProvider trustedCertificatesProvi * @return true if all given certificates are known false otherwise */ public boolean isTrusted(X509Certificate[] certs) { - if (StringUtils.isBlank(proxyCertificatesEndpoint)) { - log.debug("No endpoint configured to retrieve trusted certificates. Provide URL via apiml.security.x509.certificatesUrl"); + if ((proxyCertificatesEndpoints == null) || (proxyCertificatesEndpoints.length == 0)) { + log.debug("No endpoint configured to retrieve trusted certificates. Provide URL via apiml.security.x509.certificatesUrls"); return false; } - List trustedCerts = trustedCertificatesProvider.getTrustedCerts(proxyCertificatesEndpoint); + List trustedCerts = Arrays.stream(proxyCertificatesEndpoints) + .map(trustedCertificatesProvider::getTrustedCerts) + .flatMap(List::stream) + .toList(); for (X509Certificate cert : certs) { if (!trustedCerts.contains(cert)) { apimlLog.log("org.zowe.apiml.security.common.verify.untrustedCert"); diff --git a/apiml-security-common/src/main/resources/security-common-log-messages.yml b/apiml-security-common/src/main/resources/security-common-log-messages.yml index b8291f204e..b2e70ad5ce 100644 --- a/apiml-security-common/src/main/resources/security-common-log-messages.yml +++ b/apiml-security-common/src/main/resources/security-common-log-messages.yml @@ -100,14 +100,14 @@ messages: type: ERROR text: "Failed to get trusted certificates from the central Gateway. Unexpected response from %s endpoint. Status code: %s. Response body: %s" reason: "The response status code is different from expected 200 OK." - action: "Ensure that the parameter apiml.security.x509.certificatesUrl is correctly configured with the complete URL to the central Gateway certificates endpoint. Test the URL manually." + action: "Ensure that the parameter apiml.security.x509.certificatesUrls is correctly configured with the complete URL to the central Gateway certificates endpoint. Test the URL manually." - key: org.zowe.apiml.security.common.verify.invalidURL number: ZWEAT502 type: ERROR text: "Invalid URL specified to get trusted certificates from the central Gateway. Error message: %s" - reason: "The parameter apiml.security.x509.certificatesUrl is not correctly configured with the complete URL to the central Gateway certificates endpoint." - action: "Ensure that the parameter apiml.security.x509.certificatesUrl is correctly configured." + reason: "The parameter apiml.security.x509.certificatesUrls is not correctly configured with the complete URL to the central Gateway certificates endpoint." + action: "Ensure that the parameter apiml.security.x509.certificatesUrls is correctly configured." - key: org.zowe.apiml.security.common.verify.httpError number: ZWEAT503 @@ -121,14 +121,14 @@ messages: type: ERROR text: "Failed to parse the trusted certificates provided by the central Gateway. Error message %s" reason: "The string sent by the central Gateway was not recognized as valid DER-encoded certificates in the Base64 printable form." - action: "Check that the URL configured in apiml.security.x509.certificatesUrl responds with valid DER-encoded certificates in the Base64 printable form." + action: "Check that the URL configured in apiml.security.x509.certificatesUrls responds with valid DER-encoded certificates in the Base64 printable form." - key: org.zowe.apiml.security.common.verify.untrustedCert number: ZWEAT505 type: ERROR text: "Incoming request certificate is not one of the trusted certificates provided by the central Gateway." reason: "The Gateway performs additional check of request certificates when the central Gateway forwards incoming client certificate to the domain Gateway. This check may fail when the certificatesUrl parameter does not point to proper central Gateway certificates endpoint." - action: "Check that the URL configured in apiml.security.x509.certificatesUrl points to the central Gateway and it responds with valid DER-encoded certificates in the Base64 printable form." + action: "Check that the URL configured in apiml.security.x509.certificatesUrls points to the central Gateway and it responds with valid DER-encoded certificates in the Base64 printable form." # Various messages # 600-699 diff --git a/apiml-security-common/src/test/java/org/zowe/apiml/security/common/verify/CertificateValidatorTest.java b/apiml-security-common/src/test/java/org/zowe/apiml/security/common/verify/CertificateValidatorTest.java index dac6c2d8f5..cad41bc1e0 100644 --- a/apiml-security-common/src/test/java/org/zowe/apiml/security/common/verify/CertificateValidatorTest.java +++ b/apiml-security-common/src/test/java/org/zowe/apiml/security/common/verify/CertificateValidatorTest.java @@ -13,10 +13,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.util.ReflectionTestUtils; import org.zowe.apiml.security.common.utils.X509Utils; -import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.*; import java.util.stream.Collectors; @@ -28,7 +33,8 @@ public class CertificateValidatorTest { - private static final String URL_PROVIDE_TRUSTED_CERTS = "trusted_certs_url"; + private static final String URL_PROVIDE_TWO_TRUSTED_CERTS = "trusted_two_certs_url"; + private static final String URL_PROVIDE_THIRD_TRUSTED_CERT = "third_trusted_cert_url"; private static final String URL_WITH_NO_TRUSTED_CERTS = "invalid_url_for_trusted_certs"; private static final X509Certificate cert1 = X509Utils.getCertificate(X509Utils.correctBase64("correct_certificate_1")); private static final X509Certificate cert2 = X509Utils.getCertificate(X509Utils.correctBase64("correct_certificate_2")); @@ -38,11 +44,9 @@ public class CertificateValidatorTest { @BeforeEach void setUp() { - List trustedCerts = new ArrayList<>(); - trustedCerts.add(cert1); - trustedCerts.add(cert2); TrustedCertificatesProvider mockProvider = mock(TrustedCertificatesProvider.class); - when(mockProvider.getTrustedCerts(URL_PROVIDE_TRUSTED_CERTS)).thenReturn(trustedCerts); + when(mockProvider.getTrustedCerts(URL_PROVIDE_TWO_TRUSTED_CERTS)).thenReturn(Arrays.asList(cert1, cert2)); + when(mockProvider.getTrustedCerts(URL_PROVIDE_THIRD_TRUSTED_CERT)).thenReturn(Collections.singletonList(cert3)); when(mockProvider.getTrustedCerts(URL_WITH_NO_TRUSTED_CERTS)).thenReturn(Collections.emptyList()); certificateValidator = new CertificateValidator(mockProvider, Collections.emptySet()); } @@ -52,7 +56,7 @@ class WhenTrustedCertsProvided { @BeforeEach void setUp() { - ReflectionTestUtils.setField(certificateValidator, "proxyCertificatesEndpoint", URL_PROVIDE_TRUSTED_CERTS, String.class); + ReflectionTestUtils.setField(certificateValidator, "proxyCertificatesEndpoints", new String[] {URL_PROVIDE_TWO_TRUSTED_CERTS}); } @Test void whenAllCertificatesFoundThenTheyAreTrusted() { @@ -74,7 +78,7 @@ class WhenNoTrustedCertsProvided { @BeforeEach void setUp() { - ReflectionTestUtils.setField(certificateValidator, "proxyCertificatesEndpoint", URL_WITH_NO_TRUSTED_CERTS, String.class); + ReflectionTestUtils.setField(certificateValidator, "proxyCertificatesEndpoints", new String[] {URL_WITH_NO_TRUSTED_CERTS}); } @Test void thenAnyCertificateIsNotTrusted() { @@ -101,7 +105,81 @@ void whenUpdatePublicKeys_thenSetUpdated() { assertTrue(publicKeys.contains(Base64.getEncoder().encodeToString(cert1.getPublicKey().getEncoded()))); assertTrue(publicKeys.contains(Base64.getEncoder().encodeToString(cert2.getPublicKey().getEncoded()))); assertTrue(publicKeys.contains(Base64.getEncoder().encodeToString(cert3.getPublicKey().getEncoded()))); + } + + } + + @Nested + class WhenMultipleSources { + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(certificateValidator, "proxyCertificatesEndpoints", new String[] {URL_PROVIDE_TWO_TRUSTED_CERTS, URL_PROVIDE_THIRD_TRUSTED_CERT}); + } + + @Test + void whenAllCertificatesFoundThenTheyAreTrusted() { + assertTrue(certificateValidator.isTrusted(new X509Certificate[]{cert1})); + assertTrue(certificateValidator.isTrusted(new X509Certificate[]{cert2})); + assertTrue(certificateValidator.isTrusted(new X509Certificate[]{cert3})); + assertTrue(certificateValidator.isTrusted(new X509Certificate[]{cert1, cert3})); + } + + } + + @Nested + @Import(CertificateValidator.class) + @MockBean(TrustedCertificatesProvider.class) + class Configuration { + + @Nested + @TestPropertySource(properties = { + "apiml.security.x509.certificatesUrl=url1" + }) + @ExtendWith(SpringExtension.class) + class OldPropertyValue { + + @Autowired + private CertificateValidator certificateValidator; + + @Test + void thenUrlIsSetAsListCorrectly() { + assertArrayEquals(new String[] {"url1"}, (String[]) ReflectionTestUtils.getField(certificateValidator, "proxyCertificatesEndpoints")); + } } + + @Nested + @TestPropertySource(properties = { + "apiml.security.x509.certificatesUrls=url1,url2" + }) + @ExtendWith(SpringExtension.class) + class NewPropertyValue { + + @Autowired + private CertificateValidator certificateValidator; + + @Test + void thenUrlsAreSetCorrectly() { + assertArrayEquals(new String[] {"url1", "url2"}, (String[]) ReflectionTestUtils.getField(certificateValidator, "proxyCertificatesEndpoints")); + } + + } + + @Nested + @ExtendWith(SpringExtension.class) + class NoUrlIsProvided { + + @Autowired + private CertificateValidator certificateValidator; + + @Test + void thenNoUrlIsProvided() { + assertArrayEquals(new String[0], (String[]) ReflectionTestUtils.getField(certificateValidator, "proxyCertificatesEndpoints")); + } + + } + } + } diff --git a/apiml-security-common/src/test/resources/security-service-messages.yml b/apiml-security-common/src/test/resources/security-service-messages.yml index a17ec79006..d26855e06c 100644 --- a/apiml-security-common/src/test/resources/security-service-messages.yml +++ b/apiml-security-common/src/test/resources/security-service-messages.yml @@ -70,14 +70,14 @@ messages: type: ERROR text: "Failed to get trusted certificates from the central Gateway. Unexpected response from %s endpoint. Status code: %s. Response body: %s" reason: "The response status code is different from expected 200 OK." - action: "Ensure that the parameter apiml.security.x509.certificatesUrl is correctly configured with the complete URL to the central Gateway certificates endpoint. Test the URL manually." + action: "Ensure that the parameter apiml.security.x509.certificatesUrls is correctly configured with the complete URL to the central Gateway certificates endpoint. Test the URL manually." - key: org.zowe.apiml.security.common.verify.invalidURL number: ZWEAT502 type: ERROR text: "Invalid URL specified to get trusted certificates from the central Gateway. Error message: %s" - reason: "The parameter apiml.security.x509.certificatesUrl is not correctly configured with the complete URL to the central Gateway certificates endpoint." - action: "Ensure that the parameter apiml.security.x509.certificatesUrl is correctly configured." + reason: "The parameter apiml.security.x509.certificatesUrls is not correctly configured with the complete URL to the central Gateway certificates endpoint." + action: "Ensure that the parameter apiml.security.x509.certificatesUrls is correctly configured." - key: org.zowe.apiml.security.common.verify.httpError number: ZWEAT503 @@ -91,14 +91,14 @@ messages: type: ERROR text: "Failed to parse the trusted certificates provided by the central Gateway. Error message %s" reason: "The string sent by the central Gateway was not recognized as valid DER-encoded certificates in the Base64 printable form." - action: "Check that the URL configured in apiml.security.x509.certificatesUrl responds with valid DER-encoded certificates in the Base64 printable form." + action: "Check that the URL configured in apiml.security.x509.certificatesUrls responds with valid DER-encoded certificates in the Base64 printable form." - key: org.zowe.apiml.security.common.verify.untrustedCert number: ZWEAT505 type: ERROR text: "Incoming request certificate is not one of the trusted certificates provided by the central Gateway." reason: "The Gateway performs additional check of request certificates when the central Gateway forwards incoming client certificate to the domain Gateway. This check may fail when the certificatesUrl parameter does not point to proper central Gateway certificates endpoint." - action: "Check that the URL configured in apiml.security.x509.certificatesUrl points to the central Gateway and it responds with valid DER-encoded certificates in the Base64 printable form." + action: "Check that the URL configured in apiml.security.x509.certificatesUrls points to the central Gateway and it responds with valid DER-encoded certificates in the Base64 printable form." # Personal access token messages - key: org.zowe.apiml.security.query.invalidAccessTokenBody diff --git a/gateway-package/src/main/resources/bin/start.sh b/gateway-package/src/main/resources/bin/start.sh index 65b425cb09..597d142a8b 100755 --- a/gateway-package/src/main/resources/bin/start.sh +++ b/gateway-package/src/main/resources/bin/start.sh @@ -295,7 +295,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${GATEWAY_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.service.forwardClientCertEnabled=${ZWE_configs_apiml_security_x509_enabled:-false} \ -Dapiml.security.x509.enabled=${ZWE_configs_apiml_security_x509_enabled:-false} \ -Dapiml.security.x509.acceptForwardedCert=${ZWE_configs_apiml_security_x509_acceptForwardedCert:-false} \ - -Dapiml.security.x509.certificatesUrl=${ZWE_configs_apiml_security_x509_certificatesUrl:-} \ + -Dapiml.security.x509.certificatesUrls=${ZWE_configs_apiml_security_x509_certificatesUrls:-${ZWE_configs_apiml_security_x509_certificatesUrl:-}} \ -Dapiml.service.externalUrl="${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" \ -Dapiml.service.corsEnabled=${ZWE_configs_apiml_service_corsEnabled:-false} \ -Dapiml.security.x509.registry.allowedUsers=${ZWE_configs_apiml_security_x509_registry_allowedUsers:-} \ diff --git a/zaas-package/src/main/resources/bin/start.sh b/zaas-package/src/main/resources/bin/start.sh index 2bfe0b506d..a7872940e4 100755 --- a/zaas-package/src/main/resources/bin/start.sh +++ b/zaas-package/src/main/resources/bin/start.sh @@ -285,6 +285,10 @@ if [ -n "${ZWE_java_home}" ]; then JAVA_BIN_DIR=${ZWE_java_home}/bin/ fi +CERTIFICATES_URLS=${internalProtocol:-https}://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_gateway_port:-7554}/gateway/certificates +CERTIFICATES_URLS=${ZWE_configs_apiml_security_x509_certificatesUrl:-${ZWE_components_gateway_apiml_security_x509_certificatesUrl:-${CERTIFICATES_URLS}}} +CERTIFICATES_URLS=${ZWE_configs_apiml_security_x509_certificatesUrls:-${ZWE_components_gateway_apiml_security_x509_certificatesUrls:-${CERTIFICATES_URLS}}} + ZAAS_CODE=AZ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${ZAAS_CODE} ${JAVA_BIN_DIR}java \ -Xms${ZWE_configs_heap_init:-32}m -Xmx${ZWE_configs_heap_max:-512}m \ @@ -333,7 +337,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${ZAAS_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.security.x509.externalMapperUrl=${ZWE_configs_apiml_security_x509_externalMapperUrl:-${ZWE_components_gateway_apiml_security_x509_externalMapperUrl:-"${internalProtocol:-https}://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_gateway_port:-7554}/zss/api/v1/certificate/x509/map"}} \ -Dapiml.security.x509.externalMapperUser=${ZWE_configs_apiml_security_x509_externalMapperUser:-${ZWE_components_gateway_apiml_security_x509_externalMapperUser:-${ZWE_zowe_setup_security_users_zowe:-ZWESVUSR}}} \ -Dapiml.security.x509.acceptForwardedCert=${ZWE_configs_apiml_security_x509_enabled:-${ZWE_components_gateway_apiml_security_x509_enabled:-${ZWE_components_gateway_apiml_security_x509_enabled:-true}}} \ - -Dapiml.security.x509.certificatesUrl=${ZWE_configs_apiml_security_x509_certificatesUrl:-${ZWE_components_gateway_apiml_security_x509_certificatesUrl:-"${internalProtocol:-https}://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_gateway_port:-7554}/gateway/certificates"}} \ + -Dapiml.security.x509.certificatesUrls=${CERTIFICATES_URLS} \ -Dapiml.security.authorization.provider=${ZWE_configs_apiml_security_authorization_provider:-${ZWE_components_gateway_apiml_security_authorization_provider:-}} \ -Dapiml.security.authorization.endpoint.enabled=${ZWE_configs_apiml_security_authorization_endpoint_enabled:-${ZWE_components_gateway_apiml_security_authorization_endpoint_enabled:-false}} \ -Dapiml.security.authorization.endpoint.url=${ZWE_configs_apiml_security_authorization_endpoint_url:-${ZWE_components_gateway_apiml_security_authorization_endpoint_url:-"${internalProtocol:-https}://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_gateway_port:-7554}/zss/api/v1/saf-auth"}} \