Skip to content

Commit d76b1c4

Browse files
committed
Update nginx template for TLS passthrough
Problem: nginx configuration templates didn't support TLS passthrough Solution: I added a template setup fro stream servers
1 parent 3ef5ac7 commit d76b1c4

File tree

14 files changed

+385
-16
lines changed

14 files changed

+385
-16
lines changed

charts/nginx-gateway-fabric/templates/deployment.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ spec:
118118
volumeMounts:
119119
- name: nginx-conf
120120
mountPath: /etc/nginx/conf.d
121+
- name: nginx-stream-conf
122+
mountPath: /etc/nginx/stream-conf.d
121123
- name: module-includes
122124
mountPath: /etc/nginx/module-includes
123125
- name: nginx-secrets
@@ -153,6 +155,8 @@ spec:
153155
volumeMounts:
154156
- name: nginx-conf
155157
mountPath: /etc/nginx/conf.d
158+
- name: nginx-stream-conf
159+
mountPath: /etc/nginx/stream-conf.d
156160
- name: module-includes
157161
mountPath: /etc/nginx/module-includes
158162
- name: nginx-secrets
@@ -189,6 +193,8 @@ spec:
189193
volumes:
190194
- name: nginx-conf
191195
emptyDir: {}
196+
- name: nginx-stream-conf
197+
emptyDir: {}
192198
- name: module-includes
193199
emptyDir: {}
194200
- name: nginx-secrets

internal/mode/static/nginx/conf/nginx.conf

+4
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ http {
3636
}
3737
}
3838
}
39+
40+
stream {
41+
include /etc/nginx/stream-conf.d/*.conf;
42+
}

internal/mode/static/nginx/config/base_http_config_template.go

+16
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,20 @@ package config
22

33
const baseHTTPTemplateText = `
44
{{- if .HTTP2 }}http2 on;{{ end }}
5+
6+
# Set $gw_api_compliant_host variable to the value of $http_host unless $http_host is empty, then set it to the value
7+
# of $host. We prefer $http_host because it contains the original value of the host header, which is required by the
8+
# Gateway API. However, in an HTTP/1.0 request, it's possible that $http_host can be empty. In this case, we will use
9+
# the value of $host. See http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host.
10+
map $http_host $gw_api_compliant_host {
11+
'' $host;
12+
default $http_host;
13+
}
14+
15+
# Set $connection_header variable to upgrade when the $http_upgrade header is set, otherwise, set it to close. This
16+
# allows support for websocket connections. See https://nginx.org/en/docs/http/websocket.html.
17+
map $http_upgrade $connection_upgrade {
18+
default upgrade;
19+
'' close;
20+
}
521
`

internal/mode/static/nginx/config/generator.go

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const (
1717
// httpFolder is the folder where NGINX HTTP configuration files are stored.
1818
httpFolder = configFolder + "/conf.d"
1919

20+
// streamFolder is the folder where NGINX HTTP configuration files are stored.
21+
streamFolder = configFolder + "/stream-conf.d"
22+
2023
// modulesIncludesFolder is the folder where the included "load_module" file is stored.
2124
modulesIncludesFolder = configFolder + "/module-includes"
2225

@@ -29,6 +32,9 @@ const (
2932
// httpConfigFile is the path to the configuration file with HTTP configuration.
3033
httpConfigFile = httpFolder + "/http.conf"
3134

35+
// streamConfigFile is the path to the configuration file with Stream configuration.
36+
streamConfigFile = streamFolder + "/stream.conf"
37+
3238
// configVersionFile is the path to the config version configuration file.
3339
configVersionFile = httpFolder + "/config-version.conf"
3440

@@ -157,6 +163,9 @@ func (g GeneratorImpl) getExecuteFuncs() []executeFunc {
157163
executeSplitClients,
158164
executeMaps,
159165
executeTelemetry,
166+
executeStreamServers,
167+
g.executeStreamUpstreams,
168+
executeStreamMaps,
160169
}
161170
}
162171

internal/mode/static/nginx/config/maps.go

+43
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"strings"
56
gotemplate "text/template"
67

@@ -21,6 +22,48 @@ func executeMaps(conf dataplane.Configuration) []executeResult {
2122
return []executeResult{result}
2223
}
2324

25+
func executeStreamMaps(conf dataplane.Configuration) []executeResult {
26+
maps := createStreamMaps(conf)
27+
28+
result := executeResult{
29+
dest: streamConfigFile,
30+
data: helpers.MustExecuteTemplate(mapsTemplate, maps),
31+
}
32+
33+
return []executeResult{result}
34+
}
35+
36+
func createStreamMaps(conf dataplane.Configuration) []*http.Map {
37+
var maps []*http.Map
38+
portsToMap := make(map[int32]*http.Map)
39+
40+
for _, t := range conf.TLSServers {
41+
streamMap, ok := portsToMap[t.Port]
42+
43+
if !ok {
44+
m := http.Map{
45+
Source: "$ssl_preread_server_name",
46+
Variable: "$dest" + fmt.Sprint(t.Port),
47+
Parameters: []http.MapParameter{
48+
{
49+
Value: t.Hostname,
50+
Result: "unix:/var/lib/nginx/" + t.Hostname + fmt.Sprint(t.Port) + ".sock",
51+
},
52+
},
53+
}
54+
maps = append(maps, &m)
55+
portsToMap[t.Port] = &m
56+
} else {
57+
streamMap.Parameters = append(streamMap.Parameters, http.MapParameter{
58+
Value: t.Hostname,
59+
Result: "unix:/var/lib/nginx/" + t.Hostname + fmt.Sprint(t.Port) + ".sock",
60+
})
61+
}
62+
}
63+
64+
return maps
65+
}
66+
2467
func buildAddHeaderMaps(servers []dataplane.VirtualServer) []http.Map {
2568
addHeaderNames := make(map[string]struct{})
2669

internal/mode/static/nginx/config/maps_template.go

-16
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,4 @@ map {{ $m.Source }} {{ $m.Variable }} {
88
{{ end }}
99
}
1010
{{- end }}
11-
12-
# Set $gw_api_compliant_host variable to the value of $http_host unless $http_host is empty, then set it to the value
13-
# of $host. We prefer $http_host because it contains the original value of the host header, which is required by the
14-
# Gateway API. However, in an HTTP/1.0 request, it's possible that $http_host can be empty. In this case, we will use
15-
# the value of $host. See http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host.
16-
map $http_host $gw_api_compliant_host {
17-
'' $host;
18-
default $http_host;
19-
}
20-
21-
# Set $connection_header variable to upgrade when the $http_upgrade header is set, otherwise, set it to close. This
22-
# allows support for websocket connections. See https://nginx.org/en/docs/http/websocket.html.
23-
map $http_upgrade $connection_upgrade {
24-
default upgrade;
25-
'' close;
26-
}
2711
`

internal/mode/static/nginx/config/maps_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,85 @@ func TestBuildAddHeaderMaps(t *testing.T) {
189189

190190
g.Expect(maps).To(ConsistOf(expectedMap))
191191
}
192+
193+
func TestExecuteStreamMaps(t *testing.T) {
194+
g := NewWithT(t)
195+
conf := dataplane.Configuration{
196+
TLSServers: []dataplane.Layer4Server{
197+
{
198+
Hostname: "example.com",
199+
Port: 8081,
200+
UpstreamName: "backend1",
201+
},
202+
{
203+
Hostname: "example.com",
204+
Port: 8080,
205+
UpstreamName: "backend1",
206+
},
207+
{
208+
Hostname: "cafe.example.com",
209+
Port: 8080,
210+
UpstreamName: "backend2",
211+
},
212+
},
213+
}
214+
215+
expSubStrings := map[string]int{
216+
"example.com unix:/var/lib/nginx/example.com8081.sock;": 1,
217+
"example.com unix:/var/lib/nginx/example.com8080.sock;": 1,
218+
"cafe.example.com unix:/var/lib/nginx/cafe.example.com8080.sock;": 1,
219+
}
220+
221+
type assertion func(g *WithT, data string)
222+
223+
expectedResults := map[string]assertion{
224+
streamConfigFile: func(g *WithT, data string) {
225+
for expSubStr, expCount := range expSubStrings {
226+
g.Expect(strings.Count(data, expSubStr)).To(Equal(expCount))
227+
}
228+
},
229+
}
230+
231+
results := executeStreamMaps(conf)
232+
g.Expect(results).To(HaveLen(len(expectedResults)))
233+
234+
for _, res := range results {
235+
g.Expect(expectedResults).To(HaveKey(res.dest), "executeStreamServers returned unexpected result destination")
236+
237+
assertData := expectedResults[res.dest]
238+
assertData(g, string(res.data))
239+
}
240+
}
241+
242+
func TestCreateStreamMaps(t *testing.T) {
243+
g := NewWithT(t)
244+
conf := dataplane.Configuration{
245+
TLSServers: []dataplane.Layer4Server{
246+
{
247+
Hostname: "example.com",
248+
Port: 8081,
249+
UpstreamName: "backend1",
250+
},
251+
{
252+
Hostname: "example.com",
253+
Port: 8080,
254+
UpstreamName: "backend1",
255+
},
256+
{
257+
Hostname: "cafe.example.com",
258+
Port: 8080,
259+
UpstreamName: "backend2",
260+
},
261+
},
262+
}
263+
264+
maps := createStreamMaps(conf)
265+
g.Expect(maps).To(HaveLen(2))
266+
267+
g.Expect(maps[0].Parameters).To(HaveLen(1))
268+
g.Expect(maps[1].Parameters).To(HaveLen(2))
269+
270+
g.Expect(maps[0].Parameters[0].Result).To(Equal("unix:/var/lib/nginx/example.com8081.sock"))
271+
g.Expect(maps[1].Parameters[0].Result).To(Equal("unix:/var/lib/nginx/example.com8080.sock"))
272+
g.Expect(maps[0].Parameters[0].Result).To(Equal("unix:/var/lib/nginx/cafe.example.com8080.sock"))
273+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package stream
2+
3+
// Server holds all configuration for a stream server.
4+
type Server struct {
5+
Listen string
6+
Destination string
7+
SSLPreread bool
8+
ProxyPass bool
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
gotemplate "text/template"
6+
7+
"github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers"
8+
"github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/stream"
9+
"github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane"
10+
)
11+
12+
var streamServersTemplate = gotemplate.Must(gotemplate.New("servers").Parse(streamServersTemplateText))
13+
14+
func executeStreamServers(conf dataplane.Configuration) []executeResult {
15+
streamServers := createStreamServers(conf)
16+
17+
streamServerResult := executeResult{
18+
dest: streamConfigFile,
19+
data: helpers.MustExecuteTemplate(streamServersTemplate, streamServers),
20+
}
21+
22+
result := []executeResult{
23+
streamServerResult,
24+
}
25+
26+
return result
27+
}
28+
29+
func createStreamServers(conf dataplane.Configuration) []stream.Server {
30+
streamServers := make([]stream.Server, 0)
31+
for _, t := range conf.TLSServers {
32+
streamServers = append(streamServers, stream.Server{
33+
Listen: "unix:/var/lib/nginx/" + t.Hostname + fmt.Sprint(t.Port) + ".sock",
34+
Destination: t.UpstreamName,
35+
ProxyPass: true,
36+
SSLPreread: false,
37+
})
38+
}
39+
40+
ports := make(map[int32]bool)
41+
42+
for _, t := range conf.TLSServers {
43+
if ports[t.Port] {
44+
continue
45+
}
46+
ports[t.Port] = true
47+
streamServers = append(streamServers, stream.Server{
48+
Listen: fmt.Sprint(t.Port),
49+
Destination: "$dest" + fmt.Sprint(t.Port),
50+
ProxyPass: false,
51+
SSLPreread: true,
52+
})
53+
}
54+
55+
return streamServers
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package config
2+
3+
const streamServersTemplateText = `
4+
{{- range $s := . }}
5+
server {
6+
listen {{ $s.Listen }};
7+
8+
{{- if $s.ProxyPass }}
9+
proxy_pass {{ $s.Destination }};
10+
{{- else }}
11+
pass {{ $s.Destination }};
12+
{{- end }}
13+
14+
{{- if $s.SSLPreread }}
15+
ssl_preread on;
16+
{{- end }}
17+
}
18+
{{- end }}
19+
`

0 commit comments

Comments
 (0)