diff --git a/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml b/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml index 9a70ed8cc6..8a1a1d2eb5 100644 --- a/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml +++ b/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml @@ -539,6 +539,8 @@ spec: type: string next-upstream-tries: type: integer + ntlm: + type: boolean port: type: integer queue: diff --git a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml index 213b9d1f3f..598a6893ad 100644 --- a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml +++ b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml @@ -569,6 +569,8 @@ spec: type: string next-upstream-tries: type: integer + ntlm: + type: boolean port: type: integer queue: diff --git a/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml b/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml index 9a70ed8cc6..8a1a1d2eb5 100644 --- a/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml +++ b/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml @@ -539,6 +539,8 @@ spec: type: string next-upstream-tries: type: integer + ntlm: + type: boolean port: type: integer queue: diff --git a/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml b/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml index 213b9d1f3f..598a6893ad 100644 --- a/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml +++ b/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml @@ -569,6 +569,8 @@ spec: type: string next-upstream-tries: type: integer + ntlm: + type: boolean port: type: integer queue: diff --git a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md index d8b5420be2..342b03c743 100644 --- a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md +++ b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md @@ -304,6 +304,7 @@ tls: |``buffering`` | Enables buffering of responses from the upstream server. See the [proxy_buffering](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering) directive. The default is set in the ``proxy-buffering`` ConfigMap key. | ``boolean`` | No | |``buffers`` | Configures the buffers used for reading a response from the upstream server for a single connection. | [buffers](#upstream-buffers) | No | |``buffer-size`` | Sets the size of the buffer used for reading the first part of a response received from the upstream server. See the [proxy_buffer_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) directive. The default is set in the ``proxy-buffer-size`` ConfigMap key. | ``string`` | No | +|``ntlm`` | Allows proxying requests with NTLM Authentication. See the [ntlm](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ntlm) directive. In order for NTLM authentication to work, it is necessary to enable keepalive connections to upstream servers using the ``keepalive`` field. Note: this feature is supported only in NGINX Plus.| ``boolean`` | No | {{% /table %}} ### Upstream.Buffers diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 2a73f696f0..0d621c5bca 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -37,6 +37,7 @@ type Upstream struct { Queue *Queue SessionCookie *SessionCookie UpstreamLabels UpstreamLabels + NTLM bool } // UpstreamServer defines an upstream server. diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index 23b83b894a..b8efae7d2f 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -21,6 +21,8 @@ upstream {{ $u.Name }} { sticky cookie {{ .Name }}{{ if .Expires }} expires={{ .Expires }}{{ end }}{{ if .Domain }} domain={{ .Domain }}{{ end }}{{ if .HTTPOnly }} httponly{{ end }}{{ if .Secure }} secure{{ end }}{{ if .Path }} path={{ .Path }}{{ end }}; {{ end }} {{ end }} + + {{ if $u.NTLM }}ntlm;{{ end }} } {{ end }} diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 816af89496..68b732e759 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -34,6 +34,7 @@ var virtualServerCfg = VirtualServerConfig{ UpstreamZoneSize: "256k", Queue: &Queue{Size: 10, Timeout: "60s"}, SessionCookie: &SessionCookie{Enable: true, Name: "test", Path: "/tea", Expires: "25s"}, + NTLM: true, }, { Name: "coffee-v1", diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 6dfea0a8ba..a8cbbbc84a 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -1183,6 +1183,7 @@ func (vsc *virtualServerConfigurator) generateUpstream( ups.SlowStart = vsc.generateSlowStartForPlus(owner, upstream, lbMethod) ups.Queue = generateQueueForPlus(upstream.Queue, "60s") ups.SessionCookie = generateSessionCookie(upstream.SessionCookie) + ups.NTLM = upstream.NTLM } return ups diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index cea9248f24..91d72fb35d 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -3597,6 +3597,51 @@ func TestGenerateUpstreamForExternalNameService(t *testing.T) { } } +func TestGenerateUpstreamWithNTLM(t *testing.T) { + name := "test-upstream" + upstream := conf_v1.Upstream{Service: name, Port: 80, NTLM: true} + endpoints := []string{ + "192.168.10.10:8080", + } + cfgParams := ConfigParams{ + LBMethod: "random", + MaxFails: 1, + MaxConns: 0, + FailTimeout: "10s", + Keepalive: 21, + UpstreamZoneSize: "256k", + } + + expected := version2.Upstream{ + Name: "test-upstream", + UpstreamLabels: version2.UpstreamLabels{ + Service: "test-upstream", + }, + Servers: []version2.UpstreamServer{ + { + Address: "192.168.10.10:8080", + }, + }, + MaxFails: 1, + MaxConns: 0, + FailTimeout: "10s", + LBMethod: "random", + Keepalive: 21, + UpstreamZoneSize: "256k", + NTLM: true, + } + + vsc := newVirtualServerConfigurator(&cfgParams, true, false, &StaticConfigParams{}) + result := vsc.generateUpstream(nil, name, upstream, false, endpoints) + if !reflect.DeepEqual(result, expected) { + t.Errorf("generateUpstream() returned %v but expected %v", result, expected) + } + + if len(vsc.warnings) != 0 { + t.Errorf("generateUpstream returned warnings for %v", upstream) + } +} + func TestGenerateProxyPass(t *testing.T) { tests := []struct { tlsEnabled bool diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index b5d4194ff3..578d60767f 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -78,6 +78,7 @@ type Upstream struct { Queue *UpstreamQueue `json:"queue"` SessionCookie *SessionCookie `json:"sessionCookie"` UseClusterIP bool `json:"use-cluster-ip"` + NTLM bool `json:"ntlm"` } // UpstreamBuffers defines Buffer Configuration for an Upstream. diff --git a/pkg/apis/configuration/validation/virtualserver.go b/pkg/apis/configuration/validation/virtualserver.go index 373e750e93..ce01f00ca3 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -1420,6 +1420,10 @@ func rejectPlusResourcesInOSS(upstream v1.Upstream, idxPath *field.Path, isPlus allErrs = append(allErrs, field.Forbidden(idxPath.Child("queue"), "queue is only supported in NGINX Plus")) } + if upstream.NTLM { + allErrs = append(allErrs, field.Forbidden(idxPath.Child("ntlm"), "NTLM is only supported in NGINX Plus")) + } + return allErrs } diff --git a/pkg/apis/configuration/validation/virtualserver_test.go b/pkg/apis/configuration/validation/virtualserver_test.go index baa14709d2..cbb787e922 100644 --- a/pkg/apis/configuration/validation/virtualserver_test.go +++ b/pkg/apis/configuration/validation/virtualserver_test.go @@ -2571,6 +2571,11 @@ func TestRejectPlusResourcesInOSS(t *testing.T) { Queue: &v1.UpstreamQueue{}, }, }, + { + upstream: &v1.Upstream{ + NTLM: true, + }, + }, } for _, test := range tests { diff --git a/tests/suite/test_virtual_server_upstream_options.py b/tests/suite/test_virtual_server_upstream_options.py index 01ad15f6d5..f00db5e50a 100644 --- a/tests/suite/test_virtual_server_upstream_options.py +++ b/tests/suite/test_virtual_server_upstream_options.py @@ -302,13 +302,14 @@ class TestOptionsSpecificForPlus: "healthCheck": {"enable": True, "port": 8080}, "slow-start": "3h", "queue": {"size": 100}, + "ntlm": True, "sessionCookie": {"enable": True, "name": "TestCookie", "path": "/some-valid/path", "expires": "max", "domain": "virtual-server-route.example.com", "httpOnly": True, "secure": True}}, ["health_check uri=/ port=8080 interval=5s jitter=0s", "fails=1 passes=1;", - "slow_start=3h", "queue 100 timeout=60s;", + "slow_start=3h", "queue 100 timeout=60s;", "ntlm;", "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly secure path=/some-valid/path;"]), ({"lb-method": "least_conn", "healthCheck": {"enable": True, "path": "/health", @@ -318,12 +319,12 @@ class TestOptionsSpecificForPlus: "connect-timeout": "35s", "read-timeout": "45s", "send-timeout": "55s", "headers": [{"name": "Host", "value": "virtual-server.example.com"}]}, "queue": {"size": 1000, "timeout": "66s"}, - "slow-start": "0s"}, + "slow-start": "0s", "ntlm": True}, ["health_check uri=/health port=8080 interval=15s jitter=3", "fails=2 passes=2 match=", "proxy_pass https://vs", "status 200;", "proxy_connect_timeout 35s;", "proxy_read_timeout 45s;", "proxy_send_timeout 55s;", 'proxy_set_header Host "virtual-server.example.com";', - "slow_start=0s", "queue 1000 timeout=66s;"]) + "slow_start=0s", "queue 1000 timeout=66s;", "ntlm;"]) ]) def test_config_and_events(self, kube_apis, ingress_controller_prerequisites,