Enforcing TLS1.2 for dynamic targets

I have an Apigee Edge proxy brokering a webhook API which delivers payloads to dynamic targets set using a Javascript as shown below

var targeturl = context.getVariable("request.header.Target");
context.setVariable("target.url",targeturl);

From a security perspective, I want the targeturl to always have ssl and TLS 1.2

I added these requirements on the target side of the proxy using the following block

<HTTPTargetConnection>
  <URL>https://will-be-set-dynamically.com</URL>
  <SSLInfo>
    <Enabled>true</Enabled>
    <ClientAuthEnabled>false</ClientAuthEnabled>
    <TrustStore>ref://truststoreref</TrustStore>
    <Protocols>
      <Protocol>TLSv1.2</Protocol>
    </Protocols>
  </SSLInfo>
  <Properties>
    <Property name="request.retain.headers.enabled">true</Property>
    <Property
name="response.retain.headers.enabled">false</Property>
    <Property
name="response.retain.headers">Correlation-Id</Property>
    <Property name="io.timeout.millis">5000</Property>
    <Property
name="success.codes">1xx,2xx,3xx,4xx,5xx</Property>
  </Properties> 
</HTTPTargetConnection>

The URL tag above is just a placeholder as it is dynamically set using the Javascript.

The problem I'm facing is Apigee doesn't seem to enforce SSL and TLS 1.2 with the above configuration.

For testing, I'm sending the Target as http://neverssl.com or https://neverssl.com and Apigee does not throw an exception/fault with both these URLs.

I understand neverssl.com exists specifically as an example of no SSL/TLS site.

Should I change anything in the configuration or does anyone have other sites that can help me test SSL/TLS enforcement.

How can I have Apigee stop forwarding a request to the target if it does not have SSL and TLS 1.2?

Solved Solved
1 12 673
1 ACCEPTED SOLUTION

Hi Krishna,

thanks for the question.

You wrote

Apigee doesn't seem to enforce SSL and TLS 1.2 with the above configuration.

That is not correct, and I'll explain why. I think neverssl.com is not quite behaving the way you expect. You described it as

exists specifically as an example of no SSL/TLS site.

I visited the site and read the text, and I understand why they describe themselves that way. but when I test it, https://neverssl.com returns a certificate; it participates in a TLS handshake. Contrary to what the site name says, this site "does TLS". This output shows what I mean:

$ openssl s_client -showcerts -connect neverssl.com:443  -servername neverssl.com  < /dev/null   2> /dev/null | openssl x509 -noout -dates -subject -issuer

notBefore=Feb 22 00:00:00 2021 GMT
notAfter=Feb 21 23:59:59 2022 GMT
subject= /CN=*.cloudfront.net
issuer= /C=US/O=DigiCert Inc/CN=DigiCert Global CA G2

The site returns a certificate, and the certificate is valid, and has been issued by a well-known and generally trusted issuer. Though the subject on the certificate (cloudfront.net) does not match what is expected (neverssl.com).

Given all that, if you use this SSLInfo:

    <SSLInfo>
      <Enabled>true</Enabled>
      <IgnoreValidationErrors>false</IgnoreValidationErrors>
      <ClientAuthEnabled>false</ClientAuthEnabled>
      <TrustStore>myTrustStore</TrustStore>
      <Protocols>
        <Protocol>TLSv1.2</Protocol>
      </Protocols>
    </SSLInfo>

...and if your truststore has the "DigiCert Global CA G2" certificate, then you can expect Apigee to connect successfully into that target. And that is exactly what I see when I try it. I get back a 403 error and an XML payload from neverssl.com. This confirms that the TLS negotiation happens successfully. (If TLS handshake failed because the cert was not present, not trusted, invalid (bad dates), etc... then Apigee would return a 500). It sounds like you are seeing the same thing.

If you want Apigee to also check the subject on the certificate against what is expected, you need to use an additional configuration element under SSLInfo: CommonName. It looks like this:

    <SSLInfo>
      <Enabled>true</Enabled>
      <IgnoreValidationErrors>false</IgnoreValidationErrors>
      <ClientAuthEnabled>false</ClientAuthEnabled>
      <CommonName ref='target-common-name'/>
      <TrustStore>myTrustStore</TrustStore>
      <Protocols>
        <Protocol>TLSv1.2</Protocol>
      </Protocols>
    </SSLInfo>

And... when you dynamically set target.url, you must also set target-common-name, the variable referenced in the CommonName element there. In that case, Apigee will go through the TLS handshake, find that cloudfront.net does not match "neverssl.com", and then reject the connection.

This is what I see in my tests.

But I think this is not what you intend to test. I think you are attempting to test a case in which the peer (upstream) does not present a valid TLS certificate, or maybe uses TLS v1.1, when you want to enforce TLS v1.2. In the case of the neverssl.com , we see that it does present a valid, trusted certificate. I think perhaps what you want to try to use is "badssl.com", which is a set of sites specifically designed to allow you to exercise incorrect TLS configuration.

There are different examples of expired certs, host name mismatch, certs signed with untrusted root, TLS v1.1, many other variations.

Give that a try, check out the different options, make the various changes in SSLinfo and maybe you'll satisfy yourself that the Apigee peer is doing the expected thing.

When i try it with the badssl endpoint that supports only TLS v1.1, I get this:

curl -i https://$ORG-$ENV.apigee.net/test-badssl/badssl-tlsv11

HTTP/1.1 503 Service Unavailable
Date: Fri, 19 Mar 2021 19:30:20 GMT
Content-Type: application/json
Content-Length: 253
Connection: keep-alive


{"fault":{"faultstring":"SSL Handshake failed javax.net.ssl.SSLHandshakeException: Server chose TLSv1.1, but that protocol version is not enabled or not supported by the client.","detail":{"errorcode":"messaging.adaptors.http.flow.SslHandshakeFailed"}}}

...which is what I expect.

View solution in original post

12 REPLIES 12

can anyone help here?

Also wanted to add that the KVM policy should not use the index attribute when pulling in a comma separated list.

Hi Krishna,

thanks for the question.

You wrote

Apigee doesn't seem to enforce SSL and TLS 1.2 with the above configuration.

That is not correct, and I'll explain why. I think neverssl.com is not quite behaving the way you expect. You described it as

exists specifically as an example of no SSL/TLS site.

I visited the site and read the text, and I understand why they describe themselves that way. but when I test it, https://neverssl.com returns a certificate; it participates in a TLS handshake. Contrary to what the site name says, this site "does TLS". This output shows what I mean:

$ openssl s_client -showcerts -connect neverssl.com:443  -servername neverssl.com  < /dev/null   2> /dev/null | openssl x509 -noout -dates -subject -issuer

notBefore=Feb 22 00:00:00 2021 GMT
notAfter=Feb 21 23:59:59 2022 GMT
subject= /CN=*.cloudfront.net
issuer= /C=US/O=DigiCert Inc/CN=DigiCert Global CA G2

The site returns a certificate, and the certificate is valid, and has been issued by a well-known and generally trusted issuer. Though the subject on the certificate (cloudfront.net) does not match what is expected (neverssl.com).

Given all that, if you use this SSLInfo:

    <SSLInfo>
      <Enabled>true</Enabled>
      <IgnoreValidationErrors>false</IgnoreValidationErrors>
      <ClientAuthEnabled>false</ClientAuthEnabled>
      <TrustStore>myTrustStore</TrustStore>
      <Protocols>
        <Protocol>TLSv1.2</Protocol>
      </Protocols>
    </SSLInfo>

...and if your truststore has the "DigiCert Global CA G2" certificate, then you can expect Apigee to connect successfully into that target. And that is exactly what I see when I try it. I get back a 403 error and an XML payload from neverssl.com. This confirms that the TLS negotiation happens successfully. (If TLS handshake failed because the cert was not present, not trusted, invalid (bad dates), etc... then Apigee would return a 500). It sounds like you are seeing the same thing.

If you want Apigee to also check the subject on the certificate against what is expected, you need to use an additional configuration element under SSLInfo: CommonName. It looks like this:

    <SSLInfo>
      <Enabled>true</Enabled>
      <IgnoreValidationErrors>false</IgnoreValidationErrors>
      <ClientAuthEnabled>false</ClientAuthEnabled>
      <CommonName ref='target-common-name'/>
      <TrustStore>myTrustStore</TrustStore>
      <Protocols>
        <Protocol>TLSv1.2</Protocol>
      </Protocols>
    </SSLInfo>

And... when you dynamically set target.url, you must also set target-common-name, the variable referenced in the CommonName element there. In that case, Apigee will go through the TLS handshake, find that cloudfront.net does not match "neverssl.com", and then reject the connection.

This is what I see in my tests.

But I think this is not what you intend to test. I think you are attempting to test a case in which the peer (upstream) does not present a valid TLS certificate, or maybe uses TLS v1.1, when you want to enforce TLS v1.2. In the case of the neverssl.com , we see that it does present a valid, trusted certificate. I think perhaps what you want to try to use is "badssl.com", which is a set of sites specifically designed to allow you to exercise incorrect TLS configuration.

There are different examples of expired certs, host name mismatch, certs signed with untrusted root, TLS v1.1, many other variations.

Give that a try, check out the different options, make the various changes in SSLinfo and maybe you'll satisfy yourself that the Apigee peer is doing the expected thing.

When i try it with the badssl endpoint that supports only TLS v1.1, I get this:

curl -i https://$ORG-$ENV.apigee.net/test-badssl/badssl-tlsv11

HTTP/1.1 503 Service Unavailable
Date: Fri, 19 Mar 2021 19:30:20 GMT
Content-Type: application/json
Content-Length: 253
Connection: keep-alive


{"fault":{"faultstring":"SSL Handshake failed javax.net.ssl.SSLHandshakeException: Server chose TLSv1.1, but that protocol version is not enabled or not supported by the client.","detail":{"errorcode":"messaging.adaptors.http.flow.SslHandshakeFailed"}}}

...which is what I expect.

Ok I'm able to get it now. I need to add custom fault rules to capture this and send custom response to the backend so it can tell the client the URL they're trying to register has a BAD SSL.

capture.png@dino-at-google I'm now facing an issue that all URLs are reported as Certificate name mismatch even though the certificate Subject is the same as the target.

Here's what I am doing -

As you suggested, I'm using Javacsript to dynamically set "target-common-name" which is something like "api-bat.dhlecs.com"

SSL Info looks like below now

        <SSLInfo>
            <Enabled>true</Enabled>
            <CommonName ref="target-common-name"/>
            <ClientAuthEnabled>false</ClientAuthEnabled>
            <TrustStore>ref://truststoreref</TrustStore>
            <Protocols>
                <Protocol>TLSv1.2</Protocol>
            </Protocols>
        </SSLInfo>

And this domain does have a valid cert with the subject CN = api-bat.dhlecs.com. I visited this site on Chrome and I see this information on the "Subject" along with a few "Subject Alternative Names" (see attachment)

But Apigee still returns a Certificate name mismatch error when processing this request.

Does Apigee look under the Certificate "Subject" which has information like CN,O,L,S,C?

When my target is api-bat.dhlecs.com/mock/v4/webhooks why does Apigee believe there's a certificate name mismatch here?

@Dino-at-Google I can see what the issue is but I don't know how to resolve it. The CommonName attribute as described by you seems like it cannot accept a variable set through a javascript

<CommonName ref="target-common-name"/> 

If I set it directly to a domain name such as

<CommonName>api.mysite.com</CommonName>

then its working

I then tried

<CommonName>{target-common-name}</CommonName>

is also not working.

Please help me how to set this dynamically.

This is not present in the documentation so I don't know what else might work.

Not just that, I also want to check if the host matches any Subject alternative names and not just subjects. Is that doable?

Hi Krishna

The CommonName element does in fact accept a ref= attribute. I think you may need to check that you are successfully setting the variable "target-common-name". Please verify that the variable is being correctly and successfully set in your JavaScript.

as for SANs, I think the CommonName element is smart enough to do the right thing when you use SNI. Are you using SNI?

I'm attaching screenshots where it shows the variable 'target-common-name' is set in Javascript and is seen in the trace.

Also attaching the error step where it shows "Common-Name mismatch"

Is there any variable scope, which unsets the variable by the time the flow enters Target?

I'm setting it in the same JS where I set target.url, so if I target.url is available, I assumed target-common-url will also be available.capture2.pngcapture3.png

@Dino can you offer any further guidance here?


I hope it would work like what you say but I can't get it to work. I even moved the JS to Target endpoint Request to execute just before the cert validation step but it still fails with Cert mismatch error.

I'm sorry, I don't know how to correct that. you may wish to open a support ticket.

There MAY BE a workaround. You can perform the common-name check, AFTER the target has been contacted, and after your proxy has received the response from the target. At that point you can examine the target.cn variable, and raise a fault if it does not match what is expected.

Ok that solution is working.

I'm letting the request go to the target and using the target.cn variable to get the common name of the target cert.

Unfortunately, there is no variable that contains target SANs hence I'm doing the following -

  • Using a Javascript to remove subdomain(s) and get the main domain name for the target.host and target.cn
  • Raising fault if the host domain name does not match the cn domain name

Not the same as checking SANs but at least this won't raise a fault as long the host is in the same domain as the cn domain.

I'm glad you got it sorted, thanks for the followup. (I always learn things when people describe how they resolved their problems, it's really helpful for me. )