Skip to content

Commit f9bf557

Browse files
authored
Generate a default SSL server for select HTTPS listener hostname (#173)
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.
1 parent 8aee103 commit f9bf557

File tree

7 files changed

+324
-45
lines changed

7 files changed

+324
-45
lines changed

internal/nginx/config/generator.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,22 @@ func generateDefaultHTTPServer() server {
8080
func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore) (server, Warnings) {
8181
warnings := newWarnings()
8282

83-
locs := make([]location, 0, len(virtualServer.PathRules)) // FIXME(pleshakov): expand with rule.Routes
83+
s := server{ServerName: virtualServer.Hostname}
84+
85+
if virtualServer.SSL != nil {
86+
s.SSL = &ssl{
87+
Certificate: virtualServer.SSL.CertificatePath,
88+
CertificateKey: virtualServer.SSL.CertificatePath,
89+
}
90+
}
91+
92+
if len(virtualServer.PathRules) == 0 {
93+
// generate default "/" 404 location
94+
s.Locations = []location{{Path: "/", Return: &returnVal{Code: statusNotFound}}}
95+
return s, warnings
96+
}
8497

98+
locs := make([]location, 0, len(virtualServer.PathRules)) // FIXME(pleshakov): expand with rule.Routes
8599
for _, rule := range virtualServer.PathRules {
86100
matches := make([]httpMatch, 0, len(rule.MatchRules))
87101

@@ -125,16 +139,9 @@ func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore
125139
locs = append(locs, pathLoc)
126140
}
127141
}
128-
s := server{
129-
ServerName: virtualServer.Hostname,
130-
Locations: locs,
131-
}
132-
if virtualServer.SSL != nil {
133-
s.SSL = &ssl{
134-
Certificate: virtualServer.SSL.CertificatePath,
135-
CertificateKey: virtualServer.SSL.CertificatePath,
136-
}
137-
}
142+
143+
s.Locations = locs
144+
138145
return s, warnings
139146
}
140147

internal/nginx/config/http.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,30 @@ type httpServers struct {
55
}
66

77
type server struct {
8-
IsDefaultHTTP bool
9-
IsDefaultSSL bool
10-
ServerName string
118
SSL *ssl
9+
ServerName string
1210
Locations []location
11+
IsDefaultHTTP bool
12+
IsDefaultSSL bool
1313
}
1414

1515
type location struct {
16+
Return *returnVal
1617
Path string
1718
ProxyPass string
1819
HTTPMatchVar string
1920
Internal bool
2021
}
2122

23+
type returnVal struct {
24+
Code statusCode
25+
}
26+
2227
type ssl struct {
2328
Certificate string
2429
CertificateKey string
2530
}
31+
32+
type statusCode int
33+
34+
const statusNotFound statusCode = 404

internal/nginx/config/template.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,18 @@ server {
3939
{{ if $l.Internal }}
4040
internal;
4141
{{ end }}
42-
43-
proxy_set_header Host $host;
42+
43+
{{ if $l.Return }}
44+
return {{ $l.Return.Code }};
45+
{{ end }}
4446
4547
{{ if $l.HTTPMatchVar }}
4648
set $http_matches {{ $l.HTTPMatchVar | printf "%q" }};
4749
js_content httpmatches.redirect;
4850
{{ end }}
4951
5052
{{ if $l.ProxyPass }}
53+
proxy_set_header Host $host;
5154
proxy_pass {{ $l.ProxyPass }}$request_uri;
5255
{{ end }}
5356
}

internal/state/change_processor_test.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ var _ = Describe("ChangeProcessor", func() {
246246
},
247247
},
248248
},
249+
{
250+
Hostname: "~^",
251+
SSL: &state.SSL{CertificatePath: certificatePath},
252+
},
249253
},
250254
}
251255

@@ -333,6 +337,10 @@ var _ = Describe("ChangeProcessor", func() {
333337
},
334338
},
335339
},
340+
{
341+
Hostname: "~^",
342+
SSL: &state.SSL{CertificatePath: certificatePath},
343+
},
336344
},
337345
}
338346
expectedStatuses := state.Statuses{
@@ -419,6 +427,10 @@ var _ = Describe("ChangeProcessor", func() {
419427
},
420428
},
421429
},
430+
{
431+
Hostname: "~^",
432+
SSL: &state.SSL{CertificatePath: certificatePath},
433+
},
422434
},
423435
}
424436
expectedStatuses := state.Statuses{
@@ -505,6 +517,10 @@ var _ = Describe("ChangeProcessor", func() {
505517
},
506518
},
507519
},
520+
{
521+
Hostname: "~^",
522+
SSL: &state.SSL{CertificatePath: certificatePath},
523+
},
508524
},
509525
}
510526
expectedStatuses := state.Statuses{
@@ -590,6 +606,10 @@ var _ = Describe("ChangeProcessor", func() {
590606
CertificatePath: certificatePath,
591607
},
592608
},
609+
{
610+
Hostname: "~^",
611+
SSL: &state.SSL{CertificatePath: certificatePath},
612+
},
593613
},
594614
}
595615
expectedStatuses := state.Statuses{
@@ -669,6 +689,10 @@ var _ = Describe("ChangeProcessor", func() {
669689
},
670690
},
671691
},
692+
{
693+
Hostname: "~^",
694+
SSL: &state.SSL{CertificatePath: certificatePath},
695+
},
672696
},
673697
}
674698
expectedStatuses := state.Statuses{
@@ -754,6 +778,10 @@ var _ = Describe("ChangeProcessor", func() {
754778
},
755779
},
756780
},
781+
{
782+
Hostname: "~^",
783+
SSL: &state.SSL{CertificatePath: certificatePath},
784+
},
757785
},
758786
}
759787
expectedStatuses := state.Statuses{
@@ -791,12 +819,17 @@ var _ = Describe("ChangeProcessor", func() {
791819
Expect(helpers.Diff(expectedStatuses, statuses)).To(BeEmpty())
792820
})
793821

794-
It("should return empty configuration and updated statuses after deleting the second HTTPRoute", func() {
822+
It("should return configuration with default ssl server and updated statuses after deleting the second HTTPRoute", func() {
795823
processor.CaptureDeleteChange(&v1alpha2.HTTPRoute{}, types.NamespacedName{Namespace: "test", Name: "hr-2"})
796824

797825
expectedConf := state.Configuration{
798826
HTTPServers: []state.VirtualServer{},
799-
SSLServers: []state.VirtualServer{},
827+
SSLServers: []state.VirtualServer{
828+
{
829+
Hostname: "~^",
830+
SSL: &state.SSL{CertificatePath: certificatePath},
831+
},
832+
},
800833
}
801834
expectedStatuses := state.Statuses{
802835
GatewayClassStatus: &state.GatewayClassStatus{

internal/state/configuration.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"sigs.k8s.io/gateway-api/apis/v1alpha2"
88
)
99

10+
const wildcardHostname = "~^"
11+
1012
// Configuration is an internal representation of Gateway configuration.
1113
// We can think of Configuration as an intermediate state between the Gateway API resources and the data plane (NGINX)
1214
// configuration.
@@ -89,8 +91,8 @@ type configBuilder struct {
8991

9092
func newConfigBuilder() *configBuilder {
9193
return &configBuilder{
92-
http: newVirtualServerBuilder(),
93-
ssl: newVirtualServerBuilder(),
94+
http: newVirtualServerBuilder(v1alpha2.HTTPProtocolType),
95+
ssl: newVirtualServerBuilder(v1alpha2.HTTPSProtocolType),
9496
}
9597
}
9698

@@ -113,19 +115,27 @@ func (b *configBuilder) build() Configuration {
113115
}
114116

115117
type virtualServerBuilder struct {
118+
protocolType v1alpha2.ProtocolType
116119
rulesPerHost map[string]map[string]PathRule
117120
listenersForHost map[string]*listener
121+
listeners []*listener
118122
}
119123

120-
func newVirtualServerBuilder() *virtualServerBuilder {
124+
func newVirtualServerBuilder(protocolType v1alpha2.ProtocolType) *virtualServerBuilder {
121125
return &virtualServerBuilder{
126+
protocolType: protocolType,
122127
rulesPerHost: make(map[string]map[string]PathRule),
123128
listenersForHost: make(map[string]*listener),
129+
listeners: make([]*listener, 0),
124130
}
125131
}
126132

127133
func (b *virtualServerBuilder) upsertListener(l *listener) {
128134

135+
if b.protocolType == v1alpha2.HTTPSProtocolType {
136+
b.listeners = append(b.listeners, l)
137+
}
138+
129139
for _, r := range l.Routes {
130140
var hostnames []string
131141

@@ -137,13 +147,15 @@ func (b *virtualServerBuilder) upsertListener(l *listener) {
137147

138148
for _, h := range hostnames {
139149
b.listenersForHost[h] = l
150+
140151
if _, exist := b.rulesPerHost[h]; !exist {
141152
b.rulesPerHost[h] = make(map[string]PathRule)
142153
}
143154
}
144155

145156
for i, rule := range r.Source.Spec.Rules {
146157
for _, h := range hostnames {
158+
147159
for j, m := range rule.Matches {
148160
path := getPath(m.Path)
149161

@@ -167,7 +179,7 @@ func (b *virtualServerBuilder) upsertListener(l *listener) {
167179

168180
func (b *virtualServerBuilder) build() []VirtualServer {
169181

170-
servers := make([]VirtualServer, 0, len(b.rulesPerHost))
182+
servers := make([]VirtualServer, 0, len(b.rulesPerHost)+len(b.listeners))
171183

172184
for h, rules := range b.rulesPerHost {
173185
s := VirtualServer{
@@ -198,14 +210,34 @@ func (b *virtualServerBuilder) build() []VirtualServer {
198210
servers = append(servers, s)
199211
}
200212

201-
// sort servers for predictable order
213+
for _, l := range b.listeners {
214+
hostname := getListenerHostname(l.Source.Hostname)
215+
// generate a 404 ssl server block for listeners with no routes or listeners with wildcard (match-all) routes
216+
// FIXME(kate-osborn): when we support regex hostnames (e.g. *.example.com) we will have to modify this check to catch regex hostnames.
217+
if len(l.Routes) == 0 || hostname == wildcardHostname {
218+
servers = append(servers, VirtualServer{
219+
Hostname: hostname,
220+
SSL: &SSL{CertificatePath: l.SecretPath},
221+
})
222+
}
223+
}
224+
202225
sort.Slice(servers, func(i, j int) bool {
203226
return servers[i].Hostname < servers[j].Hostname
204227
})
205228

206229
return servers
207230
}
208231

232+
func getListenerHostname(h *v1alpha2.Hostname) string {
233+
name := getHostname(h)
234+
if name == "" {
235+
return wildcardHostname
236+
}
237+
238+
return name
239+
}
240+
209241
func getPath(path *v1alpha2.HTTPPathMatch) string {
210242
if path == nil || path.Value == nil || *path.Value == "" {
211243
return "/"

0 commit comments

Comments
 (0)