Skip to content

Commit 6f00de0

Browse files
committed
Add snippet feature to TransportServer
1 parent 1387861 commit 6f00de0

13 files changed

+242
-31
lines changed

deployments/common/crds-v1beta1/k8s.nginx.org_transportservers.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ spec:
5555
properties:
5656
timeout:
5757
type: string
58+
snippets:
59+
type: string
5860
upstreamParameters:
5961
description: UpstreamParameters defines parameters for an upstream.
6062
type: object

deployments/common/crds/k8s.nginx.org_transportservers.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ spec:
5656
properties:
5757
timeout:
5858
type: string
59+
snippets:
60+
type: string
5961
upstreamParameters:
6062
description: UpstreamParameters defines parameters for an upstream.
6163
type: object

deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ spec:
5656
properties:
5757
timeout:
5858
type: string
59+
snippets:
60+
type: string
5961
upstreamParameters:
6062
description: UpstreamParameters defines parameters for an upstream.
6163
type: object

docs-web/configuration/transportserver-resource.md

+36
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This document is the reference documentation for the TransportServer resource. T
1919
- [SessionParameters](#sessionparameters)
2020
- [Action](#action)
2121
- [Using TransportServer](#using-transportserver)
22+
- [Usings Snippets](#using-snippets)
2223
- [Validation](#validation)
2324
- [Structural Validation](#structural-validation)
2425
- [Comprehensive Validation](#comprehensive-validation)
@@ -366,6 +367,41 @@ secure-app 46sm
366367

367368
In the kubectl get and similar commands, you can also use the short name `ts` instead of `transportserver`.
368369

370+
### Using Snippets
371+
372+
Snippets allow you to insert raw NGINX config into different contexts of NGINX configuration. In the example below, we use snippets to configure [access control](http://nginx.org/en/docs/stream/ngx_stream_access_module.html) in a TransportServer:
373+
374+
```yaml
375+
apiVersion: k8s.nginx.org/v1alpha1
376+
kind: TransportServer
377+
metadata:
378+
name: cafe
379+
spec:
380+
host: cafe.example.com
381+
snippets: |
382+
deny 192.168.1.1;
383+
allow 192.168.1.0/24;
384+
upstreams:
385+
- name: tea
386+
service: tea-svc
387+
port: 80
388+
```
389+
390+
Snippets are intended to be used by advanced NGINX users who need more control over the generated NGINX configuration.
391+
392+
However, because of the disadvantages described below, snippets are disabled by default. To use snippets, set the [`enable-snippets`](/nginx-ingress-controller/configuration/global-configuration/command-line-arguments#cmdoption-enable-snippets) command-line argument.
393+
394+
Disadvantages of using snippets:
395+
* *Complexity*. To use snippets, you will need to:
396+
* Understand NGINX configuration primitives and implement a correct NGINX configuration.
397+
* Understand how the IC generates NGINX configuration so that a snippet doesn't interfere with the other features in the configuration.
398+
* *Decreased robustness*. An incorrect snippet makes the NGINX config invalid which will lead to a failed reload. This will prevent any new configuration updates, including updates for the other TransportServer resource until the snippet is fixed.
399+
* *Security implications*. Snippets give access to NGINX configuration primitives and those primitives are not validated by the Ingress Controller.
400+
401+
402+
> Note that during a period when the NGINX config includes an invalid snippet, NGINX will continue to operate with the latest valid configuration.
403+
404+
369405
### Validation
370406

371407
Two types of validation are available for the TransportServer resource:

internal/configs/configurator.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ func (cnf *Configurator) AddOrUpdateTransportServer(transportServerEx *Transport
557557
func (cnf *Configurator) addOrUpdateTransportServer(transportServerEx *TransportServerEx) error {
558558
name := getFileNameForTransportServer(transportServerEx.TransportServer)
559559

560-
tsCfg := generateTransportServerConfig(transportServerEx, transportServerEx.ListenerPort, cnf.isPlus)
560+
tsCfg, _ := generateTransportServerConfig(transportServerEx, transportServerEx.ListenerPort, cnf.isPlus, cnf.staticCfgParams.EnableSnippets)
561561

562562
content, err := cnf.templateExecutorV2.ExecuteTransportServerTemplate(&tsCfg)
563563
if err != nil {
@@ -1073,6 +1073,7 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, ingExes []*Ingres
10731073
return allWarnings, nil
10741074
}
10751075

1076+
// UpdateTransportServers updates TransportServers.
10761077
func (cnf *Configurator) UpdateTransportServers(updatedTSExes []*TransportServerEx, deletedKeys []string) error {
10771078
for _, tsEx := range updatedTSExes {
10781079
err := cnf.addOrUpdateTransportServer(tsEx)

internal/configs/transportserver.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ func (tsEx *TransportServerEx) String() string {
3030
}
3131

3232
// generateTransportServerConfig generates a full configuration for a TransportServer.
33-
func generateTransportServerConfig(transportServerEx *TransportServerEx, listenerPort int, isPlus bool) version2.TransportServerConfig {
33+
func generateTransportServerConfig(transportServerEx *TransportServerEx, listenerPort int, isPlus bool, snippetsEnabled bool) (version2.TransportServerConfig, Warnings) {
34+
warn := newWarnings()
3435
upstreamNamer := newUpstreamNamerForTransportServer(transportServerEx.TransportServer)
3536

3637
upstreams := generateStreamUpstreams(transportServerEx, upstreamNamer, isPlus)
@@ -59,14 +60,17 @@ func generateTransportServerConfig(transportServerEx *TransportServerEx, listene
5960
proxyTimeout = transportServerEx.TransportServer.Spec.SessionParameters.Timeout
6061
}
6162

62-
statusZone := ""
63+
serverSnippets := generateSnippets(snippetsEnabled, transportServerEx.TransportServer.Spec.Snippets, []string{})
64+
if !snippetsEnabled && (transportServerEx.TransportServer.Spec.Snippets != "") {
65+
warn.AddWarning(transportServerEx.TransportServer, "snippet specified but snippets feature is not enabled")
66+
}
67+
68+
statusZone := transportServerEx.TransportServer.Spec.Listener.Name
6369
if transportServerEx.TransportServer.Spec.Listener.Name == conf_v1alpha1.TLSPassthroughListenerName {
6470
statusZone = transportServerEx.TransportServer.Spec.Host
65-
} else {
66-
statusZone = transportServerEx.TransportServer.Spec.Listener.Name
6771
}
6872

69-
return version2.TransportServerConfig{
73+
tsConfig := version2.TransportServerConfig{
7074
Server: version2.StreamServer{
7175
TLSPassthrough: transportServerEx.TransportServer.Spec.Listener.Name == conf_v1alpha1.TLSPassthroughListenerName,
7276
UnixSocket: generateUnixSocket(transportServerEx),
@@ -84,9 +88,12 @@ func generateTransportServerConfig(transportServerEx *TransportServerEx, listene
8488
ProxyNextUpstreamTimeout: generateTime(nextUpstreamTimeout, "0"),
8589
ProxyNextUpstreamTries: nextUpstreamTries,
8690
HealthCheck: healthCheck,
91+
Snippets: serverSnippets,
8792
},
8893
Upstreams: upstreams,
8994
}
95+
96+
return tsConfig, warn
9097
}
9198

9299
func generateUnixSocket(transportServerEx *TransportServerEx) string {

internal/configs/transportserver_test.go

+164-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package configs
22

33
import (
4-
"reflect"
54
"testing"
65

76
"github.com/google/go-cmp/cmp"
@@ -62,6 +61,157 @@ func TestTransportServerExString(t *testing.T) {
6261
}
6362
}
6463

64+
func TestGenerateTransportServerConfigForTCPSnippets(t *testing.T) {
65+
transportServerEx := TransportServerEx{
66+
TransportServer: &conf_v1alpha1.TransportServer{
67+
ObjectMeta: meta_v1.ObjectMeta{
68+
Name: "tcp-server",
69+
Namespace: "default",
70+
},
71+
Spec: conf_v1alpha1.TransportServerSpec{
72+
Listener: conf_v1alpha1.TransportServerListener{
73+
Name: "tcp-listener",
74+
Protocol: "TCP",
75+
},
76+
Upstreams: []conf_v1alpha1.Upstream{
77+
{
78+
Name: "tcp-app",
79+
Service: "tcp-app-svc",
80+
Port: 5001,
81+
},
82+
},
83+
Action: &conf_v1alpha1.Action{
84+
Pass: "tcp-app",
85+
},
86+
Snippets: "deny 192.168.1.1;\nallow 192.168.1.0/24;",
87+
},
88+
},
89+
Endpoints: map[string][]string{
90+
"default/tcp-app-svc:5001": {
91+
"10.0.0.20:5001",
92+
},
93+
},
94+
}
95+
96+
listenerPort := 2020
97+
98+
expected := version2.TransportServerConfig{
99+
Upstreams: []version2.StreamUpstream{
100+
{
101+
Name: "ts_default_tcp-server_tcp-app",
102+
Servers: []version2.StreamUpstreamServer{
103+
{
104+
Address: "10.0.0.20:5001",
105+
MaxFails: 1,
106+
FailTimeout: "10s",
107+
},
108+
},
109+
UpstreamLabels: version2.UpstreamLabels{
110+
ResourceName: "tcp-server",
111+
ResourceType: "transportserver",
112+
ResourceNamespace: "default",
113+
Service: "tcp-app-svc",
114+
},
115+
},
116+
},
117+
Server: version2.StreamServer{
118+
Port: listenerPort,
119+
UDP: false,
120+
StatusZone: "tcp-listener",
121+
ProxyPass: "ts_default_tcp-server_tcp-app",
122+
Name: "tcp-server",
123+
Namespace: "default",
124+
ProxyConnectTimeout: "60s",
125+
ProxyNextUpstream: false,
126+
ProxyNextUpstreamTries: 0,
127+
ProxyNextUpstreamTimeout: "0s",
128+
ProxyTimeout: "10m",
129+
HealthCheck: nil,
130+
Snippets: []string{"deny 192.168.1.1;", "allow 192.168.1.0/24;"},
131+
},
132+
}
133+
134+
result, _ := generateTransportServerConfig(&transportServerEx, listenerPort, true, true)
135+
if diff := cmp.Diff(expected, result); diff != "" {
136+
t.Errorf("generateTransportServerConfig() mismatch (-want +got):\n%s", diff)
137+
}
138+
}
139+
140+
func TestGenerateTransportServerConfigForTCPSnippetsNotEnabled(t *testing.T) {
141+
transportServerEx := TransportServerEx{
142+
TransportServer: &conf_v1alpha1.TransportServer{
143+
ObjectMeta: meta_v1.ObjectMeta{
144+
Name: "tcp-server",
145+
Namespace: "default",
146+
},
147+
Spec: conf_v1alpha1.TransportServerSpec{
148+
Listener: conf_v1alpha1.TransportServerListener{
149+
Name: "tcp-listener",
150+
Protocol: "TCP",
151+
},
152+
Upstreams: []conf_v1alpha1.Upstream{
153+
{
154+
Name: "tcp-app",
155+
Service: "tcp-app-svc",
156+
Port: 5001,
157+
},
158+
},
159+
Action: &conf_v1alpha1.Action{
160+
Pass: "tcp-app",
161+
},
162+
Snippets: "deny 192.168.1.1;\nallow 192.168.1.0/24;",
163+
},
164+
},
165+
Endpoints: map[string][]string{
166+
"default/tcp-app-svc:5001": {
167+
"10.0.0.20:5001",
168+
},
169+
},
170+
}
171+
172+
listenerPort := 2020
173+
174+
expected := version2.TransportServerConfig{
175+
Upstreams: []version2.StreamUpstream{
176+
{
177+
Name: "ts_default_tcp-server_tcp-app",
178+
Servers: []version2.StreamUpstreamServer{
179+
{
180+
Address: "10.0.0.20:5001",
181+
MaxFails: 1,
182+
FailTimeout: "10s",
183+
},
184+
},
185+
UpstreamLabels: version2.UpstreamLabels{
186+
ResourceName: "tcp-server",
187+
ResourceType: "transportserver",
188+
ResourceNamespace: "default",
189+
Service: "tcp-app-svc",
190+
},
191+
},
192+
},
193+
Server: version2.StreamServer{
194+
Port: 2020,
195+
UDP: false,
196+
StatusZone: "tcp-listener",
197+
ProxyPass: "ts_default_tcp-server_tcp-app",
198+
Name: "tcp-server",
199+
Namespace: "default",
200+
ProxyConnectTimeout: "60s",
201+
ProxyNextUpstream: false,
202+
ProxyNextUpstreamTries: 0,
203+
ProxyNextUpstreamTimeout: "0s",
204+
ProxyTimeout: "10m",
205+
Snippets: []string{},
206+
},
207+
}
208+
209+
result, _ := generateTransportServerConfig(&transportServerEx, listenerPort, true, false)
210+
if diff := cmp.Diff(expected, result); diff != "" {
211+
t.Errorf("generateTransportServerConfig() mismatch (-want +got):\n%s", diff)
212+
}
213+
}
214+
65215
func TestGenerateTransportServerConfigForTCP(t *testing.T) {
66216
transportServerEx := TransportServerEx{
67217
TransportServer: &conf_v1alpha1.TransportServer{
@@ -136,14 +286,15 @@ func TestGenerateTransportServerConfigForTCP(t *testing.T) {
136286
ProxyNextUpstreamTimeout: "0s",
137287
ProxyTimeout: "50s",
138288
HealthCheck: nil,
289+
Snippets: []string{},
139290
},
140291
}
141292

142-
isPlus := true
143-
result := generateTransportServerConfig(&transportServerEx, listenerPort, isPlus)
144-
if !reflect.DeepEqual(result, expected) {
145-
t.Errorf("generateTransportServerConfig() returned \n%+v but expected \n%+v", result, expected)
293+
result, _ := generateTransportServerConfig(&transportServerEx, listenerPort, true, false)
294+
if diff := cmp.Diff(expected, result); diff != "" {
295+
t.Errorf("generateTransportServerConfig() mismatch (-want +got):\n%s", diff)
146296
}
297+
147298
}
148299

149300
func TestGenerateTransportServerConfigForTLSPasstrhough(t *testing.T) {
@@ -220,13 +371,13 @@ func TestGenerateTransportServerConfigForTLSPasstrhough(t *testing.T) {
220371
ProxyNextUpstreamTries: 0,
221372
ProxyTimeout: "10m",
222373
HealthCheck: nil,
374+
Snippets: []string{},
223375
},
224376
}
225377

226-
isPlus := true
227-
result := generateTransportServerConfig(&transportServerEx, listenerPort, isPlus)
228-
if !reflect.DeepEqual(result, expected) {
229-
t.Errorf("generateTransportServerConfig() returned \n%+v but expected \n%+v", result, expected)
378+
result, _ := generateTransportServerConfig(&transportServerEx, listenerPort, true, false)
379+
if diff := cmp.Diff(expected, result); diff != "" {
380+
t.Errorf("generateTransportServerConfig() mismatch (-want +got):\n%s", diff)
230381
}
231382
}
232383

@@ -309,13 +460,13 @@ func TestGenerateTransportServerConfigForUDP(t *testing.T) {
309460
ProxyNextUpstreamTries: 0,
310461
ProxyTimeout: "10m",
311462
HealthCheck: nil,
463+
Snippets: []string{},
312464
},
313465
}
314466

315-
isPlus := true
316-
result := generateTransportServerConfig(&transportServerEx, listenerPort, isPlus)
317-
if !reflect.DeepEqual(result, expected) {
318-
t.Errorf("generateTransportServerConfig() returned \n%+v but expected \n%+v", result, expected)
467+
result, _ := generateTransportServerConfig(&transportServerEx, listenerPort, true, false)
468+
if diff := cmp.Diff(expected, result); diff != "" {
469+
t.Errorf("generateTransportServerConfig() mismatch (-want +got):\n%s", diff)
319470
}
320471
}
321472

internal/configs/version2/nginx-plus.transportserver.tmpl

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ server {
2828
proxy_responses {{ $s.ProxyResponses }};
2929
{{ end }}
3030

31+
{{ range $snippet := $s.Snippets }}
32+
{{- $snippet }}
33+
{{ end }}
34+
3135
proxy_pass {{ $s.ProxyPass }};
3236

3337
{{ if $s.HealthCheck }}

internal/configs/version2/nginx.transportserver.tmpl

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ server {
2626
proxy_responses {{ $s.ProxyResponses }};
2727
{{ end }}
2828

29+
{{ range $snippet := $s.Snippets }}
30+
{{- $snippet }}
31+
{{ end }}
32+
2933
proxy_pass {{ $s.ProxyPass }};
3034

3135
proxy_timeout {{ $s.ProxyTimeout }};

internal/configs/version2/stream.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type StreamServer struct {
3838
ProxyNextUpstreamTimeout string
3939
ProxyNextUpstreamTries int
4040
HealthCheck *StreamHealthCheck
41+
Snippets []string
4142
}
4243

4344
// StreamHealthCheck defines a health check for a StreamUpstream in a StreamServer.

0 commit comments

Comments
 (0)