From caa0677762f15242863262318da49c834659df4a Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Fri, 5 Aug 2022 13:30:21 -0600 Subject: [PATCH 1/2] Generate a default SSL server for select listener hostnames Problems: (1) If a valid HTTPS listener is configured but has no attached routes, a request to the listener's hostname will result in an SSL handshake error. (2) If a valid HTTPS listener with a catch-all hostname is configured, a request to any hostname that does not match an existing route will result in an SSL handshake error. After discussion, we decided that it doesn't make sense to reject the SSL handshake when there's a valid HTTPS listener configured for the hostname. The desired behavior is to return a 404. Solution: Generate an SSL server block for each listener described above that terminates the TLS connection and returns a 404. --- internal/nginx/config/generator.go | 29 ++-- internal/nginx/config/http.go | 15 +- internal/nginx/config/template.go | 7 +- internal/state/change_processor_test.go | 37 ++++- internal/state/configuration.go | 61 ++++++-- internal/state/configuration_test.go | 199 +++++++++++++++++++++--- internal/state/graph_test.go | 40 +++++ 7 files changed, 339 insertions(+), 49 deletions(-) diff --git a/internal/nginx/config/generator.go b/internal/nginx/config/generator.go index a3b405baa9..25f70a07aa 100644 --- a/internal/nginx/config/generator.go +++ b/internal/nginx/config/generator.go @@ -80,8 +80,22 @@ func generateDefaultHTTPServer() server { func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore) (server, Warnings) { warnings := newWarnings() - locs := make([]location, 0, len(virtualServer.PathRules)) // FIXME(pleshakov): expand with rule.Routes + s := server{ServerName: virtualServer.Hostname} + + if virtualServer.SSL != nil { + s.SSL = &ssl{ + Certificate: virtualServer.SSL.CertificatePath, + CertificateKey: virtualServer.SSL.CertificatePath, + } + } + + if virtualServer.PathRules == nil { + // generate default "/" 404 location + s.Locations = []location{{Path: "/", Return: &returnVal{Code: statusNotFound}}} + return s, warnings + } + locs := make([]location, 0, len(virtualServer.PathRules)) // FIXME(pleshakov): expand with rule.Routes for _, rule := range virtualServer.PathRules { matches := make([]httpMatch, 0, len(rule.MatchRules)) @@ -125,16 +139,9 @@ func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore locs = append(locs, pathLoc) } } - s := server{ - ServerName: virtualServer.Hostname, - Locations: locs, - } - if virtualServer.SSL != nil { - s.SSL = &ssl{ - Certificate: virtualServer.SSL.CertificatePath, - CertificateKey: virtualServer.SSL.CertificatePath, - } - } + + s.Locations = locs + return s, warnings } diff --git a/internal/nginx/config/http.go b/internal/nginx/config/http.go index 50d39b9025..11d2a1762e 100644 --- a/internal/nginx/config/http.go +++ b/internal/nginx/config/http.go @@ -5,21 +5,30 @@ type httpServers struct { } type server struct { - IsDefaultHTTP bool - IsDefaultSSL bool - ServerName string SSL *ssl + ServerName string Locations []location + IsDefaultHTTP bool + IsDefaultSSL bool } type location struct { + Return *returnVal Path string ProxyPass string HTTPMatchVar string Internal bool } +type returnVal struct { + Code statusCode +} + type ssl struct { Certificate string CertificateKey string } + +type statusCode int + +const statusNotFound statusCode = 404 diff --git a/internal/nginx/config/template.go b/internal/nginx/config/template.go index 843a0f5635..b67a162318 100644 --- a/internal/nginx/config/template.go +++ b/internal/nginx/config/template.go @@ -39,8 +39,10 @@ server { {{ if $l.Internal }} internal; {{ end }} - - proxy_set_header Host $host; + + {{ if $l.Return }} + return {{ $l.Return.Code }}; + {{ end }} {{ if $l.HTTPMatchVar }} set $http_matches {{ $l.HTTPMatchVar | printf "%q" }}; @@ -48,6 +50,7 @@ server { {{ end }} {{ if $l.ProxyPass }} + proxy_set_header Host $host; proxy_pass {{ $l.ProxyPass }}$request_uri; {{ end }} } diff --git a/internal/state/change_processor_test.go b/internal/state/change_processor_test.go index 6de1be50b6..bd64f31813 100644 --- a/internal/state/change_processor_test.go +++ b/internal/state/change_processor_test.go @@ -246,6 +246,10 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } @@ -333,6 +337,10 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } expectedStatuses := state.Statuses{ @@ -419,6 +427,10 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } expectedStatuses := state.Statuses{ @@ -505,6 +517,10 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } expectedStatuses := state.Statuses{ @@ -590,6 +606,10 @@ var _ = Describe("ChangeProcessor", func() { CertificatePath: certificatePath, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } expectedStatuses := state.Statuses{ @@ -669,6 +689,10 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } expectedStatuses := state.Statuses{ @@ -754,6 +778,10 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, }, } expectedStatuses := state.Statuses{ @@ -791,12 +819,17 @@ var _ = Describe("ChangeProcessor", func() { Expect(helpers.Diff(expectedStatuses, statuses)).To(BeEmpty()) }) - It("should return empty configuration and updated statuses after deleting the second HTTPRoute", func() { + It("should return configuration with default ssl server and updated statuses after deleting the second HTTPRoute", func() { processor.CaptureDeleteChange(&v1alpha2.HTTPRoute{}, types.NamespacedName{Namespace: "test", Name: "hr-2"}) expectedConf := state.Configuration{ HTTPServers: []state.VirtualServer{}, - SSLServers: []state.VirtualServer{}, + SSLServers: []state.VirtualServer{ + { + Hostname: "~^", + SSL: &state.SSL{CertificatePath: certificatePath}, + }, + }, } expectedStatuses := state.Statuses{ GatewayClassStatus: &state.GatewayClassStatus{ diff --git a/internal/state/configuration.go b/internal/state/configuration.go index 034909a9d5..9ce039c8c8 100644 --- a/internal/state/configuration.go +++ b/internal/state/configuration.go @@ -7,6 +7,8 @@ import ( "sigs.k8s.io/gateway-api/apis/v1alpha2" ) +const wildcardHostname = "~^" + // Configuration is an internal representation of Gateway configuration. // We can think of Configuration as an intermediate state between the Gateway API resources and the data plane (NGINX) // configuration. @@ -99,6 +101,7 @@ func (b *configBuilder) upsertListener(l *listener) { case v1alpha2.HTTPProtocolType: b.http.upsertListener(l) case v1alpha2.HTTPSProtocolType: + b.ssl.listeners = append(b.ssl.listeners, l) b.ssl.upsertListener(l) default: panic(fmt.Sprintf("listener protocol %s not supported", l.Source.Protocol)) @@ -107,25 +110,26 @@ func (b *configBuilder) upsertListener(l *listener) { func (b *configBuilder) build() Configuration { return Configuration{ - HTTPServers: b.http.build(), - SSLServers: b.ssl.build(), + HTTPServers: b.http.buildHTTP(), + SSLServers: b.ssl.buildSSL(), } } type virtualServerBuilder struct { rulesPerHost map[string]map[string]PathRule listenersForHost map[string]*listener + listeners []*listener } func newVirtualServerBuilder() *virtualServerBuilder { return &virtualServerBuilder{ rulesPerHost: make(map[string]map[string]PathRule), listenersForHost: make(map[string]*listener), + listeners: make([]*listener, 0), } } func (b *virtualServerBuilder) upsertListener(l *listener) { - for _, r := range l.Routes { var hostnames []string @@ -137,6 +141,7 @@ func (b *virtualServerBuilder) upsertListener(l *listener) { for _, h := range hostnames { b.listenersForHost[h] = l + if _, exist := b.rulesPerHost[h]; !exist { b.rulesPerHost[h] = make(map[string]PathRule) } @@ -144,6 +149,7 @@ func (b *virtualServerBuilder) upsertListener(l *listener) { for i, rule := range r.Source.Spec.Rules { for _, h := range hostnames { + for j, m := range rule.Matches { path := getPath(m.Path) @@ -165,8 +171,41 @@ func (b *virtualServerBuilder) upsertListener(l *listener) { } } -func (b *virtualServerBuilder) build() []VirtualServer { +func (b *virtualServerBuilder) buildSSL() []VirtualServer { + servers := make([]VirtualServer, 0, len(b.rulesPerHost)+len(b.listeners)) + + for _, l := range b.listeners { + hostname := getListenerHostname(l.Source.Hostname) + // generate a 404 ssl server block for listeners with no routes or listeners with wildcard (match-all) routes + // FIXME(kate-osborn): when we support regex hostnames (e.g. *.example.com) we will have to modify this check to catch regex hostnames. + if len(l.Routes) == 0 || hostname == wildcardHostname { + servers = append(servers, VirtualServer{ + Hostname: hostname, + SSL: &SSL{CertificatePath: l.SecretPath}, + }) + } + } + + servers = append(servers, b.build()...) + + sort.Slice(servers, func(i, j int) bool { + return servers[i].Hostname < servers[j].Hostname + }) + + return servers +} + +func (b *virtualServerBuilder) buildHTTP() []VirtualServer { + servers := b.build() + sort.Slice(servers, func(i, j int) bool { + return servers[i].Hostname < servers[j].Hostname + }) + + return servers +} + +func (b *virtualServerBuilder) build() []VirtualServer { servers := make([]VirtualServer, 0, len(b.rulesPerHost)) for h, rules := range b.rulesPerHost { @@ -198,14 +237,18 @@ func (b *virtualServerBuilder) build() []VirtualServer { servers = append(servers, s) } - // sort servers for predictable order - sort.Slice(servers, func(i, j int) bool { - return servers[i].Hostname < servers[j].Hostname - }) - return servers } +func getListenerHostname(h *v1alpha2.Hostname) string { + name := getHostname(h) + if name == "" { + return wildcardHostname + } + + return name +} + func getPath(path *v1alpha2.HTTPPathMatch) string { if path == nil || path.Value == nil || *path.Value == "" { return "/" diff --git a/internal/state/configuration_test.go b/internal/state/configuration_test.go index dde277d90f..cd412ac472 100644 --- a/internal/state/configuration_test.go +++ b/internal/state/configuration_test.go @@ -123,7 +123,17 @@ func TestBuildConfiguration(t *testing.T) { httpsRouteHR4 := &route{ Source: httpsHR4, ValidSectionNameRefs: map[string]struct{}{ - "listener-80-1": {}, + "listener-443-1": {}, + }, + InvalidSectionNameRefs: map[string]struct{}{}, + } + + httpsHR5 := createRoute("https-hr-5", "example.com", "listener-443-with-hostname", "/") + + httpsRouteHR5 := &route{ + Source: httpsHR5, + ValidSectionNameRefs: map[string]struct{}{ + "listener-443-with-hostname": {}, }, InvalidSectionNameRefs: map[string]struct{}{}, } @@ -151,6 +161,24 @@ func TestBuildConfiguration(t *testing.T) { }, }, } + hostname := v1alpha2.Hostname("example.com") + + listener443WithHostname := v1alpha2.Listener{ + Name: "listener-443-with-hostname", + Hostname: &hostname, + Port: 443, + Protocol: v1alpha2.HTTPSProtocolType, + TLS: &v1alpha2.GatewayTLSConfig{ + Mode: helpers.GetTLSModePointer(v1alpha2.TLSModeTerminate), + CertificateRefs: []*v1alpha2.SecretObjectReference{ + { + Kind: (*v1alpha2.Kind)(helpers.GetStringPointer("Secret")), + Name: "secret", + Namespace: (*v1alpha2.Namespace)(helpers.GetStringPointer("test")), + }, + }, + }, + } invalidListener := v1alpha2.Listener{ Name: "invalid-listener", @@ -201,8 +229,34 @@ func TestBuildConfiguration(t *testing.T) { Routes: map[types.NamespacedName]*route{}, AcceptedHostnames: map[string]struct{}{}, }, + }, + }, + Routes: map[types.NamespacedName]*route{}, + }, + expected: Configuration{ + HTTPServers: []VirtualServer{}, + SSLServers: []VirtualServer{}, + }, + msg: "http listener with no routes", + }, + { + graph: &graph{ + GatewayClass: &gatewayClass{ + Source: &v1alpha2.GatewayClass{}, + Valid: true, + }, + Gateway: &gateway{ + Source: &v1alpha2.Gateway{}, + Listeners: map[string]*listener{ "listener-443-1": { - Source: listener443, + Source: listener443, // nil hostname + Valid: true, + Routes: map[types.NamespacedName]*route{}, + AcceptedHostnames: map[string]struct{}{}, + SecretPath: secretPath, + }, + "listener-443-with-hostname": { + Source: listener443WithHostname, // non-nil hostname Valid: true, Routes: map[types.NamespacedName]*route{}, AcceptedHostnames: map[string]struct{}{}, @@ -214,9 +268,18 @@ func TestBuildConfiguration(t *testing.T) { }, expected: Configuration{ HTTPServers: []VirtualServer{}, - SSLServers: []VirtualServer{}, + SSLServers: []VirtualServer{ + { + Hostname: string(hostname), + SSL: &SSL{CertificatePath: secretPath}, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{CertificatePath: secretPath}, + }, + }, }, - msg: "http and https listeners with no routes", + msg: "https listeners with no routes", }, { graph: &graph{ @@ -274,26 +337,11 @@ func TestBuildConfiguration(t *testing.T) { "bar.example.com": {}, }, }, - "listener-443-1": { - Source: listener443, - Valid: true, - SecretPath: secretPath, - Routes: map[types.NamespacedName]*route{ - {Namespace: "test", Name: "https-hr-1"}: httpsRouteHR1, - {Namespace: "test", Name: "https-hr-2"}: httpsRouteHR2, - }, - AcceptedHostnames: map[string]struct{}{ - "foo.example.com": {}, - "bar.example.com": {}, - }, - }, }, }, Routes: map[types.NamespacedName]*route{ - {Namespace: "test", Name: "hr-1"}: routeHR1, - {Namespace: "test", Name: "hr-2"}: routeHR2, - {Namespace: "test", Name: "https-hr-1"}: httpsRouteHR1, - {Namespace: "test", Name: "https-hr-2"}: httpsRouteHR2, + {Namespace: "test", Name: "hr-1"}: routeHR1, + {Namespace: "test", Name: "hr-2"}: routeHR2, }, }, expected: Configuration{ @@ -329,6 +377,53 @@ func TestBuildConfiguration(t *testing.T) { }, }, }, + SSLServers: []VirtualServer{}, + }, + msg: "one http listener with two routes for different hostnames", + }, + { + graph: &graph{ + GatewayClass: &gatewayClass{ + Source: &v1alpha2.GatewayClass{}, + Valid: true, + }, + Gateway: &gateway{ + Source: &v1alpha2.Gateway{}, + Listeners: map[string]*listener{ + "listener-443-1": { + Source: listener443, + Valid: true, + SecretPath: secretPath, + Routes: map[types.NamespacedName]*route{ + {Namespace: "test", Name: "https-hr-1"}: httpsRouteHR1, + {Namespace: "test", Name: "https-hr-2"}: httpsRouteHR2, + }, + AcceptedHostnames: map[string]struct{}{ + "foo.example.com": {}, + "bar.example.com": {}, + }, + }, + "listener-443-with-hostname": { + Source: listener443WithHostname, + Valid: true, + SecretPath: secretPath, + Routes: map[types.NamespacedName]*route{ + {Namespace: "test", Name: "https-hr-5"}: httpsRouteHR5, + }, + AcceptedHostnames: map[string]struct{}{ + "example.com": {}, + }, + }, + }, + }, + Routes: map[types.NamespacedName]*route{ + {Namespace: "test", Name: "https-hr-1"}: httpsRouteHR1, + {Namespace: "test", Name: "https-hr-2"}: httpsRouteHR2, + {Namespace: "test", Name: "https-hr-5"}: httpsRouteHR5, + }, + }, + expected: Configuration{ + HTTPServers: []VirtualServer{}, SSLServers: []VirtualServer{ { Hostname: "bar.example.com", @@ -348,6 +443,24 @@ func TestBuildConfiguration(t *testing.T) { CertificatePath: secretPath, }, }, + { + Hostname: "example.com", + PathRules: []PathRule{ + { + Path: "/", + MatchRules: []MatchRule{ + { + MatchIdx: 0, + RuleIdx: 0, + Source: httpsHR5, + }, + }, + }, + }, + SSL: &SSL{ + CertificatePath: secretPath, + }, + }, { Hostname: "foo.example.com", PathRules: []PathRule{ @@ -366,9 +479,13 @@ func TestBuildConfiguration(t *testing.T) { CertificatePath: secretPath, }, }, + { + Hostname: wildcardHostname, + SSL: &SSL{CertificatePath: secretPath}, + }, }, }, - msg: "one http and one https listener each with two routes for different hostnames", + msg: "two https listeners each with routes for different hostnames", }, { graph: &graph{ @@ -498,6 +615,10 @@ func TestBuildConfiguration(t *testing.T) { }, }, }, + { + Hostname: wildcardHostname, + SSL: &SSL{CertificatePath: secretPath}, + }, }, }, msg: "one http and one https listener with two routes with the same hostname with and without collisions", @@ -679,3 +800,37 @@ func TestMatchRuleGetMatch(t *testing.T) { } } } + +func TestGetListenerHostname(t *testing.T) { + var emptyHostname v1alpha2.Hostname + var hostname v1alpha2.Hostname = "example.com" + + tests := []struct { + hostname *v1alpha2.Hostname + expected string + msg string + }{ + { + hostname: nil, + expected: wildcardHostname, + msg: "nil hostname", + }, + { + hostname: &emptyHostname, + expected: wildcardHostname, + msg: "empty hostname", + }, + { + hostname: &hostname, + expected: string(hostname), + msg: "normal hostname", + }, + } + + for _, test := range tests { + result := getListenerHostname(test.hostname) + if result != test.expected { + t.Errorf("getListenerHostname() returned %q but expected %q for the case of %q", result, test.expected, test.msg) + } + } +} diff --git a/internal/state/graph_test.go b/internal/state/graph_test.go index fe4922ea45..5790bc6cbf 100644 --- a/internal/state/graph_test.go +++ b/internal/state/graph_test.go @@ -411,6 +411,17 @@ func TestBuildListeners(t *testing.T) { }, }, } + + tlsConfigInvalidSecret := &v1alpha2.GatewayTLSConfig{ + Mode: helpers.GetTLSModePointer(v1alpha2.TLSModeTerminate), + CertificateRefs: []*v1alpha2.SecretObjectReference{ + { + Kind: (*v1alpha2.Kind)(helpers.GetStringPointer("Secret")), + Name: "does-not-exist", + Namespace: (*v1alpha2.Namespace)(helpers.GetStringPointer("test")), + }, + }, + } // https listeners listener4431 := v1alpha2.Listener{ Name: "listener-443-1", @@ -440,6 +451,13 @@ func TestBuildListeners(t *testing.T) { TLS: nil, // invalid https listener; missing tls config Protocol: v1alpha2.HTTPSProtocolType, } + listener4435 := v1alpha2.Listener{ + Name: "listener-443-5", + Hostname: (*v1alpha2.Hostname)(helpers.GetStringPointer("foo.example.com")), + Port: 443, + TLS: tlsConfigInvalidSecret, // invalid https listener; secret does not exist + Protocol: v1alpha2.HTTPSProtocolType, + } tests := []struct { gateway *v1alpha2.Gateway expected map[string]*listener @@ -535,6 +553,28 @@ func TestBuildListeners(t *testing.T) { }, msg: "invalid https listener (tls config missing)", }, + { + gateway: &v1alpha2.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + }, + Spec: v1alpha2.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1alpha2.Listener{ + listener4435, + }, + }, + }, + expected: map[string]*listener{ + "listener-443-5": { + Source: listener4435, + Valid: false, + Routes: map[types.NamespacedName]*route{}, + AcceptedHostnames: map[string]struct{}{}, + }, + }, + msg: "invalid https listener (secret does not exist)", + }, { gateway: &v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ From afb4e45a06cd550850d54b864499d31079975e87 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 9 Aug 2022 13:01:13 -0600 Subject: [PATCH 2/2] Rework virtual server builder --- internal/nginx/config/generator.go | 2 +- internal/state/configuration.go | 71 +++++++++++++----------------- 2 files changed, 31 insertions(+), 42 deletions(-) diff --git a/internal/nginx/config/generator.go b/internal/nginx/config/generator.go index 25f70a07aa..800a0ed134 100644 --- a/internal/nginx/config/generator.go +++ b/internal/nginx/config/generator.go @@ -89,7 +89,7 @@ func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore } } - if virtualServer.PathRules == nil { + if len(virtualServer.PathRules) == 0 { // generate default "/" 404 location s.Locations = []location{{Path: "/", Return: &returnVal{Code: statusNotFound}}} return s, warnings diff --git a/internal/state/configuration.go b/internal/state/configuration.go index 9ce039c8c8..47cb2e8ec6 100644 --- a/internal/state/configuration.go +++ b/internal/state/configuration.go @@ -91,8 +91,8 @@ type configBuilder struct { func newConfigBuilder() *configBuilder { return &configBuilder{ - http: newVirtualServerBuilder(), - ssl: newVirtualServerBuilder(), + http: newVirtualServerBuilder(v1alpha2.HTTPProtocolType), + ssl: newVirtualServerBuilder(v1alpha2.HTTPSProtocolType), } } @@ -101,7 +101,6 @@ func (b *configBuilder) upsertListener(l *listener) { case v1alpha2.HTTPProtocolType: b.http.upsertListener(l) case v1alpha2.HTTPSProtocolType: - b.ssl.listeners = append(b.ssl.listeners, l) b.ssl.upsertListener(l) default: panic(fmt.Sprintf("listener protocol %s not supported", l.Source.Protocol)) @@ -110,19 +109,21 @@ func (b *configBuilder) upsertListener(l *listener) { func (b *configBuilder) build() Configuration { return Configuration{ - HTTPServers: b.http.buildHTTP(), - SSLServers: b.ssl.buildSSL(), + HTTPServers: b.http.build(), + SSLServers: b.ssl.build(), } } type virtualServerBuilder struct { + protocolType v1alpha2.ProtocolType rulesPerHost map[string]map[string]PathRule listenersForHost map[string]*listener listeners []*listener } -func newVirtualServerBuilder() *virtualServerBuilder { +func newVirtualServerBuilder(protocolType v1alpha2.ProtocolType) *virtualServerBuilder { return &virtualServerBuilder{ + protocolType: protocolType, rulesPerHost: make(map[string]map[string]PathRule), listenersForHost: make(map[string]*listener), listeners: make([]*listener, 0), @@ -130,6 +131,11 @@ func newVirtualServerBuilder() *virtualServerBuilder { } func (b *virtualServerBuilder) upsertListener(l *listener) { + + if b.protocolType == v1alpha2.HTTPSProtocolType { + b.listeners = append(b.listeners, l) + } + for _, r := range l.Routes { var hostnames []string @@ -171,42 +177,9 @@ func (b *virtualServerBuilder) upsertListener(l *listener) { } } -func (b *virtualServerBuilder) buildSSL() []VirtualServer { - servers := make([]VirtualServer, 0, len(b.rulesPerHost)+len(b.listeners)) - - for _, l := range b.listeners { - hostname := getListenerHostname(l.Source.Hostname) - // generate a 404 ssl server block for listeners with no routes or listeners with wildcard (match-all) routes - // FIXME(kate-osborn): when we support regex hostnames (e.g. *.example.com) we will have to modify this check to catch regex hostnames. - if len(l.Routes) == 0 || hostname == wildcardHostname { - servers = append(servers, VirtualServer{ - Hostname: hostname, - SSL: &SSL{CertificatePath: l.SecretPath}, - }) - } - } - - servers = append(servers, b.build()...) - - sort.Slice(servers, func(i, j int) bool { - return servers[i].Hostname < servers[j].Hostname - }) - - return servers -} - -func (b *virtualServerBuilder) buildHTTP() []VirtualServer { - servers := b.build() - - sort.Slice(servers, func(i, j int) bool { - return servers[i].Hostname < servers[j].Hostname - }) - - return servers -} - func (b *virtualServerBuilder) build() []VirtualServer { - servers := make([]VirtualServer, 0, len(b.rulesPerHost)) + + servers := make([]VirtualServer, 0, len(b.rulesPerHost)+len(b.listeners)) for h, rules := range b.rulesPerHost { s := VirtualServer{ @@ -237,6 +210,22 @@ func (b *virtualServerBuilder) build() []VirtualServer { servers = append(servers, s) } + for _, l := range b.listeners { + hostname := getListenerHostname(l.Source.Hostname) + // generate a 404 ssl server block for listeners with no routes or listeners with wildcard (match-all) routes + // FIXME(kate-osborn): when we support regex hostnames (e.g. *.example.com) we will have to modify this check to catch regex hostnames. + if len(l.Routes) == 0 || hostname == wildcardHostname { + servers = append(servers, VirtualServer{ + Hostname: hostname, + SSL: &SSL{CertificatePath: l.SecretPath}, + }) + } + } + + sort.Slice(servers, func(i, j int) bool { + return servers[i].Hostname < servers[j].Hostname + }) + return servers }