amazon-web-serviceswso2wso2-api-manageraws-application-load-balancermtls

WSO2 API Manager 4.2 with AWS ALB : Problems with mTLS Authentication Setup


I am using the WSO2 API Manager 4.2 platform, which is fronted by an AWS Application Load Balancer (ALB). I need to implement Mutual TLS (mTLS) authentication for a specific API deployed on WSO2 gateway. To achieve this, I configured mTLS on the AWS ALB using passthrough mode.

On the WSO2 side, I have configured the API to require mTLS and updated the deployment.toml file to handle the certificate header sent by ALB.

[apimgt.mutual_ssl]
certificate_header = "X-Amzn-Mtls-Clientcert"
enable_client_validation = false
client_certificate_encode = true

Despite these configurations, I am encountering issues where WSO2 responds with an error.

curl -v https://WSO2-GW-ALB.env.dev/my-api/1.0.0/test --key client-v3.key --cert client-v3.crt

< content-type: application/json; charset=UTF-8
< set-cookie: AWSALBTG=xxxxxxxxxx...; Expires=Tue, 06 Aug  GMT; Path=/
< set-cookie: AWSALBTGCORS=xxxxxxxxxx...; Expires=Tue, 06 Aug 2024 14:03:35 GMT; Path=/; SameSite=None; Secure
< activityid: e18b453b-f9ff-4dec-8aaa-0e9ea316312e
< access-control-expose-headers:
< access-control-allow-origin: *
< access-control-allow-methods: GET
< x-forwarded-proto: https
< x-forwarded-for: 10.XX.XX.XX
< x-forwarded-port: 443
< access-control-allow-headers: authorization,Access-Control-Allow-Origin,Content-Type,SOAPAction,apikey,Internal-Key,Authorization
< x-amzn-trace-id: Root=1-6ccccf2b7-4c1026ccccc83630554457e0
< x-amzn-mtls-clientcert: -----BEGIN%20CERTIFICATE-----%0AMIIFJDCCAwxxxxxxxxxxxxxxxxxxxxxxxxygAwxAgI0Aw6<reduced>%0A-----END%20CERTIFICATE-----%0A
<
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection #0 to host WSO2-GW-ALB.env.dev left intact
{"code":"900900","message":"Unclassified Authentication Failure","description":"Error while validating into Certificate Existence"}

It appears that the ALB is sending the client certificate in the X-Amzn-Mtls-Clientcert header, but WSO2 cannot parse it (see the logs below).

WSO2 LOGS :

TID: [] [] [2024-07-30 13:32:12,953] ERROR {org.wso2.carbon.apimgt.gateway.handlers.Utils} - 
Error while validating into Certificate Existence org.wso2.carbon.apimgt.api.APIManagementException:
Error while converting into X509Certificate
        at org.wso2.carbon.apimgt.gateway.handlers.Utils.getClientCertificateFromHeader_aroundBody34(Utils.java:491)
        at org.wso2.carbon.apimgt.gateway.handlers.Utils.getClientCertificateFromHeader(Utils.java:1)
        at org.wso2.carbon.apimgt.gateway.handlers.Utils.getClientCertificate_aroundBody32(Utils.java:448)
        at org.wso2.carbon.apimgt.gateway.handlers.Utils.getClientCertificate(Utils.java:1)
        at org.wso2.carbon.apimgt.gateway.handlers.security.authenticator.MutualSSLAuthenticator.authenticate_aroundBody4(MutualSSLAuthenticator.java:105)
       at org.wso2.carbon.apimgt.gateway.handlers.security.authenticator.MutualSSLAuthenticator.authenticate(MutualSSLAuthenticator.java:1)
        at org.wso2.carbon.apimgt.gateway.handlers.security.APIAuthenticationHandler.isAuthenticate_aroundBody56(APIAuthenticationHandler.java:546)
...
...
...

Caused by: java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Incomplete BER/DER data
        at java.base/sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:115)
        at java.base/java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:355)
        at org.wso2.carbon.apimgt.gateway.handlers.Utils.getClientCertificateFromHeader_aroundBody34(Utils.java:488)

On the other hand, When I bypass the ALB and call the API directly through the gateway node, the mTLS authentication works as expected. Additionally, when I use NGINX as the load balancer and configure it with the ssl-client-cert header for mTLS, the authentication also functions correctly using the same certificate I used in ALB.

Any ideas on what the problem could be ?

EDIT

After further investigation, it appears that the ALB sends the certificate header in URL-encoded PEM format, but the encoding does not include characters such as +, /, and =, which confuses WSO2, as it expects a fully URL-encoded header.

Is there a way to ensure that these characters (+, /, =) are properly encoded before sending the header to WSO2 ?


Solution

  • I had to patch wso2 source code to make it work .

    The certificate header is currently decoded within the getClientCertificateFromHeader method of the Utils.java class using a simple URL decode operation.

    The patch adds support for AWS ALB mTLS, by incorporated the following logic :

    1. Introduce a new configuration property to enable or disable handling of ALB-specific certificate header encoding . for example :
    [apimgt.mutual_ssl]
    certificate_header = "X-Amzn-Mtls-Clientcert"
    client_certificate_encode = true
    ### NEW Property ### 
    aws_alb_certificate_encode = true
    
    1. check If the property aws_alb_certificate_encode is set to true.
    2. Adjust Decoding Logic : Encode the safe characters +, /, and = in the header before decoding the certificate
    3. Then Perform the URL decode operation

    Code implementation :

    https://github.com/wso2/carbon-apimgt/blob/master/components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/handlers/Utils.java

     private static boolean isAwsAlbEncodedCertificate() {
    
            APIManagerConfiguration apiManagerConfiguration =
                    ServiceReferenceHolder.getInstance().getAPIManagerConfiguration();
            if (apiManagerConfiguration != null) {
                String firstProperty = apiManagerConfiguration
                        .getFirstProperty(APIConstants.MutualSSL.AWS_ALB_CERTIFICATE_ENCODE);
                return Boolean.parseBoolean(firstProperty);
            }
            return false;
        }
    
    
    private static Certificate getClientCertificateFromHeader(org.apache.axis2.context.MessageContext axis2MessageContext)
                throws APIManagementException {
            Map headers =
                    (Map) axis2MessageContext.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
            String certificate = (String) headers.get(Utils.getClientCertificateHeader());
            byte[] bytes;
    
            if (certificate != null) {
                if (!isClientCertificateEncoded()) {
                    certificate = APIUtil.getX509certificateContent(certificate);
                    bytes = certificate.getBytes();
                } else {
                    try {
    
                       // If ALB AWS header is received , Encode certificate characters that are considered safe by AWS ALB : + = /
                       if (isAwsAlbEncodedCertificate()) {
                              certificate = certificate
                             .replace("+", "%2B") // Encode '+' to '%2B'
                             .replace("/", "%2F") // Encode '/' to '%2F'
                             .replace("=", "%3D"); // Encode '=' to '%3D
                        // then Url Decode the certificate
                             certificate = URLDecoder.decode(processedAwsCeritifcate, "UTF-8");
                       } else {
                             certificate = URLDecoder.decode(certificate, "UTF-8");
                        }
                    } catch (UnsupportedEncodingException e) {
                        String msg = "Error while URL decoding certificate";
                        throw new APIManagementException(msg, e);
                    }
    
                    certificate = APIUtil.getX509certificateContent(certificate);
                    bytes = Base64.decodeBase64(certificate);
                }
    
                try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                    return cf.generateCertificate(inputStream);
                } catch (IOException | CertificateException e) {
                    String msg = "Error while converting into X509Certificate";
                    throw new APIManagementException(msg, e);
                }
            }
    
            return null;
        }
    

    https://github.com/wso2/carbon-apimgt/blob/604b9c98e8c2d9cfc3045a2fe9bf7a96a252ecc3/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java#L2460

    https://github.com/wso2/carbon-apimgt/blob/604b9c98e8c2d9cfc3045a2fe9bf7a96a252ecc3/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java.orig#L2419

        public static class MutualSSL {
    
            public static final String MUTUAL_SSL_CONFIG_ROOT = "MutualSSL";
            public static final String CLIENT_CERTIFICATE_HEADER = MUTUAL_SSL_CONFIG_ROOT + ".ClientCertificateHeader";
            public static final String CLIENT_CERTIFICATE_ENCODE = MUTUAL_SSL_CONFIG_ROOT + ".ClientCertificateEncode";
    // ########### ALB mTLS Constant ########## 
            public static final String AWS_ALB_CERTIFICATE_ENCODE = MUTUAL_SSL_CONFIG_ROOT + ".AwsAlbCertificateEncode";
            public static final String ENABLE_CLIENT_CERTIFICATE_VALIDATION = MUTUAL_SSL_CONFIG_ROOT +
                    ".EnableClientCertificateValidation";
    
        }
    
      <MutualSSL>
            <ClientCertificateHeader>{{apimgt.mutual_ssl.certificate_header}}</ClientCertificateHeader>
            <EnableClientCertificateValidation>{{apimgt.mutual_ssl.enable_client_validation}}</EnableClientCertificateValidation>
     <!-- AWS ALB Encoding -->
            <AwsAlbCertificateEncode>{{apimgt.mutual_ssl.aws_alb_certificate_encode}}</AwsAlbCertificateEncode>
            {% if apimgt.mutual_ssl.client_certificate_encode is defined %}
            <ClientCertificateEncode>{{apimgt.mutual_ssl.client_certificate_encode}}</ClientCertificateEncode>
            {% endif %}
         </MutualSSL>
    
    [apimgt.mutual_ssl]
    certificate_header = "x-amzn-mtls-clientcert"
    enable_client_validation = false
    client_certificate_encode = true
    aws_alb_certificate_encode = true
    

    I have submitted an enhancement request for this use case . This issue addresses the need for better support for AWS ALB's mTLS passthrough mode in WSO2 Gateway : https://github.com/wso2/api-manager/issues/3058