diff --git a/cmd/nginx/main.go b/cmd/nginx/main.go index 13bb3e761d..51ff186ed8 100644 --- a/cmd/nginx/main.go +++ b/cmd/nginx/main.go @@ -55,7 +55,7 @@ const ( // client code is overriding it. defaultBurst = 1e6 - fakeCertificate = "default-fake-certificate" + fakeCertificateName = "default-fake-certificate" ) func main() { @@ -116,12 +116,12 @@ func main() { if err != nil { klog.Fatalf("unexpected error creating fake SSL Cert: %v", err) } - err = ssl.StoreSSLCertOnDisk(fs, fakeCertificate, sslCert) + err = ssl.StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert) if err != nil { klog.Fatalf("unexpected error storing fake SSL Cert: %v", err) } - conf.FakeCertificatePath = sslCert.PemFileName - conf.FakeCertificateSHA = sslCert.PemSHA + conf.FakeCertificate = sslCert + klog.Infof("Created fake certificate with PemFileName: %v", conf.FakeCertificate.PemFileName) // end create default fake SSL certificates conf.Client = kubeClient diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index 90f7897703..95a3ec8e78 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -88,8 +88,7 @@ type Configuration struct { EnableSSLChainCompletion bool - FakeCertificatePath string - FakeCertificateSHA string + FakeCertificate *ingress.SSLCert SyncRateLimit float32 @@ -829,6 +828,21 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres return upstreams, nil } +// overridePemFileNameAndPemSHA should only be called when DynamicCertificatesEnabled +// ideally this function should not exist, the only reason why we use it is that +// we rely on PemFileName in nginx.tmpl to configure SSL directives +// and PemSHA to force reload +func (n *NGINXController) overridePemFileNameAndPemSHA(cert *ingress.SSLCert) { + // TODO(elvinefendi): It is not great but we currently use PemFileName to decide whether SSL needs to be configured + // in nginx configuration or not. The whole thing needs to be refactored, we should rely on a proper + // signal to configure SSL, not PemFileName. + cert.PemFileName = n.cfg.FakeCertificate.PemFileName + + // TODO(elvinefendi): This is again another hacky way of avoiding Nginx reload when certificate + // changes in dynamic SSL mode since FakeCertificate never changes. + cert.PemSHA = n.cfg.FakeCertificate.PemSHA +} + // createServers builds a map of host name to Server structs from a map of // already computed Upstream structs. Each Server is configured with at least // one root location, which uses a default backend if left unspecified. @@ -856,16 +870,16 @@ func (n *NGINXController) createServers(data []*ingress.Ingress, ProxyBuffering: bdef.ProxyBuffering, } - // generated on Start() with createDefaultSSLCertificate() - defaultPemFileName := n.cfg.FakeCertificatePath - defaultPemSHA := n.cfg.FakeCertificateSHA + defaultCertificate := n.cfg.FakeCertificate // read custom default SSL certificate, fall back to generated default certificate if n.cfg.DefaultSSLCertificate != "" { - defaultCertificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate) + certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate) if err == nil { - defaultPemFileName = defaultCertificate.PemFileName - defaultPemSHA = defaultCertificate.PemSHA + defaultCertificate = certificate + if n.cfg.DynamicCertificatesEnabled { + n.overridePemFileNameAndPemSHA(defaultCertificate) + } } else { klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err) } @@ -874,10 +888,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress, // initialize default server and root location servers[defServerName] = &ingress.Server{ Hostname: defServerName, - SSLCert: ingress.SSLCert{ - PemFileName: defaultPemFileName, - PemSHA: defaultPemSHA, - }, + SSLCert: *defaultCertificate, Locations: []*ingress.Location{ { Path: rootLocation, @@ -1021,8 +1032,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress, if tlsSecretName == "" { klog.V(3).Infof("Host %q is listed in the TLS section but secretName is empty. Using default certificate.", host) - servers[host].SSLCert.PemFileName = defaultPemFileName - servers[host].SSLCert.PemSHA = defaultPemSHA + servers[host].SSLCert = *defaultCertificate continue } @@ -1030,8 +1040,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress, cert, err := n.store.GetLocalSSLCert(secrKey) if err != nil { klog.Warningf("Error getting SSL certificate %q: %v. Using default certificate", secrKey, err) - servers[host].SSLCert.PemFileName = defaultPemFileName - servers[host].SSLCert.PemSHA = defaultPemSHA + servers[host].SSLCert = *defaultCertificate continue } @@ -1046,16 +1055,13 @@ func (n *NGINXController) createServers(data []*ingress.Ingress, klog.Warningf("SSL certificate %q does not contain a Common Name or Subject Alternative Name for server %q: %v", secrKey, host, err) klog.Warningf("Using default certificate") - servers[host].SSLCert.PemFileName = defaultPemFileName - servers[host].SSLCert.PemSHA = defaultPemSHA + servers[host].SSLCert = *defaultCertificate continue } } if n.cfg.DynamicCertificatesEnabled { - // useless placeholders: just to shut up NGINX configuration loader errors: - cert.PemFileName = defaultPemFileName - cert.PemSHA = defaultPemSHA + n.overridePemFileNameAndPemSHA(cert) } servers[host].SSLCert = *cert diff --git a/internal/ingress/controller/controller_test.go b/internal/ingress/controller/controller_test.go index 35fbd7384c..b173d0cdea 100644 --- a/internal/ingress/controller/controller_test.go +++ b/internal/ingress/controller/controller_test.go @@ -38,8 +38,11 @@ import ( ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" "k8s.io/ingress-nginx/internal/ingress/controller/store" "k8s.io/ingress-nginx/internal/k8s" + "k8s.io/ingress-nginx/internal/net/ssl" ) +const fakeCertificateName = "default-fake-certificate" + func TestMergeAlternativeBackends(t *testing.T) { testCases := map[string]struct { ingress *ingress.Ingress @@ -918,7 +921,19 @@ func newNGINXController(t *testing.T) *NGINXController { pod, false) + // BEGIN create fake ssl cert + defCert, defKey := ssl.GetFakeSSLCert() + sslCert, err := ssl.CreateSSLCert(defCert, defKey) + if err != nil { + t.Fatalf("unexpected error creating fake SSL Cert: %v", err) + } + err = ssl.StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert) + if err != nil { + t.Fatalf("unexpected error storing fake SSL Cert: %v", err) + } + // END create fake ssl cert config := &Configuration{ + FakeCertificate: sslCert, ListenPorts: &ngx_config.ListenPorts{ Default: 80, }, diff --git a/internal/ingress/controller/store/backend_ssl.go b/internal/ingress/controller/store/backend_ssl.go index a422e3f2ea..93cdfdad99 100644 --- a/internal/ingress/controller/store/backend_ssl.go +++ b/internal/ingress/controller/store/backend_ssl.go @@ -103,7 +103,7 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err) } - if !s.isDynamicCertificatesEnabled || len(ca) > 0 || s.defaultSSLCertificate == secretName { + if !s.isDynamicCertificatesEnabled || len(ca) > 0 { err = ssl.StoreSSLCertOnDisk(s.filesystem, nsSecName, sslCert) if err != nil { return nil, fmt.Errorf("error while storing certificate and key: %v", err) diff --git a/rootfs/etc/nginx/lua/certificate.lua b/rootfs/etc/nginx/lua/certificate.lua index 9fea4a21c3..e07ebcb080 100644 --- a/rootfs/etc/nginx/lua/certificate.lua +++ b/rootfs/etc/nginx/lua/certificate.lua @@ -4,6 +4,8 @@ local re_sub = ngx.re.sub local _M = {} +local DEFAULT_CERT_HOSTNAME = "_" + local function set_pem_cert_key(pem_cert_key) local der_cert, der_cert_err = ssl.cert_pem_to_der(pem_cert_key) if not der_cert then @@ -47,13 +49,20 @@ end function _M.call() local hostname, hostname_err = ssl.server_name() if hostname_err then - ngx.log(ngx.ERR, "Error getting the hostname, falling back on default certificate: " .. hostname_err) - return + ngx.log(ngx.ERR, "error while obtaining hostname: " .. hostname_err) + end + if not hostname then + ngx.log(ngx.INFO, + "obtained hostname is nil (the client does not support SNI?), falling back to default certificate") + hostname = DEFAULT_CERT_HOSTNAME end local pem_cert_key = get_pem_cert_key(hostname) - if not pem_cert_key or pem_cert_key == "" then - ngx.log(ngx.ERR, "Certificate not found, falling back on default certificate for hostname: " .. tostring(hostname)) + if not pem_cert_key then + pem_cert_key = get_pem_cert_key(DEFAULT_CERT_HOSTNAME) + end + if not pem_cert_key then + ngx.log(ngx.ERR, "certificate not found, falling back to fake certificate for hostname: " .. tostring(hostname)) return end diff --git a/rootfs/etc/nginx/lua/test/certificate_test.lua b/rootfs/etc/nginx/lua/test/certificate_test.lua index a71843748e..2de532ad69 100644 --- a/rootfs/etc/nginx/lua/test/certificate_test.lua +++ b/rootfs/etc/nginx/lua/test/certificate_test.lua @@ -1,14 +1,41 @@ local certificate = require("certificate") - -local PEM_CERT_KEY = "-----BEGIN CERTIFICATE-----\nMIID6DCCAlCgAwIBAgIQcfG0mA7BIFqhlnr/Zwh6TzANBgkqhkiG9w0BAQsFADBC\nMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExIDAeBgNVBAsMF2hlbnJ5\ndHJhbkBPVFQtSGVucnlUcmFuMB4XDTE4MDcwOTIwMTY1MloXDTI4MDcwOTIwMTY1\nMlowSzEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMSAw\nHgYDVQQLDBdoZW5yeXRyYW5AT1RULUhlbnJ5VHJhbjCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBALIrsgHzjZZyKWPn3rGzTkaj9jADYAMhM+0wY3iky2Dx\ndr2YbKnZbbGxKLfVukYRsUUOK0SnBMTX15fsGanirj2hflMHfGvHilaVkVAkPJgD\nBTf2PkxFff99hS7/Ncz20MR6+E/vqp7Hx7IKDrg9lC9u1n82aotfN3gPhif8HyQu\n+P9cltsr9PewyPe4573WQmzXhTKaFm9+U9xZ2qS1J0DMEizRs45vuM040hxtiwVz\nM4Lm8DVpaYxMBWNI/zo9EZzoSJZH1sYUpTMwhNj+caEX+LK9PCM4Sht/yhPUc6aD\nnIEqraz+bS8dNFH5Ehp7n1SZL7YH6xT6da4F3ci7jEECAwEAAaNRME8wDgYDVR0P\nAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwGgYD\nVR0RBBMwEYIPbXltaW5pa3ViZS5pbmZvMA0GCSqGSIb3DQEBCwUAA4IBgQC/TEpx\nJkL/ek37L2XKwGq96hNT10IZ9yE+RlndNGNY6eAc8y313sXBHTFbDCWQ2s0pWZZS\n+va20dnTQNyWzJAxFpdNvcKOCUGat4RPD/j+pBTEYk5n/oo7s2FWkG8kW6tKDilf\njWHpk7m9uYCO2sOZFiQPR81idR5PLox46SpJmIhDVfCi6VS4N+8fAT8Tbt9xkPmS\nmODhpnuIUt0NVTi62eqnxeO185qAt73xhz9Gj1KHntAK1ebcx0k3UxKRXQp9WY76\nF39sSz8OuhEhvv9ayl6uS6ZdSLvvb6kJrRRneKr01ridCOtiYB7cuXykDL1c6PUk\nugxDgTyCjiuPnRl0CLwxWT659PVozA2SO1YCW6UcoGdj2KMvsXezeWKpNGx3NHXO\nufdlxSbzWlamn+sPunWP3v2tfV0J8sHG3n1roeBO2N52197/ennGuCZfnF8C5MoG\n9YfMjKg9Z03G8sDpk9g5bHp9p28TO1X+Ht30PQzkUNhx3fjTO2DDvCyGk2k=\n-----END CERTIFICATE-----\n\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyK7IB842Wcilj\n596xs05Go/YwA2ADITPtMGN4pMtg8Xa9mGyp2W2xsSi31bpGEbFFDitEpwTE19eX\n7Bmp4q49oX5TB3xrx4pWlZFQJDyYAwU39j5MRX3/fYUu/zXM9tDEevhP76qex8ey\nCg64PZQvbtZ/NmqLXzd4D4Yn/B8kLvj/XJbbK/T3sMj3uOe91kJs14UymhZvflPc\nWdqktSdAzBIs0bOOb7jNONIcbYsFczOC5vA1aWmMTAVjSP86PRGc6EiWR9bGFKUz\nMITY/nGhF/iyvTwjOEobf8oT1HOmg5yBKq2s/m0vHTRR+RIae59UmS+2B+sU+nWu\nBd3Iu4xBAgMBAAECggEBAIbuUIDp0fB9xJrEnwI0qLMWuPrjk3LLUmfunWZgZyWj\nuCkdpi17XHeVkyCl28v02itR77KuSg5I6B1F0Km34f0KsIBwyulU1I999e6bgsgc\ngXdAJS3d8u3qQVK2NChlQvWJq0PeXXiiE7nhpAQjnnXNmuP8cfPayEdEenUNmwfq\nxjEh2/oDzUTPD/4z5Hpw8n728SItgBolMNgGvmv5cC4JNLqujCUPwkLiZ3a2YbTY\nrmOO1xDkZnQWqyNP+baOwYwpu/kISPM3IveP5GGBNQsUsDxs6t80HNW/w8Ry0f50\n+gNTIuJVOLXfpVLIo87wTMEtRAqMzT4vxQIi+vj2XYECgYEA6QftSRKqur1G4P6Y\n9cseDnljJFWIjqex2q3NrvMaHbnlXp6AtPROoNz6L8H+PnBy8o1yZJwWnhKTvPaD\nsi+a1g7dqQIM4TjKLlidV57lt5ENw4ueW1o7Jbk75gawLhrBPSCFxR5xSqF+kQxn\nmWGjLnZoomD6fM2CG7EF1fg+wjsCgYEAw7t6db9tjGGM9J0rUe+jVtxMWiG9ArT0\nhmaLZQlKrOSFeEf8c4ZYBNxp/X+/jg0GWBX8P4KRubAz6bbn0A+07K9TClrvMFWq\nveqnK1JUsMGWsYQPp8dX8VS/jOFzYdiji9Ekyzs9RiXW8jzp0wzrccSjEr0de8HK\niEa9CH7cZ7MCgYB1V5mD51NzbyZG281YT+yVq0hiHnQCKa1kiYp+I0ouV9KJP9Vd\nyXvigwO0ksIc3PD09Ib65KJ6/K3KRHPygQg97ARwO2kS7E7a4aJxYcEZG4DLy/10\n0M3h5BGmdg23WZ+e0UarCPZRd1rNXWq5kLHkDpoH0j+wIqf2m8Bti3DGywKBgBEK\nn6zkz9rrG2So0n69yJDleVhXm6dCrg+NmhFf77qB4wUH73j3d25k6m2B0+HATI8a\nyu2Upq9uIfb1T9WTqIL6+NXr+OtSah1C8u8YqfsBv+cQwnQvLP78C/luH6ejPwoL\nWZLAQ6N54+8PUqRneZBcOH6HLKv7wXCACDFXKkV1AoGAfDb5GJ0NsovWhLfU5WEB\nSfdzHBplbp72q08S0aqTNm0wlTiCGYgm2Lle4IaOGoJ+7ipirL0KzuwisAZJFTvJ\nhsMqOmH/Ckledmf2JpLxyg8KB5KVA+RVQkrfVEv8yhqLcKQU6Z2n4jSTon7hXb1T\nf8neDpZ8DwO0W9cOdYLYyTg=\n-----END PRIVATE KEY-----" +local ssl = require("ngx.ssl") + +local function read_file(path) + local file = assert(io.open(path, "rb")) + local content = file:read("*a") + file:close() + return content +end + +local EXAMPLE_CERT = read_file("rootfs/etc/nginx/lua/test/fixtures/example-com-cert.pem") +local DEFAULT_CERT = read_file("rootfs/etc/nginx/lua/test/fixtures/default-cert.pem") +local DEFAULT_CERT_HOSTNAME = "_" + +local function assert_certificate_is_set(cert) + spy.on(ngx, "log") + spy.on(ssl, "set_der_cert") + spy.on(ssl, "set_der_priv_key") + + assert.has_no.errors(certificate.call) + assert.spy(ngx.log).was_not_called_with(ngx.ERR, _) + assert.spy(ssl.set_der_cert).was_called_with(ssl.cert_pem_to_der(cert)) + assert.spy(ssl.set_der_priv_key).was_called_with(ssl.priv_key_pem_to_der(cert)) +end + +local function refute_certificate_is_set() + spy.on(ssl, "set_der_cert") + spy.on(ssl, "set_der_priv_key") + + assert.has_no.errors(certificate.call) + assert.spy(ssl.set_der_cert).was_not_called() + assert.spy(ssl.set_der_priv_key).was_not_called() +end local unmocked_ngx = _G.ngx describe("Certificate", function() describe("call", function() - local ssl = require("ngx.ssl") - local match = require("luassert.match") - before_each(function() ssl.server_name = function() return "hostname", nil end ssl.clear_certs = function() return true, "" end @@ -16,6 +43,9 @@ describe("Certificate", function() ssl.set_der_priv_key = function(priv_key) return true, "" end ngx.exit = function(status) end + + + ngx.shared.certificate_data:set(DEFAULT_CERT_HOSTNAME, DEFAULT_CERT) end) after_each(function() @@ -23,101 +53,53 @@ describe("Certificate", function() ngx.shared.certificate_data:flush_all() end) - it("does not clear fallback certificates and logs error message when host is not in dictionary", function() - ngx.shared.certificate_data:set("hostname", "") - - spy.on(ngx, "log") - spy.on(ssl, "clear_certs") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") - - assert.has_no.errors(certificate.call) - assert.spy(ngx.log).was_called_with(ngx.ERR, "Certificate not found, falling back on default certificate for hostname: hostname") - assert.spy(ssl.clear_certs).was_not_called() - assert.spy(ssl.set_der_cert).was_not_called() - assert.spy(ssl.set_der_priv_key).was_not_called() - end) - - it("does not clear fallback certificates and logs error message when the cert is empty for given host", function() - spy.on(ngx, "log") - spy.on(ssl, "clear_certs") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") - - assert.has_no.errors(certificate.call) - assert.spy(ngx.log).was_called_with(ngx.ERR, "Certificate not found, falling back on default certificate for hostname: hostname") - assert.spy(ssl.clear_certs).was_not_called() - assert.spy(ssl.set_der_cert).was_not_called() - assert.spy(ssl.set_der_priv_key).was_not_called() - end) - - it("successfully sets SSL certificate and key when hostname is found in dictionary", function() - ngx.shared.certificate_data:set("hostname", PEM_CERT_KEY) - - spy.on(ngx, "log") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") + it("sets certificate and key when hostname is found in dictionary", function() + ngx.shared.certificate_data:set("hostname", EXAMPLE_CERT) - assert.has_no.errors(certificate.call) - assert.spy(ngx.log).was_not_called_with(ngx.ERR, _) - assert.spy(ssl.set_der_cert).was_called_with(ssl.cert_pem_to_der(PEM_CERT_KEY)) - assert.spy(ssl.set_der_priv_key).was_called_with(ssl.priv_key_pem_to_der(PEM_CERT_KEY)) + assert_certificate_is_set(EXAMPLE_CERT) end) - it("successfully sets SSL certificate and key for wildcard cert", function() + it("sets certificate and key for wildcard cert", function() ssl.server_name = function() return "sub.hostname", nil end - ngx.shared.certificate_data:set("*.hostname", PEM_CERT_KEY) - - spy.on(ngx, "log") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") + ngx.shared.certificate_data:set("*.hostname", EXAMPLE_CERT) - assert.has_no.errors(certificate.call) - assert.spy(ngx.log).was_not_called_with(ngx.ERR, _) - assert.spy(ssl.set_der_cert).was_called_with(ssl.cert_pem_to_der(PEM_CERT_KEY)) - assert.spy(ssl.set_der_priv_key).was_called_with(ssl.priv_key_pem_to_der(PEM_CERT_KEY)) + assert_certificate_is_set(EXAMPLE_CERT) end) - it("successfully sets SSL certificate and key for nested wildcard cert", function() + it("sets certificate and key for nested wildcard cert", function() ssl.server_name = function() return "sub.nested.hostname", nil end - ngx.shared.certificate_data:set("*.nested.hostname", PEM_CERT_KEY) + ngx.shared.certificate_data:set("*.nested.hostname", EXAMPLE_CERT) - spy.on(ngx, "log") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") - - assert.has_no.errors(certificate.call) - assert.spy(ngx.log).was_not_called_with(ngx.ERR, _) - assert.spy(ssl.set_der_cert).was_called_with(ssl.cert_pem_to_der(PEM_CERT_KEY)) - assert.spy(ssl.set_der_priv_key).was_called_with(ssl.priv_key_pem_to_der(PEM_CERT_KEY)) + assert_certificate_is_set(EXAMPLE_CERT) end) it("logs error message when certificate in dictionary is invalid", function() ngx.shared.certificate_data:set("hostname", "something invalid") spy.on(ngx, "log") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") - assert.has_no.errors(certificate.call) + refute_certificate_is_set() assert.spy(ngx.log).was_called_with(ngx.ERR, "failed to convert certificate chain from PEM to DER: PEM_read_bio_X509_AUX() failed") - assert.spy(ssl.set_der_cert).was_not_called() - assert.spy(ssl.set_der_priv_key).was_not_called() end) - it("does not clear fallback certificates and logs error message when hostname could not be fetched", function() - ssl.server_name = function() return nil, "error" end + it("uses default certificate when there's none found for given hostname", function() + assert_certificate_is_set(DEFAULT_CERT) + end) + + it("uses default certificate when hostname can not be obtained", function() + ssl.server_name = function() return nil, "crazy hostname error" end + + assert_certificate_is_set(DEFAULT_CERT) + assert.spy(ngx.log).was_called_with(ngx.ERR, "error while obtaining hostname: crazy hostname error") + end) + + it("fails when hostname does not have certificate and default cert is invalid", function() + ngx.shared.certificate_data:set(DEFAULT_CERT_HOSTNAME, "invalid") spy.on(ngx, "log") - spy.on(ssl, "clear_certs") - spy.on(ssl, "set_der_cert") - spy.on(ssl, "set_der_priv_key") - - assert.has_no.errors(certificate.call) - assert.spy(ngx.log).was_called_with(ngx.ERR, "Error getting the hostname, falling back on default certificate: error") - assert.spy(ssl.clear_certs).was_not_called() - assert.spy(ssl.set_der_cert).was_not_called() - assert.spy(ssl.set_der_priv_key).was_not_called() + + refute_certificate_is_set() + assert.spy(ngx.log).was_called_with(ngx.ERR, "failed to convert certificate chain from PEM to DER: PEM_read_bio_X509_AUX() failed") end) end) end) diff --git a/rootfs/etc/nginx/lua/test/fixtures/default-cert.pem b/rootfs/etc/nginx/lua/test/fixtures/default-cert.pem new file mode 100644 index 0000000000..e8631c85b5 --- /dev/null +++ b/rootfs/etc/nginx/lua/test/fixtures/default-cert.pem @@ -0,0 +1,45 @@ +-----BEGIN CERTIFICATE----- +MIICxDCCAawCCQCjnxUYH38uOjANBgkqhkiG9w0BAQsFADAkMRAwDgYDVQQDDAdk +ZWZhdWx0MRAwDgYDVQQKDAdkZWZhdWx0MB4XDTE5MDQxMzE3NTgwNVoXDTM5MDQw +ODE3NTgwNVowJDEQMA4GA1UEAwwHZGVmYXVsdDEQMA4GA1UECgwHZGVmYXVsdDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANuhdV19jJGMRCy7/x6krbHh +GUoN/C2T9a/ZuA8CaBHpDCcCnYCrYjPWjOezuuk5CMWx/IHRgTxbz9z2MXyfSMli +7bkDra0tmahI3Z0ADNxt/QQ30f1I84Y877urO5RJt3W4NmWM9jqbv/AFO8+oWRjI +s9+leQxyvIWtKz524eXGmu0iGD4KkeH6bPCXYotC/t5XH4v9NfHRoZ3M9eaDuKd6 +k54EVol8LUDaBvbicIE8M1Znf1vQWdP8w4nhP739Oc/p5YKcG7jJahLa9nx+AJIe +vxPP9/nQxN1PAcuXK6HAtgF3nkadtW2nd9Ws3bsOn+ZHaE+hQXtMzLZ5/L8BZ50C +AwEAATANBgkqhkiG9w0BAQsFAAOCAQEApWib3ctn/okShC0Krw56vyjqbuKx9KMQ +QuClYR6HTU8D5F9zr2NFyrSMik12wbqPH6VPYRAjVBfFEhzYDaO+DjTJp0wcIe1z +a2fWVjELLg9PEDlB4mVmtJUMkVknwbZ6eD4XRO6ooifSOhg/36KchilbnGchwwaY +Gh4/rNKWqKD5rPVQhUsptNnsZ8trPQ+W3p94rzXyQkWS8KWCD0EeMzdRZnUm/utx +4lDGCdw1GLEfm/SnNR+dyu4ETzY6/s5csChBVZw9xlXzId6QymeGvJe0jcoTnLCG +KNq3F1fTqUXXhP3PTuuNclz0c4/8QZC/l2xH6Xb07H2iOPuuFnDVZA== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDboXVdfYyRjEQs +u/8epK2x4RlKDfwtk/Wv2bgPAmgR6QwnAp2Aq2Iz1ozns7rpOQjFsfyB0YE8W8/c +9jF8n0jJYu25A62tLZmoSN2dAAzcbf0EN9H9SPOGPO+7qzuUSbd1uDZljPY6m7/w +BTvPqFkYyLPfpXkMcryFrSs+duHlxprtIhg+CpHh+mzwl2KLQv7eVx+L/TXx0aGd +zPXmg7inepOeBFaJfC1A2gb24nCBPDNWZ39b0FnT/MOJ4T+9/TnP6eWCnBu4yWoS +2vZ8fgCSHr8Tz/f50MTdTwHLlyuhwLYBd55GnbVtp3fVrN27Dp/mR2hPoUF7TMy2 +efy/AWedAgMBAAECggEBAJ37LLX8CjHjqGJZNDCxmfNajFtVZfDO/inovNmnDH7d +mI0y92JHZRMOoDpGcQszqFi0J4Kl1YU6MXGqcXxIAw5BJ+guei4Yn++JwkcdcyLX +xujS0iyT3f/QM01V5TxMLjfyMsanN7J+t/iJezVqzfPi4mfb2g+XNH4fSvzafLFO +7p9/Mw3J2rB2rV0aJxh8abh0p4bSSPSoQgeubQ6KlwoOJYBZ/a8TmmZqB8DjOPYb +Pad0sTHsQc4q9wrP7zxZmeCDnD0XEluAxX9ZF4Ou/AWBHTRQpu5HH2pUXvm88VI1 +/4QAaxYozxuAknqSnVqpCXjSpYoXAXEX64aroJui/UECgYEA7VdN4jtn62jgVK1V +HHaoZlyfIAzrC7yObyfdlL39NDz4B7emRNvFx7FXgslPRnvlMb/B2mASzrQij7uI +sfsIO7kOJBq6BqnEalCynFj9p5EFQcOehOXYu46Qj1dKp43dptTaxnYnA1xKL9Z5 +DDwrxpD2Z6ur3o6A55qX7M6tLTECgYEA7OW33x3dTX4H5ea8ghhXvrSnqycFhhqE +Grae9HpAUFV5/u6LC14xHk6Cd27cVI/OXAIar1M9aA1FNwnU+NmMgLKyAGgnKVPi +GkDWaAaWKeW32bNHdqg3XmP2TcEXn1PCSwNc4cVPWDfeVQeCtposH0jWITFB9C4O +9sKkfVMCVi0CgYEAzecn0lTnWvupYszdQcxPXD6ObifG4m+6wgQ734bT3DXomAlj +XemsNApOeVBcTjG+LOLHMsSWjG0KbterR30ZL3bkJb5qFM3DcNiBm9I4fN77SIqF +Q5aD6HNORozcX3BcExgmlHZ8chXm5omSimLJN4MbweTVPkcy3brogrDq3IECgYEA +x3Ls6NuS+/BVI/ms0nc+QOCGnfG/k9V1TaxdjgXzae9dRAaAaHTIM/TzoSxkMonU +uuBGqUAS3iz2Dk2n0lAPHDfW58LI3eGy5lmaaoDJIsM2lAJ982fTHhRZRcOBaPIz +DcbqB2eA0wxOkxY8thJ9fWVsawu2tKemj5j2tlESEY0CgYAl3QnNqfkuKKpen7wJ +3LF+rm0Xtw3a9kSE2+dsdkzn3sl2OpX44V1UtSJDJmpsk8OBw20ISgWp6UlnnncG +J0xmjSNaRH0UBfQ7PyntvC7FhaOncP5emrwH80oOjlGyY2i6m9ognLQBo44/XgGq +VwtXclxMu2tvVKKXaXQAwQiNOA== +-----END PRIVATE KEY----- diff --git a/rootfs/etc/nginx/lua/test/fixtures/example-com-cert.pem b/rootfs/etc/nginx/lua/test/fixtures/example-com-cert.pem new file mode 100644 index 0000000000..bc8757a42e --- /dev/null +++ b/rootfs/etc/nginx/lua/test/fixtures/example-com-cert.pem @@ -0,0 +1,45 @@ +-----BEGIN CERTIFICATE----- +MIICzDCCAbQCCQD8UB3X6pdyYjANBgkqhkiG9w0BAQsFADAoMRQwEgYDVQQDDAtl +eGFtcGxlLmNvbTEQMA4GA1UECgwHZXhhbXBsZTAeFw0xOTA0MTMxNjU2MzJaFw0z +OTA0MDgxNjU2MzJaMCgxFDASBgNVBAMMC2V4YW1wbGUuY29tMRAwDgYDVQQKDAdl +eGFtcGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+IeuF0/KeVZ +wuV54zZ1D747T+EGUmGAJgS7rBmzagSk87tFcFkVFz2Pfh/bytde0YjoYEfb+Nil +LEwCZG1tZsYAxah2sy5BQAWfclQQ5mj+VMn611Eceq5ELVzZeHTIHEqJnNuUyh7V +DCeZeWjT+kwc/NnCn8F1lwVMvm6ZTQ37reyVYKZqkQRWjCst9aTFAlQl6hYLD+LR +cg/b5oOo2SiAtqELaBJDU3lX/zBqG38o0N1hIT364bj6+9vzngx0ce8TMj1y92MJ +YA4r6RUy7NwYc6sfxVjoBr30ARmqsXdYEZwu1DK37fikWCgmqiilBT/AIVjCdb5J +MO+6NhtP6wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCSu0r59BdG0uLmQ/ncLvJJ +vealSX6pbbZInPCCztGa7SJpfkbVNj3c/Fs0q5UH2SPYH/O/yqgp8IWNYNUuTlO3 +2IJWFovi6cHpetszLxBat75XDq3Spvw8mIGd8Lhw2B6RpR5Hy/kO/mXmnpH/1aty +xRJY6V5Tin/bsx3IwWKK/kcXzEtrCAnS2w2V4WTOk7y6WOGhsEfmwVc4MbvXQc/5 +yysvN41AUcWK94XJ2FZZc8ykUkHJ+TeRGq8wnl7l3E9d0wQw+ArL4toD4puFmxvH +qZV5n628d+ecNTbAhanX46A4xdfxhD0LvnURizAfu3N5snMmhjfgL5ukMwrGCwRo +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDf4h64XT8p5VnC +5XnjNnUPvjtP4QZSYYAmBLusGbNqBKTzu0VwWRUXPY9+H9vK117RiOhgR9v42KUs +TAJkbW1mxgDFqHazLkFABZ9yVBDmaP5UyfrXURx6rkQtXNl4dMgcSomc25TKHtUM +J5l5aNP6TBz82cKfwXWXBUy+bplNDfut7JVgpmqRBFaMKy31pMUCVCXqFgsP4tFy +D9vmg6jZKIC2oQtoEkNTeVf/MGobfyjQ3WEhPfrhuPr72/OeDHRx7xMyPXL3Ywlg +DivpFTLs3Bhzqx/FWOgGvfQBGaqxd1gRnC7UMrft+KRYKCaqKKUFP8AhWMJ1vkkw +77o2G0/rAgMBAAECggEBALVyswki8b1H137gsu+WRDu1Jqbvrkr4IH8vmNa7obBM +AVBUN8v9Nt22E+TZdy4nbP6PYh4eP0aodv22wL2Z/m+sDBYmGcXQuCtmIzrqrSPA +dlhLtpPpdhZrxG+rb8lzhHeBZZSOVkGVyX9nXLiMYDjclSXMazNE/MOgFPnF81MB +fQIf1g1FzJKbH5cPrl5hAnxoaRv3SvCCxsCTs51XvweKHmy5X4MlvRAYIj9IKutk +iF2EYTQSY6MSrJWP1buZm0JriJncvT3BdArihNK6OuraxRhc5TUCW7nIx4Pi+hwo +FODwbgtj5AtHmAdiL2AWJnaJoQVPEw6Oq1JBr9i3AGECgYEA9i+0D9dS1vsckQ2G +E1P1ItVkoZBjbSFV6lB8sBsx2hAl6bUIQtJvgoffDlCqkuCl2jagGcmHwlk4V8sc +O2HivNB9TcoQh5L4m8uN6uytLUXw4vUS23YI1LNImAuwf1refEuKVPM+Mn5Y/FMk +n0fK7IfuLgu13WZ6iYkBS+C7RNECgYEA6M7RK9mw/kcquK2bag96S0/0znsYtXtj +naNgsDOfIjuOHJJFinNrVbdW72zqJePXRPtpQ8/5xoyWjysKUqb7I94BXYGPMXzv +Z8fCzSDKTFBODpu4cMvgQk7c4D4ZgQSaWP1+wf9x8WglKowyUeh0CwJ307SYa3Mw +SYPdg2OTJ/sCgYEAsUkbF0lNy6kcIk0l32dXodUgWcTcBOu7rjh2AnAjD1EPrGSE +5XIbgVmNRQbMP2dtqF4sH0Xk8Q1FKNwIoa7VFHnjspAwJSGuzKrisWntMCws05P/ +F3HB3EKbpXrNiHkMvV+8534frUcVl+fb+KQ/uuQMnrYqKp0w4zh5aYYV9fECgYA0 +EAw3AjfSpZeoNSrMTSnMLdVRV7Xu3+knF6JHxUORJEBjo1Jp4+XdBWMrp++1CX7a +rl6cC6aQAGCrI7TrRuxi2QL1JkQfjRD85G9r8ClNZ6gNHEXi87TzHy/F9h09/QmH +XSk7uSSCGAg3u6KFLrbEv4iMj5aGcPwbdKHVAC+ogQKBgQC9vkSFhE2bVrIwul6Y +SEDap+YzbA0yfP8PXd0lX47uyOsd3hD0OoGwTRsmeJr1/4Yuf0Wub838ZxgP2dpP +qfW7+7OeNyTvS2avxygWvolVV+O5Yx13rE2Dsd7DQnIyGO9yRCWCPjpuOHFqEgMv +HzJX6j3SaubH52pePu5mrzMLcg== +-----END PRIVATE KEY----- diff --git a/test/e2e/settings/default_ssl_certificate.go b/test/e2e/settings/default_ssl_certificate.go index 1672ea2fa4..26ddf2eb76 100644 --- a/test/e2e/settings/default_ssl_certificate.go +++ b/test/e2e/settings/default_ssl_certificate.go @@ -17,6 +17,7 @@ limitations under the License. package settings import ( + "crypto/tls" "fmt" "strings" @@ -28,14 +29,18 @@ import ( "k8s.io/ingress-nginx/test/e2e/framework" ) -var _ = framework.IngressNginxDescribe("Default SSL Certificate", func() { +var _ = framework.IngressNginxDescribe("default-ssl-certificate", func() { f := framework.NewDefaultFramework("default-ssl-certificate") + var tlsConfig *tls.Config secretName := "my-custom-cert" + service := "http-svc" + port := 80 BeforeEach(func() { f.NewEchoDeploymentWithReplicas(1) - tlsConfig, err := framework.CreateIngressTLSSecret(f.KubeClientSet, + var err error + tlsConfig, err = framework.CreateIngressTLSSecret(f.KubeClientSet, []string{"*"}, secretName, f.Namespace) @@ -55,33 +60,39 @@ var _ = framework.IngressNginxDescribe("Default SSL Certificate", func() { framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig) }) - It("configures ssl certificate for catch-all ingress", func() { - ing := framework.NewSingleCatchAllIngress("catch-all", f.Namespace, "http-svc", 80, nil) + It("uses default ssl certificate for catch-all ingress", func() { + ing := framework.NewSingleCatchAllIngress("catch-all", f.Namespace, service, port, nil) f.EnsureIngress(ing) - sslCertificate := fmt.Sprintf("ssl_certificate /etc/ingress-controller/ssl/%s-%s.pem;", f.Namespace, secretName) - sslCertificateKey := fmt.Sprintf("ssl_certificate_key /etc/ingress-controller/ssl/%s-%s.pem;", f.Namespace, secretName) + By("making sure new ingress is deployed") + expectedConfig := fmt.Sprintf("set $proxy_upstream_name \"%v-%v-%v\";", f.Namespace, service, port) f.WaitForNginxServer("_", func(cfg string) bool { - return strings.Contains(cfg, sslCertificate) && strings.Contains(cfg, sslCertificateKey) + return strings.Contains(cfg, expectedConfig) }) + + By("making sure new ingress is responding") + + By("making sure the configured default ssl certificate is being used") + framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig) }) - It("configures ssl certificate for host based ingress with tls spec", func() { + It("uses default ssl certificate for host based ingress when configured certificate does not match host", func() { host := "foo" - ing := f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, "http-svc", 80, nil)) - tlsConfig, err := framework.CreateIngressTLSSecret(f.KubeClientSet, - ing.Spec.TLS[0].Hosts, + ing := f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, service, port, nil)) + _, err := framework.CreateIngressTLSSecret(f.KubeClientSet, + []string{"not.foo"}, ing.Spec.TLS[0].SecretName, ing.Namespace) Expect(err).NotTo(HaveOccurred()) - framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig) - - sslCertificate := fmt.Sprintf("ssl_certificate /etc/ingress-controller/ssl/%s-%s.pem;", f.Namespace, secretName) - sslCertificateKey := fmt.Sprintf("ssl_certificate_key /etc/ingress-controller/ssl/%s-%s.pem;", f.Namespace, secretName) + By("making sure new ingress is deployed") + expectedConfig := fmt.Sprintf("set $proxy_upstream_name \"%v-%v-%v\";", f.Namespace, service, port) f.WaitForNginxServer(host, func(cfg string) bool { - return strings.Contains(cfg, "server_name foo") && strings.Contains(cfg, sslCertificate) && strings.Contains(cfg, sslCertificateKey) + return strings.Contains(cfg, expectedConfig) }) + + By("making sure the configured default ssl certificate is being used") + framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig) }) })