From e075a740f94f43262d1d3594bec36e2dbeec48bc Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 20 Nov 2025 15:21:59 +0000 Subject: [PATCH 1/6] Add client-body-buffer-size directive to VirtualServer --- .../k8s.nginx.org_virtualserverroutes.yaml | 4 + .../bases/k8s.nginx.org_virtualservers.yaml | 4 + deploy/crds.yaml | 8 + docs/crd/k8s.nginx.org_virtualserverroutes.md | 1 + docs/crd/k8s.nginx.org_virtualservers.md | 1 + .../__snapshots__/templates_test.snap | 485 ++++++++++++++++++ internal/configs/version2/http.go | 1 + .../version2/nginx-plus.virtualserver.tmpl | 3 + .../configs/version2/nginx.virtualserver.tmpl | 3 + internal/configs/version2/templates_test.go | 45 ++ internal/configs/virtualserver.go | 1 + internal/configs/virtualserver_test.go | 2 + pkg/apis/configuration/v1/types.go | 2 + .../configuration/validation/virtualserver.go | 1 + .../validation/virtualserver_test.go | 15 + 15 files changed, 576 insertions(+) diff --git a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml index 544718e877..29b89844e8 100644 --- a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml @@ -878,6 +878,10 @@ spec: is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' type: string + client-body-buffer-size: + description: Sets the size of the buffer used for reading the + client request body. + type: string client-max-body-size: description: Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index ac5bdd456a..798948336b 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -1067,6 +1067,10 @@ spec: is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' type: string + client-body-buffer-size: + description: Sets the size of the buffer used for reading the + client request body. + type: string client-max-body-size: description: Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap diff --git a/deploy/crds.yaml b/deploy/crds.yaml index fd080f2d59..d6aa0ce873 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1916,6 +1916,10 @@ spec: is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' type: string + client-body-buffer-size: + description: Sets the size of the buffer used for reading the + client request body. + type: string client-max-body-size: description: Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap @@ -3335,6 +3339,10 @@ spec: is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' type: string + client-body-buffer-size: + description: Sets the size of the buffer used for reading the + client request body. + type: string client-max-body-size: description: Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap diff --git a/docs/crd/k8s.nginx.org_virtualserverroutes.md b/docs/crd/k8s.nginx.org_virtualserverroutes.md index 52426dacee..bd7c136f31 100644 --- a/docs/crd/k8s.nginx.org_virtualserverroutes.md +++ b/docs/crd/k8s.nginx.org_virtualserverroutes.md @@ -169,6 +169,7 @@ The `.spec` object supports the following fields: | `upstreams[].buffers.number` | `integer` | Configures the number of buffers. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].buffers.size` | `string` | Configures the size of a buffer. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].busy-buffers-size` | `string` | Sets the size of the buffers used for reading a response from the upstream server when the proxy_buffering is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' | +| `upstreams[].client-body-buffer-size` | `string` | Sets the size of the buffer used for reading the client request body. | | `upstreams[].client-max-body-size` | `string` | Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. | | `upstreams[].connect-timeout` | `string` | The timeout for establishing a connection with an upstream server. The default is specified in the proxy-connect-timeout ConfigMap key. | | `upstreams[].fail-timeout` | `string` | The time during which the specified number of unsuccessful attempts to communicate with an upstream server should happen to consider the server unavailable. The default is set in the fail-timeout ConfigMap key. | diff --git a/docs/crd/k8s.nginx.org_virtualservers.md b/docs/crd/k8s.nginx.org_virtualservers.md index 280e1bb8b2..4366b8824b 100644 --- a/docs/crd/k8s.nginx.org_virtualservers.md +++ b/docs/crd/k8s.nginx.org_virtualservers.md @@ -204,6 +204,7 @@ The `.spec` object supports the following fields: | `upstreams[].buffers.number` | `integer` | Configures the number of buffers. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].buffers.size` | `string` | Configures the size of a buffer. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].busy-buffers-size` | `string` | Sets the size of the buffers used for reading a response from the upstream server when the proxy_buffering is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' | +| `upstreams[].client-body-buffer-size` | `string` | Sets the size of the buffer used for reading the client request body. | | `upstreams[].client-max-body-size` | `string` | Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. | | `upstreams[].connect-timeout` | `string` | The timeout for establishing a connection with an upstream server. The default is specified in the proxy-connect-timeout ConfigMap key. | | `upstreams[].fail-timeout` | `string` | The time during which the specified number of unsuccessful attempts to communicate with an upstream server should happen to consider the server unavailable. The default is set in the fail-timeout ConfigMap key. | diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index 50f2fe69b4..c2f290d3ad 100644 --- a/internal/configs/version2/__snapshots__/templates_test.snap +++ b/internal/configs/version2/__snapshots__/templates_test.snap @@ -1876,6 +1876,490 @@ server { --- +[TestExecuteVirtualServerTemplate_RendersTemplateDefaultClientBodyBufferSize - 1] + +upstream test-upstream { + zone test-upstream 256k; + random; + server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; + keepalive 32; + queue 10 timeout=60s; + sticky cookie test expires=25s path=/tea; + ntlm; +} + +upstream coffee-v1 { + zone coffee-v1 256k; + server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; +} + +upstream coffee-v2 { + zone coffee-v2 256k; + server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; +} + +split_clients $request_id $split_0 { + 50% @loc0; + 50% @loc1; +} +map $match_0_0 $match { + ~^1 @match_loc_0; + default @match_loc_default; +} +map $http_x_version $match_0_0 { + v2 1; + default 0; +} +# HTTP snippet +limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; +keyval $idp_sid $client_sid zone=oidc_sids; + +server { + listen 80 proxy_protocol; + listen [::]:80 proxy_protocol; + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + include oidc/oidc.conf; + + set $oidc_pkce_enable 0; + set $oidc_client_auth_method "client_secret_post"; + set $oidc_logout_redirect "https://example.com/logout"; + set $oidc_hmac_key ""; + set $zone_sync_leeway 0; + + set $oidc_authz_endpoint "https://idp.example.com/auth"; + set $oidc_authz_extra_args ""; + set $oidc_token_endpoint "https://idp.example.com/token"; + set $oidc_end_session_endpoint "https://idp.example.com/logout"; + set $oidc_jwt_keyfile "https://idp.example.com/jwks"; + set $oidc_scopes "openid+profile+email"; + set $oidc_client "test-client"; + set $oidc_client_secret "test-secret"; + set $redir_location "/custom-location"; + # Custom OIDC redirect location based on policy redirectURI + location = /custom-location { + status_zone "OIDC code exchange"; + js_content oidc.codeExchange; + error_page 500 502 504 @oidc_error; + } + listen 443 ssl proxy_protocol; + listen [::]:443 ssl proxy_protocol; + + http2 on; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + ssl_client_certificate ingress-mtls-secret; + ssl_verify_client on; + ssl_verify_depth 2; + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + server_tokens "off"; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Real-IP; + real_ip_recursive on; + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req_log_level error; + limit_req_status 503; + limit_req zone=pol_rl_test_test_test burst=5 delay=10; + auth_jwt "My Api"; + auth_jwt_key_file jwk-secret; + app_protect_enable on; + app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; + app_protect_security_log_enable on; + app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; + + # server snippet + location /split { + rewrite ^ @split_0 last; + } + location /coffee { + rewrite ^ @match last; + } + location @hc-coffee { + + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + proxy_pass http://coffee-v2; + health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; + + } + location @hc-tea { + + grpc_connect_timeout ; + grpc_read_timeout ; + grpc_send_timeout ; + grpc_pass grpc://tea-v3; + health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; + + } + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { + + default_type "application/json"; + + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { + + + add_header Set-Cookie "cookie1=test" always; + + add_header Set-Cookie "cookie2=test; Secure" always; + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + + + location @return_0 { + default_type "text/html"; + + # status code is ignored here, using 0 + return 0 "Hello!"; + } + + + + location / { + set $service ""; + status_zone ""; + internal; + # location snippet + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req zone=loc_pol_rl_test_test_test; + + + proxy_ssl_certificate egress-mtls-secret.pem; + proxy_ssl_certificate_key egress-mtls-secret.pem; + + proxy_ssl_trusted_certificate trusted-cert.pem; + proxy_ssl_verify on; + proxy_ssl_verify_depth 1; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_ciphers DEFAULT; + proxy_ssl_session_reuse on; + proxy_ssl_server_name on; + proxy_ssl_name ; + set $default_connection_header close; + rewrite $request_uri $request_uri; + rewrite $request_uri $request_uri; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + client_body_buffer_size 8k; + proxy_max_temp_file_size 1024m; + + proxy_buffering on; + proxy_buffers 8 4k; + proxy_buffer_size 4k; + proxy_busy_buffers_size 8k; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Header; + proxy_pass_header Host; + proxy_ignore_headers Cache; + add_header Header-Name "Header Value" always; + proxy_pass http://test-upstream$request_uri; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc0 { + set $service ""; + status_zone ""; + + + error_page 400 500 =200 "@error_page_1"; + error_page 500 "@error_page_2"; + proxy_intercept_errors on; + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc1 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc2 { + set $service ""; + status_zone ""; + + + error_page 400 = @grpc_internal; + error_page 401 = @grpc_unauthenticated; + error_page 403 = @grpc_permission_denied; + error_page 404 = @grpc_unimplemented; + error_page 429 = @grpc_unavailable; + error_page 502 = @grpc_unavailable; + error_page 503 = @grpc_unavailable; + error_page 504 = @grpc_unavailable; + error_page 405 = @grpc_internal; + error_page 408 = @grpc_deadline_exceeded; + error_page 413 = @grpc_resource_exhausted; + error_page 414 = @grpc_resource_exhausted; + error_page 415 = @grpc_internal; + error_page 426 = @grpc_internal; + error_page 495 = @grpc_unauthenticated; + error_page 496 = @grpc_unauthenticated; + error_page 497 = @grpc_internal; + error_page 500 = @grpc_internal; + error_page 501 = @grpc_internal; + set $default_connection_header close; + grpc_connect_timeout 30s; + grpc_read_timeout 31s; + grpc_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + grpc_set_header X-Real-IP $remote_addr; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + grpc_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-Port $server_port; + grpc_set_header X-Forwarded-Proto $scheme; + grpc_pass grpc://coffee-v3; + grpc_next_upstream ; + grpc_next_upstream_timeout ; + grpc_next_upstream_tries 0; + } + location @match_loc_0 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @match_loc_default { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location /return { + set $service ""; + status_zone ""; + + + error_page 418 =200 "@return_0"; + proxy_intercept_errors on; + proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; + set $default_connection_header close; + } + + location @grpc_deadline_exceeded { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 4; + add_header grpc-message 'deadline exceeded'; + return 204; + } + + location @grpc_permission_denied { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 7; + add_header grpc-message 'permission denied'; + return 204; + } + + location @grpc_resource_exhausted { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 8; + add_header grpc-message 'resource exhausted'; + return 204; + } + + location @grpc_unimplemented { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 12; + add_header grpc-message unimplemented; + return 204; + } + + location @grpc_internal { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 13; + add_header grpc-message 'internal error'; + return 204; + } + + location @grpc_unavailable { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 14; + add_header grpc-message unavailable; + return 204; + } + + location @grpc_unauthenticated { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 16; + add_header grpc-message unauthenticated; + return 204; + } + + + +} + +--- + +[TestExecuteVirtualServerTemplate_RendersTemplateWithClientBodyBufferSize - 1] + + +server { + listen 80; + listen [::]:80; + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + + server_tokens ""; + + + + + location / { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + client_max_body_size ; + client_body_buffer_size 16k; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://test-upstream; + proxy_next_upstream ; + proxy_next_upstream_timeout ; + proxy_next_upstream_tries 0; + } +} + +--- + [TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListener - 1] @@ -3052,6 +3536,7 @@ server { proxy_read_timeout 31s; proxy_send_timeout 32s; client_max_body_size 1m; + client_body_buffer_size 8k; proxy_max_temp_file_size 1024m; proxy_buffering on; diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index ea806bd563..da730fcf95 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -198,6 +198,7 @@ type Location struct { ProxyReadTimeout string ProxySendTimeout string ClientMaxBodySize string + ClientBodyBufferSize string ProxyMaxTempFileSize string ProxyBuffering bool ProxyBuffers string diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index 4886cc2463..90d2089d7e 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -674,6 +674,9 @@ server { {{ $proxyOrGRPC }}_read_timeout {{ $l.ProxyReadTimeout }}; {{ $proxyOrGRPC }}_send_timeout {{ $l.ProxySendTimeout }}; client_max_body_size {{ $l.ClientMaxBodySize }}; + {{- if $l.ClientBodyBufferSize }} + client_body_buffer_size {{ $l.ClientBodyBufferSize }} + {{- end }} {{- if $l.ProxyMaxTempFileSize }} proxy_max_temp_file_size {{ $l.ProxyMaxTempFileSize }}; diff --git a/internal/configs/version2/nginx.virtualserver.tmpl b/internal/configs/version2/nginx.virtualserver.tmpl index 13432de9d0..f4468e9464 100644 --- a/internal/configs/version2/nginx.virtualserver.tmpl +++ b/internal/configs/version2/nginx.virtualserver.tmpl @@ -368,6 +368,9 @@ server { {{ $proxyOrGRPC }}_read_timeout {{ $l.ProxyReadTimeout }}; {{ $proxyOrGRPC }}_send_timeout {{ $l.ProxySendTimeout }}; client_max_body_size {{ $l.ClientMaxBodySize }}; + {{- if $l.ClientBodyBufferSize }} + client_body_buffer_size {{ $l.ClientBodyBufferSize }}; + {{- end }} {{- if $l.ProxyMaxTempFileSize }} proxy_max_temp_file_size {{ $l.ProxyMaxTempFileSize }}; diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 7b5253f92a..f101d6e3ae 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -440,6 +440,36 @@ func TestExecuteVirtualServerTemplate_RendersPlusTemplateWithHTTP2Off(t *testing t.Log(string(got)) } +func TestExecuteVirtualServerTemplate_RendersTemplateWithClientBodyBufferSize(t *testing.T) { + t.Parallel() + executor := newTmplExecutorNGINXPlus(t) + + got, err := executor.ExecuteVirtualServerTemplate(&virtualServerCfgWithClientBodyBufferSize) + if err != nil { + t.Error(err) + } + if !bytes.Contains(got, []byte("client_body_buffer_size 16k;")) { + t.Error("want `client_body_buffer_size 16k;` directive in generated template") + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + +func TestExecuteVirtualServerTemplate_RendersTemplateDefaultClientBodyBufferSize(t *testing.T) { + t.Parallel() + executor := newTmplExecutorNGINXPlus(t) + + got, err := executor.ExecuteVirtualServerTemplate(&virtualServerCfg) + if err != nil { + t.Error(err) + } + if !bytes.Contains(got, []byte("client_body_buffer_size 8k;")) { + t.Error("want `client_body_buffer_size 8k;` directive in generated template") + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + func TestExecuteVirtualServerTemplate_RendersOSSTemplateWithHTTP2On(t *testing.T) { t.Parallel() executor := newTmplExecutorNGINX(t) @@ -1594,6 +1624,7 @@ var ( ProxyReadTimeout: "31s", ProxySendTimeout: "32s", ClientMaxBodySize: "1m", + ClientBodyBufferSize: "8k", ProxyBuffering: true, ProxyBuffers: "8 4k", ProxyBufferSize: "4k", @@ -2171,6 +2202,20 @@ var ( }, } + virtualServerCfgWithClientBodyBufferSize = VirtualServerConfig{ + Server: Server{ + ServerName: "example.com", + StatusZone: "example.com", + Locations: []Location{ + { + Path: "/", + ProxyPass: "http://test-upstream", + ClientBodyBufferSize: "16k", + }, + }, + }, + } + virtualServerCfgWithRateLimitJWTClaim = VirtualServerConfig{ LimitReqZones: []LimitReqZone{ { diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 8dc42d007a..a4c0145bcd 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -2647,6 +2647,7 @@ func generateLocationForProxying(path string, upstreamName string, upstream conf ProxyReadTimeout: generateTimeWithDefault(upstream.ProxyReadTimeout, cfgParams.ProxyReadTimeout), ProxySendTimeout: generateTimeWithDefault(upstream.ProxySendTimeout, cfgParams.ProxySendTimeout), ClientMaxBodySize: generateString(upstream.ClientMaxBodySize, cfgParams.ClientMaxBodySize), + ClientBodyBufferSize: upstream.ClientBodyBufferSize, ProxyMaxTempFileSize: cfgParams.ProxyMaxTempFileSize, ProxyBuffering: generateBool(upstream.ProxyBuffering, cfgParams.ProxyBuffering), ProxyBuffers: generateBuffers(upstream.ProxyBuffers, cfgParams.ProxyBuffers), diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index bf5f8d78d1..bbd8938ef8 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -16221,6 +16221,7 @@ func TestGenerateLocationForProxying(t *testing.T) { ProxyReadTimeout: "31s", ProxySendTimeout: "32s", ClientMaxBodySize: "1m", + ClientBodyBufferSize: "16k", ProxyMaxTempFileSize: "1024m", ProxyBuffering: true, ProxyBuffers: "8 4k", @@ -16238,6 +16239,7 @@ func TestGenerateLocationForProxying(t *testing.T) { ProxyReadTimeout: "31s", ProxySendTimeout: "32s", ClientMaxBodySize: "1m", + ClientBodyBufferSize: "16k", ProxyMaxTempFileSize: "1024m", ProxyBuffering: true, ProxyBuffers: "8 4k", diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 1f3dd4a6b7..a2fdfcbc91 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -157,6 +157,8 @@ type Upstream struct { ProxyBusyBuffersSize string `json:"busy-buffers-size"` // Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. ClientMaxBodySize string `json:"client-max-body-size"` + // Sets the size of the buffer used for reading the client request body. + ClientBodyBufferSize string `json:"client-body-buffer-size"` // The TLS configuration for the Upstream. TLS UpstreamTLS `json:"tls"` // The health check configuration for the Upstream. Note: this feature is supported only in NGINX Plus. diff --git a/pkg/apis/configuration/validation/virtualserver.go b/pkg/apis/configuration/validation/virtualserver.go index e5edfff294..b8e5e57cac 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -634,6 +634,7 @@ func (vsv *VirtualServerValidator) validateUpstreams(upstreams []v1.Upstream, fi allErrs = append(allErrs, validatePositiveIntOrZeroFromPointer(u.Keepalive, idxPath.Child("keepalive"))...) allErrs = append(allErrs, validatePositiveIntOrZeroFromPointer(u.MaxConns, idxPath.Child("max-conns"))...) allErrs = append(allErrs, validateOffset(u.ClientMaxBodySize, idxPath.Child("client-max-body-size"))...) + allErrs = append(allErrs, validateSize(u.ClientBodyBufferSize, idxPath.Child("client-body-buffer-size"))...) allErrs = append(allErrs, validateUpstreamHealthCheck(u.HealthCheck, u.Type, idxPath.Child("healthCheck"))...) allErrs = append(allErrs, validateTime(u.SlowStart, idxPath.Child("slow-start"))...) allErrs = append(allErrs, validateBuffer(u.ProxyBuffers, idxPath.Child("buffers"))...) diff --git a/pkg/apis/configuration/validation/virtualserver_test.go b/pkg/apis/configuration/validation/virtualserver_test.go index 3a2c2edcda..27d84f9d53 100644 --- a/pkg/apis/configuration/validation/virtualserver_test.go +++ b/pkg/apis/configuration/validation/virtualserver_test.go @@ -550,6 +550,7 @@ func TestValidateUpstreams(t *testing.T) { ProxyNextUpstreamTries: 5, MaxConns: createPointerFromInt(16), Type: "grpc", + ClientBodyBufferSize: "16k", }, { Name: "upstream2", @@ -743,6 +744,20 @@ func TestValidateUpstreamsFails(t *testing.T) { }, msg: "invalid value for ClientMaxBodySize", }, + { + upstreams: []v1.Upstream{ + { + Name: "upstream1", + Service: "test-1", + Port: 80, + ClientBodyBufferSize: "10hello", + }, + }, + expectedUpstreamNames: map[string]sets.Empty{ + "upstream1": {}, + }, + msg: "invalid value for ClientBodyBufferSize", + }, { upstreams: []v1.Upstream{ { From fd1b8fa81282c2eae4c4164dc9428787d742d157 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 20 Nov 2025 16:10:50 +0000 Subject: [PATCH 2/6] fix vs test and snaps --- internal/configs/version1/__snapshots__/template_test.snap | 2 ++ internal/configs/version2/nginx-plus.virtualserver.tmpl | 2 +- internal/configs/virtualserver.go | 2 +- internal/configs/virtualserver_test.go | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/configs/version1/__snapshots__/template_test.snap b/internal/configs/version1/__snapshots__/template_test.snap index 61a3dbfaef..5dee88408a 100644 --- a/internal/configs/version1/__snapshots__/template_test.snap +++ b/internal/configs/version1/__snapshots__/template_test.snap @@ -733,6 +733,7 @@ server { proxy_read_timeout 10s; proxy_send_timeout 10s; client_max_body_size 2m; + client_body_buffer_size 16k; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -1268,6 +1269,7 @@ server { proxy_read_timeout 10s; proxy_send_timeout 10s; client_max_body_size 2m; + client_body_buffer_size 16k; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index 90d2089d7e..9133427225 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -675,7 +675,7 @@ server { {{ $proxyOrGRPC }}_send_timeout {{ $l.ProxySendTimeout }}; client_max_body_size {{ $l.ClientMaxBodySize }}; {{- if $l.ClientBodyBufferSize }} - client_body_buffer_size {{ $l.ClientBodyBufferSize }} + client_body_buffer_size {{ $l.ClientBodyBufferSize }}; {{- end }} {{- if $l.ProxyMaxTempFileSize }} diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index a4c0145bcd..9696afb174 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -2647,7 +2647,7 @@ func generateLocationForProxying(path string, upstreamName string, upstream conf ProxyReadTimeout: generateTimeWithDefault(upstream.ProxyReadTimeout, cfgParams.ProxyReadTimeout), ProxySendTimeout: generateTimeWithDefault(upstream.ProxySendTimeout, cfgParams.ProxySendTimeout), ClientMaxBodySize: generateString(upstream.ClientMaxBodySize, cfgParams.ClientMaxBodySize), - ClientBodyBufferSize: upstream.ClientBodyBufferSize, + ClientBodyBufferSize: generateString(upstream.ClientBodyBufferSize, cfgParams.ClientBodyBufferSize), ProxyMaxTempFileSize: cfgParams.ProxyMaxTempFileSize, ProxyBuffering: generateBool(upstream.ProxyBuffering, cfgParams.ProxyBuffering), ProxyBuffers: generateBuffers(upstream.ProxyBuffers, cfgParams.ProxyBuffers), diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index bbd8938ef8..d9dd0cf516 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -16271,6 +16271,7 @@ func TestGenerateLocationForGrpcProxying(t *testing.T) { ProxyReadTimeout: "31s", ProxySendTimeout: "32s", ClientMaxBodySize: "1m", + ClientBodyBufferSize: "16k", ProxyMaxTempFileSize: "1024m", ProxyBuffering: true, ProxyBuffers: "8 4k", @@ -16290,6 +16291,7 @@ func TestGenerateLocationForGrpcProxying(t *testing.T) { ProxyReadTimeout: "31s", ProxySendTimeout: "32s", ClientMaxBodySize: "1m", + ClientBodyBufferSize: "16k", ProxyMaxTempFileSize: "1024m", ProxyBuffering: true, ProxyBuffers: "8 4k", From eef077c86be81dcf9f67046d17d3cb246d4303fd Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Fri, 21 Nov 2025 12:21:02 +0000 Subject: [PATCH 3/6] fix ClientBodyBufferSize --- internal/configs/config_params.go | 1 + internal/configs/version1/__snapshots__/template_test.snap | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/configs/config_params.go b/internal/configs/config_params.go index 1410fd45f6..454d18cc6d 100644 --- a/internal/configs/config_params.go +++ b/internal/configs/config_params.go @@ -12,6 +12,7 @@ import ( type ConfigParams struct { Context context.Context ClientMaxBodySize string + ClientBodyBufferSize string DefaultServerAccessLogOff bool DefaultServerReturn string FailTimeout string diff --git a/internal/configs/version1/__snapshots__/template_test.snap b/internal/configs/version1/__snapshots__/template_test.snap index 5dee88408a..61a3dbfaef 100644 --- a/internal/configs/version1/__snapshots__/template_test.snap +++ b/internal/configs/version1/__snapshots__/template_test.snap @@ -733,7 +733,6 @@ server { proxy_read_timeout 10s; proxy_send_timeout 10s; client_max_body_size 2m; - client_body_buffer_size 16k; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -1269,7 +1268,6 @@ server { proxy_read_timeout 10s; proxy_send_timeout 10s; client_max_body_size 2m; - client_body_buffer_size 16k; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; From 3cb2ba65d5dc9a141c3119532da4c3b669463ca7 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Fri, 21 Nov 2025 12:28:35 +0000 Subject: [PATCH 4/6] fix snaps --- .../__snapshots__/templates_test.snap | 434 ------------------ internal/configs/version2/templates_test.go | 15 - 2 files changed, 449 deletions(-) diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index c2f290d3ad..a470046f6a 100644 --- a/internal/configs/version2/__snapshots__/templates_test.snap +++ b/internal/configs/version2/__snapshots__/templates_test.snap @@ -1872,440 +1872,6 @@ server { -} - ---- - -[TestExecuteVirtualServerTemplate_RendersTemplateDefaultClientBodyBufferSize - 1] - -upstream test-upstream { - zone test-upstream 256k; - random; - server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; - keepalive 32; - queue 10 timeout=60s; - sticky cookie test expires=25s path=/tea; - ntlm; -} - -upstream coffee-v1 { - zone coffee-v1 256k; - server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; -} - -upstream coffee-v2 { - zone coffee-v2 256k; - server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; -} - -split_clients $request_id $split_0 { - 50% @loc0; - 50% @loc1; -} -map $match_0_0 $match { - ~^1 @match_loc_0; - default @match_loc_default; -} -map $http_x_version $match_0_0 { - v2 1; - default 0; -} -# HTTP snippet -limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; -keyval $idp_sid $client_sid zone=oidc_sids; - -server { - listen 80 proxy_protocol; - listen [::]:80 proxy_protocol; - - - server_name example.com; - status_zone example.com; - set $resource_type "virtualserver"; - set $resource_name ""; - set $resource_namespace ""; - include oidc/oidc.conf; - - set $oidc_pkce_enable 0; - set $oidc_client_auth_method "client_secret_post"; - set $oidc_logout_redirect "https://example.com/logout"; - set $oidc_hmac_key ""; - set $zone_sync_leeway 0; - - set $oidc_authz_endpoint "https://idp.example.com/auth"; - set $oidc_authz_extra_args ""; - set $oidc_token_endpoint "https://idp.example.com/token"; - set $oidc_end_session_endpoint "https://idp.example.com/logout"; - set $oidc_jwt_keyfile "https://idp.example.com/jwks"; - set $oidc_scopes "openid+profile+email"; - set $oidc_client "test-client"; - set $oidc_client_secret "test-secret"; - set $redir_location "/custom-location"; - # Custom OIDC redirect location based on policy redirectURI - location = /custom-location { - status_zone "OIDC code exchange"; - js_content oidc.codeExchange; - error_page 500 502 504 @oidc_error; - } - listen 443 ssl proxy_protocol; - listen [::]:443 ssl proxy_protocol; - - http2 on; - ssl_certificate cafe-secret.pem; - ssl_certificate_key cafe-secret.pem; - ssl_client_certificate ingress-mtls-secret; - ssl_verify_client on; - ssl_verify_depth 2; - if ($scheme = 'http') { - return 301 https://$host$request_uri; - } - - server_tokens "off"; - set_real_ip_from 0.0.0.0/0; - real_ip_header X-Real-IP; - real_ip_recursive on; - allow 127.0.0.1; - deny all; - deny 127.0.0.1; - allow all; - limit_req_log_level error; - limit_req_status 503; - limit_req zone=pol_rl_test_test_test burst=5 delay=10; - auth_jwt "My Api"; - auth_jwt_key_file jwk-secret; - app_protect_enable on; - app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; - app_protect_security_log_enable on; - app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; - - # server snippet - location /split { - rewrite ^ @split_0 last; - } - location /coffee { - rewrite ^ @match last; - } - location @hc-coffee { - - proxy_connect_timeout ; - proxy_read_timeout ; - proxy_send_timeout ; - proxy_pass http://coffee-v2; - health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; - - } - location @hc-tea { - - grpc_connect_timeout ; - grpc_read_timeout ; - grpc_send_timeout ; - grpc_pass grpc://tea-v3; - health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; - - } - location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { - - default_type "application/json"; - - - # status code is ignored here, using 0 - return 0 "Hello World"; - } - - location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { - - - add_header Set-Cookie "cookie1=test" always; - - add_header Set-Cookie "cookie2=test; Secure" always; - - # status code is ignored here, using 0 - return 0 "Hello World"; - } - - - - location @return_0 { - default_type "text/html"; - - # status code is ignored here, using 0 - return 0 "Hello!"; - } - - - - location / { - set $service ""; - status_zone ""; - internal; - # location snippet - allow 127.0.0.1; - deny all; - deny 127.0.0.1; - allow all; - limit_req zone=loc_pol_rl_test_test_test; - - - proxy_ssl_certificate egress-mtls-secret.pem; - proxy_ssl_certificate_key egress-mtls-secret.pem; - - proxy_ssl_trusted_certificate trusted-cert.pem; - proxy_ssl_verify on; - proxy_ssl_verify_depth 1; - proxy_ssl_protocols TLSv1.3; - proxy_ssl_ciphers DEFAULT; - proxy_ssl_session_reuse on; - proxy_ssl_server_name on; - proxy_ssl_name ; - set $default_connection_header close; - rewrite $request_uri $request_uri; - rewrite $request_uri $request_uri; - proxy_connect_timeout 30s; - proxy_read_timeout 31s; - proxy_send_timeout 32s; - client_max_body_size 1m; - client_body_buffer_size 8k; - proxy_max_temp_file_size 1024m; - - proxy_buffering on; - proxy_buffers 8 4k; - proxy_buffer_size 4k; - proxy_busy_buffers_size 8k; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $vs_connection_header; - proxy_pass_request_headers off; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_hide_header Header; - proxy_pass_header Host; - proxy_ignore_headers Cache; - add_header Header-Name "Header Value" always; - proxy_pass http://test-upstream$request_uri; - proxy_next_upstream error timeout; - proxy_next_upstream_timeout 5s; - proxy_next_upstream_tries 0; - } - location @loc0 { - set $service ""; - status_zone ""; - - - error_page 400 500 =200 "@error_page_1"; - error_page 500 "@error_page_2"; - proxy_intercept_errors on; - set $default_connection_header close; - proxy_connect_timeout 30s; - proxy_read_timeout 31s; - proxy_send_timeout 32s; - client_max_body_size 1m; - - proxy_buffering off; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $vs_connection_header; - proxy_pass_request_headers off; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://coffee-v1; - proxy_next_upstream error timeout; - proxy_next_upstream_timeout 5s; - proxy_next_upstream_tries 0; - } - location @loc1 { - set $service ""; - status_zone ""; - - - set $default_connection_header close; - proxy_connect_timeout 30s; - proxy_read_timeout 31s; - proxy_send_timeout 32s; - client_max_body_size 1m; - - proxy_buffering off; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $vs_connection_header; - proxy_pass_request_headers off; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://coffee-v2; - proxy_next_upstream error timeout; - proxy_next_upstream_timeout 5s; - proxy_next_upstream_tries 0; - } - location @loc2 { - set $service ""; - status_zone ""; - - - error_page 400 = @grpc_internal; - error_page 401 = @grpc_unauthenticated; - error_page 403 = @grpc_permission_denied; - error_page 404 = @grpc_unimplemented; - error_page 429 = @grpc_unavailable; - error_page 502 = @grpc_unavailable; - error_page 503 = @grpc_unavailable; - error_page 504 = @grpc_unavailable; - error_page 405 = @grpc_internal; - error_page 408 = @grpc_deadline_exceeded; - error_page 413 = @grpc_resource_exhausted; - error_page 414 = @grpc_resource_exhausted; - error_page 415 = @grpc_internal; - error_page 426 = @grpc_internal; - error_page 495 = @grpc_unauthenticated; - error_page 496 = @grpc_unauthenticated; - error_page 497 = @grpc_internal; - error_page 500 = @grpc_internal; - error_page 501 = @grpc_internal; - set $default_connection_header close; - grpc_connect_timeout 30s; - grpc_read_timeout 31s; - grpc_send_timeout 32s; - client_max_body_size 1m; - - proxy_buffering off; - grpc_set_header X-Real-IP $remote_addr; - grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - grpc_set_header X-Forwarded-Host $host; - grpc_set_header X-Forwarded-Port $server_port; - grpc_set_header X-Forwarded-Proto $scheme; - grpc_pass grpc://coffee-v3; - grpc_next_upstream ; - grpc_next_upstream_timeout ; - grpc_next_upstream_tries 0; - } - location @match_loc_0 { - set $service ""; - status_zone ""; - - - set $default_connection_header close; - proxy_connect_timeout 30s; - proxy_read_timeout 31s; - proxy_send_timeout 32s; - client_max_body_size 1m; - - proxy_buffering off; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $vs_connection_header; - proxy_pass_request_headers off; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://coffee-v2; - proxy_next_upstream error timeout; - proxy_next_upstream_timeout 5s; - proxy_next_upstream_tries 0; - } - location @match_loc_default { - set $service ""; - status_zone ""; - - - set $default_connection_header close; - proxy_connect_timeout 30s; - proxy_read_timeout 31s; - proxy_send_timeout 32s; - client_max_body_size 1m; - - proxy_buffering off; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $vs_connection_header; - proxy_pass_request_headers off; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://coffee-v1; - proxy_next_upstream error timeout; - proxy_next_upstream_timeout 5s; - proxy_next_upstream_tries 0; - } - location /return { - set $service ""; - status_zone ""; - - - error_page 418 =200 "@return_0"; - proxy_intercept_errors on; - proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; - set $default_connection_header close; - } - - location @grpc_deadline_exceeded { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 4; - add_header grpc-message 'deadline exceeded'; - return 204; - } - - location @grpc_permission_denied { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 7; - add_header grpc-message 'permission denied'; - return 204; - } - - location @grpc_resource_exhausted { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 8; - add_header grpc-message 'resource exhausted'; - return 204; - } - - location @grpc_unimplemented { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 12; - add_header grpc-message unimplemented; - return 204; - } - - location @grpc_internal { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 13; - add_header grpc-message 'internal error'; - return 204; - } - - location @grpc_unavailable { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 14; - add_header grpc-message unavailable; - return 204; - } - - location @grpc_unauthenticated { - default_type application/grpc; - add_header content-type application/grpc; - add_header grpc-status 16; - add_header grpc-message unauthenticated; - return 204; - } - - - } --- diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index f101d6e3ae..551bebdba4 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -455,21 +455,6 @@ func TestExecuteVirtualServerTemplate_RendersTemplateWithClientBodyBufferSize(t t.Log(string(got)) } -func TestExecuteVirtualServerTemplate_RendersTemplateDefaultClientBodyBufferSize(t *testing.T) { - t.Parallel() - executor := newTmplExecutorNGINXPlus(t) - - got, err := executor.ExecuteVirtualServerTemplate(&virtualServerCfg) - if err != nil { - t.Error(err) - } - if !bytes.Contains(got, []byte("client_body_buffer_size 8k;")) { - t.Error("want `client_body_buffer_size 8k;` directive in generated template") - } - snaps.MatchSnapshot(t, string(got)) - t.Log(string(got)) -} - func TestExecuteVirtualServerTemplate_RendersOSSTemplateWithHTTP2On(t *testing.T) { t.Parallel() executor := newTmplExecutorNGINX(t) From a1829694a77ce165b218677017920104ee65a621 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 25 Nov 2025 15:38:53 +0000 Subject: [PATCH 5/6] Move validation to crd level --- .../bases/k8s.nginx.org_virtualserverroutes.yaml | 7 +++++-- config/crd/bases/k8s.nginx.org_virtualservers.yaml | 7 +++++-- deploy/crds.yaml | 14 ++++++++++---- docs/crd/k8s.nginx.org_virtualserverroutes.md | 2 +- docs/crd/k8s.nginx.org_virtualservers.md | 2 +- .../version2/__snapshots__/templates_test.snap | 1 - internal/configs/version2/templates_test.go | 1 - pkg/apis/configuration/v1/types.go | 6 +++++- 8 files changed, 27 insertions(+), 13 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml index 29b89844e8..74607fb286 100644 --- a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml @@ -879,8 +879,11 @@ spec: ConfigMap key.' type: string client-body-buffer-size: - description: Sets the size of the buffer used for reading the - client request body. + description: |- + ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: + 'k' for kilobytes or 'm' for megabytes. + Examples: "10m" or "512k". + pattern: ^[0-9]+[kmg]$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index 798948336b..7a925a696e 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -1068,8 +1068,11 @@ spec: ConfigMap key.' type: string client-body-buffer-size: - description: Sets the size of the buffer used for reading the - client request body. + description: |- + ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: + 'k' for kilobytes or 'm' for megabytes. + Examples: "10m" or "512k". + pattern: ^[0-9]+[kmg]$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request diff --git a/deploy/crds.yaml b/deploy/crds.yaml index d6aa0ce873..c8af505cf1 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1917,8 +1917,11 @@ spec: ConfigMap key.' type: string client-body-buffer-size: - description: Sets the size of the buffer used for reading the - client request body. + description: |- + ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: + 'k' for kilobytes or 'm' for megabytes. + Examples: "10m" or "512k". + pattern: ^[0-9]+[kmg]$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request @@ -3340,8 +3343,11 @@ spec: ConfigMap key.' type: string client-body-buffer-size: - description: Sets the size of the buffer used for reading the - client request body. + description: |- + ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: + 'k' for kilobytes or 'm' for megabytes. + Examples: "10m" or "512k". + pattern: ^[0-9]+[kmg]$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request diff --git a/docs/crd/k8s.nginx.org_virtualserverroutes.md b/docs/crd/k8s.nginx.org_virtualserverroutes.md index bd7c136f31..3874555213 100644 --- a/docs/crd/k8s.nginx.org_virtualserverroutes.md +++ b/docs/crd/k8s.nginx.org_virtualserverroutes.md @@ -169,7 +169,7 @@ The `.spec` object supports the following fields: | `upstreams[].buffers.number` | `integer` | Configures the number of buffers. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].buffers.size` | `string` | Configures the size of a buffer. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].busy-buffers-size` | `string` | Sets the size of the buffers used for reading a response from the upstream server when the proxy_buffering is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' | -| `upstreams[].client-body-buffer-size` | `string` | Sets the size of the buffer used for reading the client request body. | +| `upstreams[].client-body-buffer-size` | `string` | ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: 'k' for kilobytes or 'm' for megabytes. Examples: "10m" or "512k". | | `upstreams[].client-max-body-size` | `string` | Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. | | `upstreams[].connect-timeout` | `string` | The timeout for establishing a connection with an upstream server. The default is specified in the proxy-connect-timeout ConfigMap key. | | `upstreams[].fail-timeout` | `string` | The time during which the specified number of unsuccessful attempts to communicate with an upstream server should happen to consider the server unavailable. The default is set in the fail-timeout ConfigMap key. | diff --git a/docs/crd/k8s.nginx.org_virtualservers.md b/docs/crd/k8s.nginx.org_virtualservers.md index 4366b8824b..cffd599ffd 100644 --- a/docs/crd/k8s.nginx.org_virtualservers.md +++ b/docs/crd/k8s.nginx.org_virtualservers.md @@ -204,7 +204,7 @@ The `.spec` object supports the following fields: | `upstreams[].buffers.number` | `integer` | Configures the number of buffers. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].buffers.size` | `string` | Configures the size of a buffer. The default is set in the proxy-buffers ConfigMap key. | | `upstreams[].busy-buffers-size` | `string` | Sets the size of the buffers used for reading a response from the upstream server when the proxy_buffering is enabled. The default is set in the proxy-busy-buffers-size ConfigMap key.' | -| `upstreams[].client-body-buffer-size` | `string` | Sets the size of the buffer used for reading the client request body. | +| `upstreams[].client-body-buffer-size` | `string` | ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: 'k' for kilobytes or 'm' for megabytes. Examples: "10m" or "512k". | | `upstreams[].client-max-body-size` | `string` | Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. | | `upstreams[].connect-timeout` | `string` | The timeout for establishing a connection with an upstream server. The default is specified in the proxy-connect-timeout ConfigMap key. | | `upstreams[].fail-timeout` | `string` | The time during which the specified number of unsuccessful attempts to communicate with an upstream server should happen to consider the server unavailable. The default is set in the fail-timeout ConfigMap key. | diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index a470046f6a..706165c222 100644 --- a/internal/configs/version2/__snapshots__/templates_test.snap +++ b/internal/configs/version2/__snapshots__/templates_test.snap @@ -3102,7 +3102,6 @@ server { proxy_read_timeout 31s; proxy_send_timeout 32s; client_max_body_size 1m; - client_body_buffer_size 8k; proxy_max_temp_file_size 1024m; proxy_buffering on; diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 551bebdba4..79943d57c7 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -1609,7 +1609,6 @@ var ( ProxyReadTimeout: "31s", ProxySendTimeout: "32s", ClientMaxBodySize: "1m", - ClientBodyBufferSize: "8k", ProxyBuffering: true, ProxyBuffers: "8 4k", ProxyBufferSize: "4k", diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index a2fdfcbc91..0121b8a08c 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -157,7 +157,11 @@ type Upstream struct { ProxyBusyBuffersSize string `json:"busy-buffers-size"` // Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. ClientMaxBodySize string `json:"client-max-body-size"` - // Sets the size of the buffer used for reading the client request body. + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Pattern=`^[0-9]+[kmg]$` + // ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: + // 'k' for kilobytes or 'm' for megabytes. + // Examples: "10m" or "512k". ClientBodyBufferSize string `json:"client-body-buffer-size"` // The TLS configuration for the Upstream. TLS UpstreamTLS `json:"tls"` From 352622b3d4a2adfe215c037019136c6f19af562f Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 25 Nov 2025 16:09:52 +0000 Subject: [PATCH 6/6] remove validation that is no longer being used --- .../bases/k8s.nginx.org_virtualserverroutes.yaml | 2 +- .../crd/bases/k8s.nginx.org_virtualservers.yaml | 2 +- deploy/crds.yaml | 4 ++-- pkg/apis/configuration/v1/types.go | 2 +- .../configuration/validation/virtualserver.go | 1 - .../validation/virtualserver_test.go | 15 --------------- 6 files changed, 5 insertions(+), 21 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml index 74607fb286..f8105b7dcd 100644 --- a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml @@ -883,7 +883,7 @@ spec: ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: 'k' for kilobytes or 'm' for megabytes. Examples: "10m" or "512k". - pattern: ^[0-9]+[kmg]$ + pattern: ^\d+[kKmM]?$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index 7a925a696e..677beb84aa 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -1072,7 +1072,7 @@ spec: ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: 'k' for kilobytes or 'm' for megabytes. Examples: "10m" or "512k". - pattern: ^[0-9]+[kmg]$ + pattern: ^\d+[kKmM]?$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request diff --git a/deploy/crds.yaml b/deploy/crds.yaml index c8af505cf1..3b47784561 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1921,7 +1921,7 @@ spec: ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: 'k' for kilobytes or 'm' for megabytes. Examples: "10m" or "512k". - pattern: ^[0-9]+[kmg]$ + pattern: ^\d+[kKmM]?$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request @@ -3347,7 +3347,7 @@ spec: ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: 'k' for kilobytes or 'm' for megabytes. Examples: "10m" or "512k". - pattern: ^[0-9]+[kmg]$ + pattern: ^\d+[kKmM]?$ type: string client-max-body-size: description: Sets the maximum allowed size of the client request diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 0121b8a08c..12be8b7213 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -158,7 +158,7 @@ type Upstream struct { // Sets the maximum allowed size of the client request body. The default is set in the client-max-body-size ConfigMap key. ClientMaxBodySize string `json:"client-max-body-size"` // +kubebuilder:validation:Optional - // +kubebuilder:validation:Pattern=`^[0-9]+[kmg]$` + // +kubebuilder:validation:Pattern=`^\d+[kKmM]?$` // ClientBodyBufferSize sets the size of the buffer used for reading the client request body. Must be specified as a number followed by: // 'k' for kilobytes or 'm' for megabytes. // Examples: "10m" or "512k". diff --git a/pkg/apis/configuration/validation/virtualserver.go b/pkg/apis/configuration/validation/virtualserver.go index b8e5e57cac..e5edfff294 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -634,7 +634,6 @@ func (vsv *VirtualServerValidator) validateUpstreams(upstreams []v1.Upstream, fi allErrs = append(allErrs, validatePositiveIntOrZeroFromPointer(u.Keepalive, idxPath.Child("keepalive"))...) allErrs = append(allErrs, validatePositiveIntOrZeroFromPointer(u.MaxConns, idxPath.Child("max-conns"))...) allErrs = append(allErrs, validateOffset(u.ClientMaxBodySize, idxPath.Child("client-max-body-size"))...) - allErrs = append(allErrs, validateSize(u.ClientBodyBufferSize, idxPath.Child("client-body-buffer-size"))...) allErrs = append(allErrs, validateUpstreamHealthCheck(u.HealthCheck, u.Type, idxPath.Child("healthCheck"))...) allErrs = append(allErrs, validateTime(u.SlowStart, idxPath.Child("slow-start"))...) allErrs = append(allErrs, validateBuffer(u.ProxyBuffers, idxPath.Child("buffers"))...) diff --git a/pkg/apis/configuration/validation/virtualserver_test.go b/pkg/apis/configuration/validation/virtualserver_test.go index 27d84f9d53..3a2c2edcda 100644 --- a/pkg/apis/configuration/validation/virtualserver_test.go +++ b/pkg/apis/configuration/validation/virtualserver_test.go @@ -550,7 +550,6 @@ func TestValidateUpstreams(t *testing.T) { ProxyNextUpstreamTries: 5, MaxConns: createPointerFromInt(16), Type: "grpc", - ClientBodyBufferSize: "16k", }, { Name: "upstream2", @@ -744,20 +743,6 @@ func TestValidateUpstreamsFails(t *testing.T) { }, msg: "invalid value for ClientMaxBodySize", }, - { - upstreams: []v1.Upstream{ - { - Name: "upstream1", - Service: "test-1", - Port: 80, - ClientBodyBufferSize: "10hello", - }, - }, - expectedUpstreamNames: map[string]sets.Empty{ - "upstream1": {}, - }, - msg: "invalid value for ClientBodyBufferSize", - }, { upstreams: []v1.Upstream{ {