diff --git a/docs/configuration/cluster_manager/cluster_ssl.rst b/docs/configuration/cluster_manager/cluster_ssl.rst index 291319505f9db..cc9d41bdba842 100644 --- a/docs/configuration/cluster_manager/cluster_ssl.rst +++ b/docs/configuration/cluster_manager/cluster_ssl.rst @@ -43,7 +43,11 @@ verify_certificate_hash verify_subject_alt_name *(optional, array)* An optional list of subject alt names. If specified, Envoy will verify - that the server certificate's subject alt name matches one of the specified values. + that the client certificate's subject alt name matches one of the specified values. You can specify + two types of subject alt names: DNS (``www.foo.com``) and URI (``foo.com/bar``). Both types also + support optional wildcard matching. For DNS, ``*.foo.com`` will match subject alt names like + ```bar.foo.com`` and ``baz.foo.com``. For URI, ``foo.com/*`` will match names like ``foo.com/bar`` + and ``foo.com/baz``. cipher_suites *(optional, string)* If specified, the TLS connection will only support the specified `cipher list diff --git a/docs/configuration/listeners/ssl.rst b/docs/configuration/listeners/ssl.rst index 385fbbdbbb5b5..f897fffebe0aa 100644 --- a/docs/configuration/listeners/ssl.rst +++ b/docs/configuration/listeners/ssl.rst @@ -65,7 +65,11 @@ verify_certificate_hash verify_subject_alt_name *(optional, array)* An optional list of subject alt names. If specified, Envoy will verify - that the client certificate's subject alt name matches one of the specified values. + that the client certificate's subject alt name matches one of the specified values. You can specify + two types of subject alt names: DNS (``www.foo.com``) and URI (``foo.com/bar``). Both types also + support optional wildcard matching. For DNS, ``*.foo.com`` will match subject alt names like + ```bar.foo.com`` and ``baz.foo.com``. For URI, ``foo.com/*`` will match names like ``foo.com/bar`` + and ``foo.com/baz``. cipher_suites *(optional, string)* If specified, the TLS listener will only support the specified `cipher list diff --git a/source/common/ssl/context_impl.cc b/source/common/ssl/context_impl.cc index d68d642077e91..aa47fba8ae681 100644 --- a/source/common/ssl/context_impl.cc +++ b/source/common/ssl/context_impl.cc @@ -200,7 +200,7 @@ bool ContextImpl::verifySubjectAltName(X509* cert, ASN1_STRING* str = altname->d.dNSName; char* dns_name = reinterpret_cast(ASN1_STRING_data(str)); for (auto& config_san : subject_alt_names) { - if (dNSNameMatch(config_san, dns_name)) { + if (dNSNameMatch(config_san, dns_name, static_cast(ASN1_STRING_length(str)))) { verified = true; break; } @@ -209,7 +209,7 @@ bool ContextImpl::verifySubjectAltName(X509* cert, ASN1_STRING* str = altname->d.uniformResourceIdentifier; char* crt_san = reinterpret_cast(ASN1_STRING_data(str)); for (auto& config_san : subject_alt_names) { - if (config_san.compare(crt_san) == 0) { + if (uriMatch(config_san, crt_san, static_cast(ASN1_STRING_length(str)))) { verified = true; break; } @@ -223,20 +223,29 @@ bool ContextImpl::verifySubjectAltName(X509* cert, return verified; } -bool ContextImpl::dNSNameMatch(const std::string& dNSName, const char* pattern) { - if (dNSName == pattern) { - return true; +bool ContextImpl::dNSNameMatch(const std::string& dns_pattern, const char* dns, size_t dns_len) { + size_t pattern_len = dns_pattern.length(); + if (pattern_len > 1 && dns_pattern.substr(0, 2) == "*.") { + if (dns_len >= pattern_len) { + size_t off = dns_len - pattern_len + 1; + return dns_pattern.compare(1, pattern_len - 1, dns + off, pattern_len - 1) == 0; + } + return false; } - size_t pattern_len = strlen(pattern); - if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') { - if (dNSName.length() > pattern_len - 1) { - size_t off = dNSName.length() - pattern_len + 1; - return dNSName.compare(off, pattern_len - 1, pattern + 1) == 0; + return dns_pattern == dns; +} + +bool ContextImpl::uriMatch(const std::string& uri_pattern, const char* uri, size_t uri_len) { + size_t pattern_len = uri_pattern.length(); + if (pattern_len > 1 && uri_pattern.substr(pattern_len - 2) == "/*") { + if (uri_len >= pattern_len) { + return uri_pattern.compare(0, pattern_len - 1, uri, pattern_len - 1) == 0; } + return false; } - return false; + return uri_pattern == uri; } bool ContextImpl::verifyCertificateHash(X509* cert, const std::vector& expected_hash) { diff --git a/source/common/ssl/context_impl.h b/source/common/ssl/context_impl.h index 38a3abbb08762..8118e9f5e9c94 100644 --- a/source/common/ssl/context_impl.h +++ b/source/common/ssl/context_impl.h @@ -60,13 +60,23 @@ class ContextImpl : public virtual Context { static bool verifySubjectAltName(X509* cert, const std::vector& subject_alt_names); /** - * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard. + * Determines whether the given DNS matches 'pattern' which may begin with a wildcard. * NOTE: public for testing - * @param san the subjectAltName to match - * @param pattern the pattern to match against (*.example.com) - * @return true if the san matches pattern + * @param dns_pattern the pattern to match against (*.example.com) + * @param dns the DNS to match + * @param dns_len length of DNS string + * @return true if the dns matches pattern */ - static bool dNSNameMatch(const std::string& dnsName, const char* pattern); + static bool dNSNameMatch(const std::string& dns_pattern, const char* dns, size_t dns_len); + + /** + * Determines whether the given URI matches 'pattern' which may end with a wildcard. + * @param uri_pattern the pattern to match against + * @param uri the URI to match + * @param uri_len length of URI string + * @return true if uri matches pattern + */ + static bool uriMatch(const std::string& uri_pattern, const char* uri, size_t uri_len); SslStats& stats() { return stats_; } diff --git a/test/common/ssl/context_impl_test.cc b/test/common/ssl/context_impl_test.cc index 397fe4f04df62..5b0fd3d75f07c 100644 --- a/test/common/ssl/context_impl_test.cc +++ b/test/common/ssl/context_impl_test.cc @@ -18,16 +18,31 @@ namespace Ssl { class SslContextImplTest : public SslCertsTest {}; TEST_F(SslContextImplTest, TestdNSNameMatching) { - EXPECT_TRUE(ContextImpl::dNSNameMatch("lyft.com", "lyft.com")); - EXPECT_TRUE(ContextImpl::dNSNameMatch("a.lyft.com", "*.lyft.com")); - EXPECT_TRUE(ContextImpl::dNSNameMatch("a.b.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("foo.test.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("alyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("alyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("lyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dNSNameMatch("lyft.com", "")); + EXPECT_TRUE(ContextImpl::dNSNameMatch("lyft.com", "lyft.com", 8)); + EXPECT_TRUE(ContextImpl::dNSNameMatch("*.lyft.com", "a.lyft.com", 10)); + EXPECT_TRUE(ContextImpl::dNSNameMatch("*.lyft.com", "a.b.lyft.com", 12)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("*.lyft.com", "foo.test.com", 12)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("*.lyft.com", "lyft.com", 8)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("*.lyft.com", "alyft.com", 9)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("*lyft.com", "alyft.com", 9)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("*lyft.com", "lyft.com", 8)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("*lyft.com", "", 0)); + EXPECT_FALSE(ContextImpl::dNSNameMatch("", "lyft.com", 8)); +} + +TEST_F(SslContextImplTest, TestURIMatch) { + EXPECT_TRUE(ContextImpl::uriMatch("spiffe://lyft.com/foo", "spiffe://lyft.com/foo", 21)); + EXPECT_TRUE(ContextImpl::uriMatch("spiffe://lyft.com/*", "spiffe://lyft.com/foo", 21)); + EXPECT_TRUE(ContextImpl::uriMatch("spiffe://lyft.com/foo/*", "spiffe://lyft.com/foo/bar", 25)); + EXPECT_TRUE(ContextImpl::uriMatch("spiffe://lyft.com/*", "spiffe://lyft.com/foo/bar", 25)); + EXPECT_FALSE(ContextImpl::uriMatch("spiffe://lyft.com/*", "spiffe://lyft.com/", 18)); + EXPECT_FALSE(ContextImpl::uriMatch("spiffe://lyft.com/foo", "spiffe://lyft.com/foo/bar", 25)); + EXPECT_FALSE(ContextImpl::uriMatch("spiffe://lyft.com/*", "", 0)); + EXPECT_FALSE(ContextImpl::uriMatch("spiffe://lyft.com/*", "spiffe://lyft.net/foo", 21)); + EXPECT_FALSE(ContextImpl::uriMatch("spiffe://lyft.com*", "spiffe://lyft.com/foo", 21)); + EXPECT_FALSE(ContextImpl::uriMatch("spiffe://lyft.com/*", "spiffe://lyft.comfoo", 20)); + EXPECT_FALSE(ContextImpl::uriMatch("*", "foo", 3)); + EXPECT_FALSE(ContextImpl::uriMatch("f", "foo", 3)); } TEST_F(SslContextImplTest, TestVerifySubjectAltNameDNSMatched) { @@ -52,6 +67,11 @@ TEST_F(SslContextImplTest, TestVerifySubjectAltNameURIMatched) { std::vector verify_subject_alt_name_list = {"spiffe://lyft.com/fake-team", "spiffe://lyft.com/test-team"}; EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert, verify_subject_alt_name_list)); + + verify_subject_alt_name_list.clear(); + verify_subject_alt_name_list.push_back("spiffe://lyft.com/*"); + EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert, verify_subject_alt_name_list)); + X509_free(cert); fclose(fp); } diff --git a/test/config/integration/certs/upstreamcacert.pem b/test/config/integration/certs/upstreamcacert.pem index fc1486bdd18cb..41934d0883444 100644 --- a/test/config/integration/certs/upstreamcacert.pem +++ b/test/config/integration/certs/upstreamcacert.pem @@ -1,9 +1,9 @@ -----BEGIN CERTIFICATE----- -MIIC3zCCAkigAwIBAgIJAIP3FexxDZw/MA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV +MIIC3zCCAkigAwIBAgIJAOqvogwSzSGmMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp c2NvMQ0wCwYDVQQKEwRMeWZ0MRkwFwYDVQQLExBMeWZ0IEVuZ2luZWVyaW5nMRkw -FwYDVQQDExBUZXN0IFVwc3RyZWFtIENBMB4XDTE3MDcwOTAxMzkzMloXDTE5MDcw -OTAxMzkzMlowfzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAU +FwYDVQQDExBUZXN0IFVwc3RyZWFtIENBMB4XDTE3MDcyNzE3NTcxNFoXDTE5MDcy +NzE3NTcxNFowfzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAU BgNVBAcTDVNhbiBGcmFuY2lzY28xDTALBgNVBAoTBEx5ZnQxGTAXBgNVBAsTEEx5 ZnQgRW5naW5lZXJpbmcxGTAXBgNVBAMTEFRlc3QgVXBzdHJlYW0gQ0EwgZ8wDQYJ KoZIhvcNAQEBBQADgY0AMIGJAoGBANbxYY3hK35w0cSDReeoEJtqoegs+v3wo3B7 @@ -11,8 +11,8 @@ Uaki2AWXcdQK4kEyz1zesRcwUgT3gTqNdQJ+WiN0UtZpEgqvNDvSRYj1ONLIrnP7 en1Uc2ld5KHjzZfSUnlDSlHURGad2N/V9fsT8HUUrAFSNnyRRmA54zuuQP8cQBLl YXgisaADAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG MB0GA1UdDgQWBBQ3dX2uHV9GclTYCEwe0vUNWBQwaTAfBgNVHSMEGDAWgBQ3dX2u -HV9GclTYCEwe0vUNWBQwaTANBgkqhkiG9w0BAQsFAAOBgQB6tJ+WGcHcAwc148ca -+TtjxKB1GI6LoQusMXmF5tZlFi/Prk8+pHqcNZdFeNxYSIQnZvIQijTHUFKrBfbn -1KGxrivXNGQj1/ivAAofzduLHK0k8g8koejwyM5aqkMXUucGHyp28+75D5paTnEZ -v+yOm2g0grB52deXB97mj5yYlQ== +HV9GclTYCEwe0vUNWBQwaTANBgkqhkiG9w0BAQsFAAOBgQBEuF8bxTKs/ZxjKCkV +vL/1VFl//Y0iXpYl7D+fH6R1/jC0FQ5x1esvJkmS4vVqWCkpz+u8PFi4LhgIIx5V +6IhP4fvJtBaGkSE7Ct8zFL2mZX4198Bm+XbuUXvdehOAs6GX3XIVI+Pcu7ioaXPP +MTFQHMB4XlhWsUudCZ7VVln5Ew== -----END CERTIFICATE----- diff --git a/test/config/integration/certs/upstreamcert.cfg b/test/config/integration/certs/upstreamcert.cfg index 5ce155873d4d3..c543520dec2a2 100644 --- a/test/config/integration/certs/upstreamcert.cfg +++ b/test/config/integration/certs/upstreamcert.cfg @@ -33,4 +33,4 @@ subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always [alt_names] -DNS.1 = *.lyft.com +DNS.1 = foo.lyft.com diff --git a/test/config/integration/certs/upstreamcert.pem b/test/config/integration/certs/upstreamcert.pem index a92bec5e00c0c..f845d53f51fa2 100644 --- a/test/config/integration/certs/upstreamcert.pem +++ b/test/config/integration/certs/upstreamcert.pem @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDFjCCAn+gAwIBAgIJAIEoeL+knpI0MA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV +MIIDGDCCAoGgAwIBAgIJAJPDaoHAmb5AMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp c2NvMQ0wCwYDVQQKEwRMeWZ0MRkwFwYDVQQLExBMeWZ0IEVuZ2luZWVyaW5nMRkw -FwYDVQQDExBUZXN0IFVwc3RyZWFtIENBMB4XDTE3MDcwOTAxMzkzMloXDTE5MDcw -OTAxMzkzMlowgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw +FwYDVQQDExBUZXN0IFVwc3RyZWFtIENBMB4XDTE3MDcyNzE3NTcxNFoXDTE5MDcy +NzE3NTcxNFowgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw FAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKEwRMeWZ0MRkwFwYDVQQLExBM eWZ0IEVuZ2luZWVyaW5nMR0wGwYDVQQDExRUZXN0IFVwc3RyZWFtIFNlcnZlcjCB nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAu7BZ855UTUGLBbSIG7NbmOHnit0k qhVaS7qf56wuGybRyoS82vUKKF+HG84DJZGjPCCzr9sjgPuDI2ebWf28YScS/K/v VVVk4mdJcpL8LSKF9j2wWjD1fk/soLAfeMgs3GvXBXk1GsJ+PzCr6jKJ+EqghKpV -3snf6/leRi0gBrMCAwEAAaOBlDCBkTAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF -4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKKi5s -eWZ0LmNvbTAdBgNVHQ4EFgQUL4L/GnkQef+2KrADZK0zecZe84MwHwYDVR0jBBgw -FoAUN3V9rh1fRnJU2AhMHtL1DVgUMGkwDQYJKoZIhvcNAQELBQADgYEAvE6Hoguo -ImLP3WPBzW5WuvWwheG2QOlCEo9gP3V/C4ptAr92znG4Jrsp72S86LFdFYR+ZKgK -4QRwtoPyVIb6cGK+N70TE/21Z3jWuA5gvezKT3XnGy+RhPSapLAjjafuvYsVUPv9 -aGPmBCsMiipz9dWjLxVTF9nUNZeRLnxbLSk= +3snf6/leRi0gBrMCAwEAAaOBljCBkzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF +4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwFwYDVR0RBBAwDoIMZm9v +Lmx5ZnQuY29tMB0GA1UdDgQWBBQvgv8aeRB5/7YqsANkrTN5xl7zgzAfBgNVHSME +GDAWgBQ3dX2uHV9GclTYCEwe0vUNWBQwaTANBgkqhkiG9w0BAQsFAAOBgQAHX0fA +YGUQrS5/+k+lnmcJo6j0dSwGZAjemIjbFUXqMPOK72G4vveZKGJvYQ5bLXiRdc5I +XKcxpdF1h9DNnVUdCJtb4UEHds1s167uACX/fLW8IhWiN+V6scI4oBzS0fe4qKn6 +MhcCbQFkIVgCFBesM30elX8yPHIYTSBeeA/OgQ== -----END CERTIFICATE----- diff --git a/test/config/integration/server_ssl.json b/test/config/integration/server_ssl.json index 5966fceec122f..82734794a951b 100644 --- a/test/config/integration/server_ssl.json +++ b/test/config/integration/server_ssl.json @@ -99,7 +99,7 @@ "connect_timeout_ms": 5000, "ssl_context": { "ca_cert_file": "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem", - "verify_subject_alt_name": [ "foo.lyft.com" ] + "verify_subject_alt_name": [ "*.lyft.com" ] }, "type": "static", "lb_type": "round_robin", @@ -110,7 +110,7 @@ "connect_timeout_ms": 5000, "ssl_context": { "ca_cert_file": "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem", - "verify_subject_alt_name": [ "foo.lyft.com" ] + "verify_subject_alt_name": [ "*.lyft.com" ] }, "type": "strict_dns", "lb_type": "round_robin", diff --git a/test/config/integration/server_xfcc.json b/test/config/integration/server_xfcc.json index e965098e75d25..7f17cdf939ac9 100644 --- a/test/config/integration/server_xfcc.json +++ b/test/config/integration/server_xfcc.json @@ -179,7 +179,7 @@ "connect_timeout_ms": 5000, "ssl_context": { "ca_cert_file": "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem", - "verify_subject_alt_name": [ "foo.lyft.com" ] + "verify_subject_alt_name": [ "*.lyft.com" ] }, "type": "static", "lb_type": "round_robin", @@ -190,7 +190,7 @@ "connect_timeout_ms": 5000, "ssl_context": { "ca_cert_file": "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem", - "verify_subject_alt_name": [ "foo.lyft.com" ] + "verify_subject_alt_name": [ "*.lyft.com" ] }, "type": "strict_dns", "lb_type": "round_robin",