Skip to content

Commit

Permalink
feat: Allows to obtain certificates from multiple sources (#3914)
Browse files Browse the repository at this point in the history
Signed-off-by: Pavel Jareš <[email protected]>
  • Loading branch information
pj892031 authored Nov 28, 2024
1 parent cc30d21 commit 2e028cb
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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<String> publicKeyCertificatesBase64;


Expand All @@ -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<Certificate> trustedCerts = trustedCertificatesProvider.getTrustedCerts(proxyCertificatesEndpoint);
List<Certificate> 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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"));
Expand All @@ -38,11 +44,9 @@ public class CertificateValidatorTest {

@BeforeEach
void setUp() {
List<Certificate> 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());
}
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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"));
}

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gateway-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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:-} \
Expand Down
6 changes: 5 additions & 1 deletion zaas-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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"}} \
Expand Down

0 comments on commit 2e028cb

Please sign in to comment.