diff --git a/internal/configs/configmaps.go b/internal/configs/configmaps.go index 2b18258cd..bb8db2d0d 100644 --- a/internal/configs/configmaps.go +++ b/internal/configs/configmaps.go @@ -359,10 +359,14 @@ func ParseConfigMap(ctx context.Context, cfgm *v1.ConfigMap, nginxPlus bool, has if virtualServerTemplate, exists := cfgm.Data["virtualserver-template"]; exists { cfgParams.VirtualServerTemplate = &virtualServerTemplate + } else { + cfgParams.VirtualServerTemplate = nil } if transportServerTemplate, exists := cfgm.Data["transportserver-template"]; exists { cfgParams.TransportServerTemplate = &transportServerTemplate + } else { + cfgParams.TransportServerTemplate = nil } if mainStreamSnippets, exists := GetMapKeyAsStringSlice(cfgm.Data, "stream-snippets", cfgm, "\n"); exists { diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index fd6982376..9c00d63a8 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -1318,7 +1318,7 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, resources Extende return allWarnings, fmt.Errorf("error when parsing the main template: %w", err) } } else { - // Reverse to default main template parsed at NIC startup. + // Reverse to default Main template parsed at NIC startup. cnf.templateExecutor.UseOriginalMainTemplate() } @@ -1327,6 +1327,9 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, resources Extende if err != nil { return allWarnings, fmt.Errorf("error when parsing the ingress template: %w", err) } + } else { + // Reverse to default Ingress template parsed at NIC startup. + cnf.templateExecutor.UseOriginalIngressTemplate() } if cfgParams.VirtualServerTemplate != nil { @@ -1334,6 +1337,9 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, resources Extende if err != nil { return allWarnings, fmt.Errorf("error when parsing the VirtualServer template: %w", err) } + } else { + // Reverse to default TransportServer template parsed at NIC startup. + cnf.templateExecutorV2.UseOriginalVStemplate() } if cfgParams.TransportServerTemplate != nil { @@ -1341,6 +1347,9 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, resources Extende if err != nil { return allWarnings, fmt.Errorf("error when parsing the TransportServer template: %w", err) } + } else { + // Reverse to default TransportServer template parsed at NIC startup. + cnf.templateExecutorV2.UseOriginalTStemplate() } mainCfg := GenerateNginxMainConfig(cnf.staticCfgParams, cfgParams) diff --git a/internal/configs/configurator_test.go b/internal/configs/configurator_test.go index 058c2e690..1e13034db 100644 --- a/internal/configs/configurator_test.go +++ b/internal/configs/configurator_test.go @@ -175,6 +175,86 @@ func TestConfiguratorUpdatesConfigWithCustomIngressTemplate(t *testing.T) { } } +func TestConfigratorUpdatesConfigWithCustomVStemplate(t *testing.T) { + t.Parallel() + + cnf := createTestConfigurator(t) + warnings, err := cnf.UpdateConfig(&ConfigParams{ + VirtualServerTemplate: &customTestVStemplate, + }, ExtendedResources{}) + if err != nil { + t.Fatal(err) + } + if len(warnings) != 0 { + t.Fatalf("Got warning when updating config: %+v\n", warnings) + } + + got := *cnf.CfgParams.VirtualServerTemplate + want := customTestVStemplate + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestConfiguratorUpdatesConfigWithNilCustomVSemplate(t *testing.T) { + t.Parallel() + + cnf := createTestConfigurator(t) + warnings, err := cnf.UpdateConfig(&ConfigParams{ + VirtualServerTemplate: nil, + }, ExtendedResources{}) + if err != nil { + t.Fatal(err) + } + if len(warnings) != 0 { + t.Errorf("Got warnings when updating config: %+v", warnings) + } + if cnf.CfgParams.VirtualServerTemplate != nil { + t.Errorf("Want nil VirtualServer template, got %+v\n", cnf.CfgParams.VirtualServerTemplate) + } +} + +func TestConfigratorUpdatesConfigWithCustomTStemplate(t *testing.T) { + t.Parallel() + + cnf := createTestConfigurator(t) + warnings, err := cnf.UpdateConfig(&ConfigParams{ + TransportServerTemplate: &customTestTStemplate, + }, ExtendedResources{}) + if err != nil { + t.Fatal(err) + } + if len(warnings) != 0 { + t.Fatalf("Got warning when updating config: %+v\n", warnings) + } + + got := *cnf.CfgParams.TransportServerTemplate + want := customTestTStemplate + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestConfiguratorUpdatesConfigWithNilCustomTStemplate(t *testing.T) { + t.Parallel() + + cnf := createTestConfigurator(t) + warnings, err := cnf.UpdateConfig(&ConfigParams{ + TransportServerTemplate: nil, + }, ExtendedResources{}) + if err != nil { + t.Fatal(err) + } + if len(warnings) != 0 { + t.Errorf("Got warnings when updating config: %+v", warnings) + } + if cnf.CfgParams.TransportServerTemplate != nil { + t.Errorf("Want nil TransportServer template, got %+v\n", cnf.CfgParams.TransportServerTemplate) + } +} + func TestAddOrUpdateIngress(t *testing.T) { t.Parallel() cnf := createTestConfigurator(t) @@ -1853,7 +1933,7 @@ func createTransportServerExWithHostNoTLSPassthrough() TransportServerEx { } var ( - // custom test Main Template represents a main-template passed via ConfigMap + // customTestMainTemplate represents a custom Main template passed via ConfigMap customTestMainTemplate = `# TEST NEW MAIN TEMPLATE {{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version1.MainConfig*/ -}} worker_processes {{.WorkerProcesses}}; @@ -2222,7 +2302,7 @@ mgmt { {{- end}} ` - // custom test Ingress Template represents an ingress-template passed via ConfigMap + // customTestIngressTemplate represents a custom Ingress template passed via ConfigMap customTestIngressTemplate = `# TEST NEW CUSTOM INGRESS TEMPLATE {{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version1.IngressNginxConfig*/ -}} # configuration for {{.Ingress.Namespace}}/{{.Ingress.Name}} @@ -2496,4 +2576,807 @@ server { {{- end}} }{{end}} ` + + // customTestVStemplate represents the custom VirtualServer template passed via ConfigMap + customTestVStemplate = `# TEST CUSTOM VIRTUALSERVER TEMPLATE +{{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version2.VirtualServerConfig*/ -}} +{{ range $u := .Upstreams }} +upstream {{ $u.Name }} { + zone {{ $u.Name }} {{ if ne $u.UpstreamZoneSize "0" }}{{ $u.UpstreamZoneSize }}{{ else }}512k{{ end }}; + {{- if $u.LBMethod }} + {{ $u.LBMethod }}; + {{- end }} + + {{- range $s := $u.Servers }} + server {{ $s.Address }} max_fails={{ $u.MaxFails }} fail_timeout={{ $u.FailTimeout }}{{ if $u.SlowStart }} slow_start={{ $u.SlowStart }}{{ end }} max_conns={{ $u.MaxConns }}{{ if $u.Resolve }} resolve{{ end }}; + {{- end }} + + {{- range $b := $u.BackupServers }} + server {{ $b.Address }} backup resolve; + {{- end }} + + {{- if $u.Keepalive }} + keepalive {{ $u.Keepalive }}; + {{- end }} + + {{- if $u.Queue }} + queue {{ $u.Queue.Size }} timeout={{ $u.Queue.Timeout }}; + {{- end }} + + {{- with $u.SessionCookie }} + {{- if .Enable }} + sticky cookie {{ .Name }}{{ if .Expires }} expires={{ .Expires }}{{ end }}{{ if .Domain }} domain={{ .Domain }}{{ end }}{{ if .HTTPOnly }} httponly{{ end }}{{ if .SameSite}} samesite={{.SameSite | toLower }}{{ end }}{{ if .Secure }} secure{{ end }}{{ if .Path }} path={{ .Path }}{{ end }}; + {{- end }} + {{- end }} + + {{ if $u.NTLM }}ntlm;{{ end }} +} +{{ end }} + +{{- range $kvz := .KeyValZones }} +keyval_zone zone={{ $kvz.Name }}:{{ $kvz.Size}} state={{ $kvz.State }}; +{{- end }} + +{{- range $kv := .KeyVals }} +keyval {{ $kv.Key}} {{ $kv.Variable}} zone={{ $kv.ZoneName }}; +{{- end }} + +{{- range $sc := .SplitClients }} +split_clients {{ $sc.Source }} {{ $sc.Variable }} { + {{- range $d := $sc.Distributions }} + {{ $d.Weight }} {{ $d.Value }}; + {{- end }} +} +{{- end }} + +{{- range $m := .Maps }} +map {{ $m.Source }} {{ $m.Variable }} { + {{- range $p := $m.Parameters }} + {{ $p.Value }} {{ $p.Result }}; + {{- end }} +} +{{- end }} + +{{- range $snippet := .HTTPSnippets }} +{{ $snippet }} +{{- end }} + +{{- range $z := .LimitReqZones }} +limit_req_zone {{ $z.Key }} zone={{ $z.ZoneName }}:{{ $z.ZoneSize }} rate={{ $z.Rate }}; +{{- end }} + +{{- range $m := .StatusMatches }} +match {{ $m.Name }} { + status {{ $m.Code }}; +} +{{- end }} + +{{- $s := .Server }} + +{{- with $s.JWKSAuthEnabled }} +proxy_cache_path /var/cache/nginx/jwks_uri_{{$s.VSName}} levels=1 keys_zone=jwks_uri_{{$s.VSName}}:1m max_size=10m; +{{- end }} + +server { + {{- if $s.Gunzip }}gunzip on;{{end}} + {{ makeHTTPListener $s | printf }} + + server_name {{ $s.ServerName }}; + status_zone {{ $s.StatusZone }}; + set $resource_type "virtualserver"; + set $resource_name "{{$s.VSName}}"; + set $resource_namespace "{{$s.VSNamespace}}"; + + {{- with $oidc := $s.OIDC }} + include oidc/oidc.conf; + + set $oidc_pkce_enable 0; + set $oidc_logout_redirect "{{ $oidc.PostLogoutRedirectURI }}"; + set $oidc_hmac_key "{{ $s.VSName }}"; + set $zone_sync_leeway {{ $oidc.ZoneSyncLeeway }}; + + set $oidc_authz_endpoint "{{ $oidc.AuthEndpoint }}"; + set $oidc_authz_extra_args "{{ $oidc.AuthExtraArgs }}"; + set $oidc_token_endpoint "{{ $oidc.TokenEndpoint }}"; + set $oidc_end_session_endpoint "{{ $oidc.EndSessionEndpoint }}"; + set $oidc_jwt_keyfile "{{ $oidc.JwksURI }}"; + set $oidc_scopes "{{ $oidc.Scope }}"; + set $oidc_client "{{ $oidc.ClientID }}"; + set $oidc_client_secret "{{ $oidc.ClientSecret }}"; + set $redir_location "{{ $oidc.RedirectURI }}"; + {{- end }} + + {{- with $ssl := $s.SSL }} + {{- if $s.TLSPassthrough }} + listen unix:/var/lib/nginx/passthrough-https.sock proxy_protocol; + set_real_ip_from unix:; + real_ip_header proxy_protocol; + {{- else }} + {{ makeHTTPSListener $s | printf }} + {{- end }} + {{- if $ssl.HTTP2 }} + http2 on; + {{- end }} + + {{- if $ssl.RejectHandshake }} + ssl_reject_handshake on; + {{- else if $.SpiffeCerts }} + ssl_certificate {{ makeSecretPath "/etc/nginx/secrets/spiffe_cert.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath "/etc/nginx/secrets/spiffe_key.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- else }} + ssl_certificate {{ makeSecretPath $ssl.Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath $ssl.CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- else }} + {{- if $.SpiffeCerts }} + listen 443 ssl; + {{if not $s.DisableIPV6}}listen [::]:443 ssl;{{end}} + ssl_certificate {{ makeSecretPath "/etc/nginx/secrets/spiffe_cert.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath "/etc/nginx/secrets/spiffe_key.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- end }} + + {{- with $s.IngressMTLS }} + ssl_client_certificate {{ .ClientCert }}; + {{- if .ClientCrl }} + ssl_crl {{ .ClientCrl }}; + {{- end }} + ssl_verify_client {{ .VerifyClient }}; + ssl_verify_depth {{ .VerifyDepth }}; + {{- end }} + + {{- with $s.TLSRedirect }} + if ({{ .BasedOn }} = 'http') { + return {{ .Code }} https://$host$request_uri; + } + {{- end }} + + server_tokens "{{ $s.ServerTokens }}"; + + {{- range $setRealIPFrom := $s.SetRealIPFrom }} + set_real_ip_from {{ $setRealIPFrom }}; + {{- end }} + {{- if $s.RealIPHeader }} + real_ip_header {{ $s.RealIPHeader }}; + {{- end }} + {{- if $s.RealIPRecursive }} + real_ip_recursive on; + {{- end }} + + {{- with $s.PoliciesErrorReturn }} + return {{ .Code }}; + {{- end }} + + {{- range $allow := $s.Allow }} + allow {{ $allow }}; + {{- end }} + {{- if gt (len $s.Allow) 0 }} + deny all; + {{- end }} + + {{- range $deny := $s.Deny }} + deny {{ $deny }}; + {{- end }} + {{- if gt (len $s.Deny) 0 }} + allow all; + {{- end }} + + {{- if $s.LimitReqOptions.DryRun }} + limit_req_dry_run on; + {{- end }} + + {{- with $level := $s.LimitReqOptions.LogLevel }} + limit_req_log_level {{ $level }}; + {{- end }} + + {{- with $code := $s.LimitReqOptions.RejectCode }} + limit_req_status {{ $code }}; + {{- end }} + + {{- range $rl := $s.LimitReqs }} + limit_req zone={{ $rl.ZoneName }}{{ if $rl.Burst }} burst={{ $rl.Burst }}{{ end }} + {{ if $rl.Delay }} delay={{ $rl.Delay }}{{ end }}{{ if $rl.NoDelay }} nodelay{{ end }}; + {{- end }} + + {{- with $s.JWTAuth }} + auth_jwt "{{ .Realm }}"{{ if .Token }} token={{ .Token }}{{ end }}; + {{ if .Secret}}auth_jwt_key_file {{ .Secret }};{{ end }} + {{- if .JwksURI.JwksHost }} + {{ if .KeyCache }}auth_jwt_key_cache {{ .KeyCache }};{{ end }} + auth_jwt_key_request /_jwks_uri_server_{{ .Key }}; + {{- end }} + {{- end }} + + {{- range $index, $element := $s.JWTAuthList }} + location = /_jwks_uri_server_{{ .Key }} { + internal; + proxy_method GET; + proxy_set_header Content-Length ""; + {{- if .KeyCache }} + proxy_cache jwks_uri_{{ $s.VSName }}; + proxy_cache_valid 200 12h; + {{- end }} + {{- with .JwksURI }} + proxy_set_header Host {{ .JwksHost }}; + set $idp_backend {{ .JwksHost }}; + proxy_pass {{ .JwksScheme}}://$idp_backend{{ if .JwksPort }}:{{ .JwksPort }}{{ end }}{{ .JwksPath }}; + {{- end }} + } + {{- end }} + + {{- if $s.APIKeyEnabled}} + location = /_validate_apikey_njs { + internal; + js_content apikey_auth.validate; + } + {{- end }} + + {{- with $s.BasicAuth }} + auth_basic {{ printf "%q" .Realm }}; + auth_basic_user_file {{ .Secret }}; + {{- end }} + + {{- with $s.EgressMTLS }} + {{- if .Certificate }} + proxy_ssl_certificate {{ makeSecretPath .Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + proxy_ssl_certificate_key {{ makeSecretPath .CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- if .TrustedCert }} + proxy_ssl_trusted_certificate {{ .TrustedCert }}; + {{- end }} + + proxy_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + proxy_ssl_verify_depth {{ .VerifyDepth }}; + proxy_ssl_protocols {{ .Protocols }}; + proxy_ssl_ciphers {{ .Ciphers }}; + proxy_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + proxy_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + proxy_ssl_name {{ .SSLName }}; + {{- end }} + + {{- with $s.APIKey}} + js_var $header_query_value {{ makeHeaderQueryValue $s.APIKey | printf }}; + js_var $apikey_auth_local_map "{{ .MapName}}"; + js_var $apikey_auth_token $apikey_auth_hash; + auth_request /_validate_apikey_njs; + {{- end }} + + {{- with $s.WAF }} + app_protect_enable {{ .Enable }}; + {{ if .ApPolicy }} + app_protect_policy_file {{ .ApPolicy }}; + {{ end }} + + {{ if .ApBundle }} + app_protect_policy_file {{ .ApBundle }}; + {{ end }} + + {{ if .ApSecurityLogEnable }} + app_protect_security_log_enable on; + {{ range $logconf := .ApLogConf }} + app_protect_security_log {{ $logconf }}; + {{ end }} + {{ end }} + {{ end }} + + {{- with $s.Dos }} + app_protect_dos_enable {{ .Enable }}; + {{- if .Name }} + app_protect_dos_name "{{ .Name }}"; + {{- end }} + + {{- if .AllowListPath }} + app_protect_dos_access_file "{{ .AllowListPath }}"; + {{- end }} + + {{- if .ApDosPolicy }} + app_protect_dos_policy_file {{ .ApDosPolicy }}; + {{- end }} + + {{- if .ApDosSecurityLogEnable }} + app_protect_dos_security_log_enable on; + app_protect_dos_security_log {{ .ApDosLogConf }}; + {{- end }} + + {{- if .ApDosAccessLogDest }} + set $loggable '0'; + # app-protect-dos module will set it to '1' if a request doesn't pass the rate limit + access_log {{ .ApDosAccessLogDest }} log_dos if=$loggable; + {{- end }} + + {{- if .ApDosMonitorURI }} + {{- if or .ApDosMonitorProtocol .ApDosMonitorTimeout}} + app_protect_dos_monitor uri={{ .ApDosMonitorURI }}{{if .ApDosMonitorProtocol}} protocol={{.ApDosMonitorProtocol}}{{end}}{{if .ApDosMonitorTimeout}} timeout={{.ApDosMonitorTimeout}}{{end}}; + {{- else}} + app_protect_dos_monitor "{{ .ApDosMonitorURI }}"; + {{- end}} + {{- end}} + + {{- end }} + + {{- range $snippet := $s.Snippets }} + {{ $snippet }} + {{- end }} + + {{- range $l := $s.InternalRedirectLocations }} + location {{ $l.Path }} { + rewrite ^ {{ $l.Destination }} last; + } + {{- end }} + + {{- range $hc := $s.HealthChecks }} + location @hc-{{ $hc.Name }} { + {{ $proxyOrGRPC := "proxy" }}{{ if $hc.GRPCPass }}{{ $proxyOrGRPC = "grpc" }}{{ end }} + {{- range $n, $v := $hc.Headers }} + {{ $proxyOrGRPC }}_set_header {{ $n }} "{{ $v }}"; + {{- end }} + {{ $proxyOrGRPC }}_connect_timeout {{ $hc.ProxyConnectTimeout }}; + {{ $proxyOrGRPC }}_read_timeout {{ $hc.ProxyReadTimeout }}; + {{ $proxyOrGRPC }}_send_timeout {{ $hc.ProxySendTimeout }}; + {{- if $hc.GRPCPass }} + grpc_pass {{ $hc.GRPCPass }}; + {{- else }} + proxy_pass {{ $hc.ProxyPass }}; + {{- end }} + health_check{{- if $hc.URI }} uri={{ $hc.URI }} {{ end -}} + {{- if $hc.Port }} port={{ $hc.Port }} {{ end -}} + interval={{ $hc.Interval }} jitter={{ $hc.Jitter }} fails={{ $hc.Fails }} passes={{ $hc.Passes }} + {{- if $hc.Match }} match={{ $hc.Match }}{{- end -}} + {{- if $hc.Mandatory }} mandatory {{ end -}} + {{- if $hc.Persistent }} persistent {{ end -}} + {{- if not $hc.IsGRPC }} keepalive_time={{ $hc.KeepaliveTime }}{{ end -}} + {{- if $hc.GRPCPass }} type=grpc{{- if $hc.GRPCStatus }} grpc_status={{ $hc.GRPCStatus }}{{- end -}} + {{- if $hc.GRPCService }} grpc_service={{ $hc.GRPCService }}{{- end -}}{{ end -}}; + + } + {{- end }} + + {{- range $e := $s.ErrorPageLocations }} + location {{ $e.Name }} { + {{ if $e.DefaultType }} + default_type "{{ $e.DefaultType }}"; + {{ end }} + {{ range $h := $e.Headers }} + add_header {{ $h.Name }} "{{ $h.Value }}" always; + {{ end }} + # status code is ignored here, using 0 + return 0 "{{ $e.Return.Text }}"; + } + {{ end }} + + {{ range $l := $s.ReturnLocations }} + location {{ $l.Name }} { + default_type "{{ $l.DefaultType }}"; + {{ range $h := $l.Headers }} + add_header {{ $h.Name }} "{{ $h.Value }}" always; + {{ end }} + # status code is ignored here, using 0 + return 0 "{{ $l.Return.Text }}"; + } + {{ end }} + + {{ range $l := $s.Locations }} + location {{ $l.Path }} { + set $service "{{ $l.ServiceName }}"; + status_zone "{{ $l.ServiceName }}"; + {{- if $l.IsVSR }} + set $resource_type "virtualserverroute"; + set $resource_name "{{ $l.VSRName }}"; + set $resource_namespace "{{ $l.VSRNamespace }}"; + {{- end }} + {{- if $l.Internal }} + internal; + {{- end }} + {{- range $snippet := $l.Snippets }} + {{ $snippet }} + {{- end }} + + {{- with $l.PoliciesErrorReturn }} + return {{ .Code }}; + {{- end }} + + {{- range $allow := $l.Allow }} + allow {{ $allow }}; + {{- end }} + {{- if gt (len $l.Allow) 0 }} + deny all; + {{- end }} + + {{- range $deny := $l.Deny }} + deny {{ $deny }}; + {{- end }} + {{- if gt (len $l.Deny) 0 }} + allow all; + {{- end }} + + {{- if $l.LimitReqOptions.DryRun }} + limit_req_dry_run on; + {{- end }} + + {{- with $level := $l.LimitReqOptions.LogLevel }} + limit_req_log_level {{ $level }}; + {{- end }} + + {{- with $code := $l.LimitReqOptions.RejectCode }} + limit_req_status {{ $code }}; + {{- end }} + + {{- range $rl := $l.LimitReqs }} + limit_req zone={{ $rl.ZoneName }}{{ if $rl.Burst }} burst={{ $rl.Burst }}{{ end }} + {{ if $rl.Delay }} delay={{ $rl.Delay }}{{ end }}{{ if $rl.NoDelay }} nodelay{{ end }}; + {{- end }} + + {{- with $l.JWTAuth }} + auth_jwt "{{ .Realm }}"{{ if .Token }} token={{ .Token }}{{ end }}; + {{ if .Secret}}auth_jwt_key_file {{ .Secret }};{{ end }} + {{- if .JwksURI.JwksHost }} + {{ if .KeyCache }}auth_jwt_key_cache {{ .KeyCache }};{{ end }} + auth_jwt_key_request /_jwks_uri_server_{{ .Key }}; + {{- end }} + {{- end }} + + {{- with $l.BasicAuth }} + auth_basic {{ printf "%q" .Realm }}; + auth_basic_user_file {{ .Secret }}; + {{- end }} + + {{ $proxyOrGRPC := "proxy" }}{{ if $l.GRPCPass }}{{ $proxyOrGRPC = "grpc" }}{{ end }} + + {{- with $l.EgressMTLS }} + {{- if .Certificate }} + {{ $proxyOrGRPC }}_ssl_certificate {{ makeSecretPath .Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{ $proxyOrGRPC }}_ssl_certificate_key {{ makeSecretPath .CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{ if .TrustedCert }} + {{ $proxyOrGRPC }}_ssl_trusted_certificate {{ .TrustedCert }}; + {{- end }} + {{ $proxyOrGRPC }}_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + {{ $proxyOrGRPC }}_ssl_verify_depth {{ .VerifyDepth }}; + {{ $proxyOrGRPC }}_ssl_protocols {{ .Protocols }}; + {{ $proxyOrGRPC }}_ssl_ciphers {{ .Ciphers }}; + {{ $proxyOrGRPC }}_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + {{ $proxyOrGRPC }}_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + {{ $proxyOrGRPC }}_ssl_name {{ .SSLName }}; + {{- end }} + + {{- if $l.OIDC }} + auth_jwt "" token=$session_jwt; + error_page 401 = @do_oidc_flow; + auth_jwt_key_request /_jwks_uri; + {{- $proxyOrGRPC }}_set_header username $jwt_claim_sub; + {{- if $s.OIDC.AccessTokenEnable }} + {{ $proxyOrGRPC }}_set_header Authorization "Bearer $access_token"; + {{- end }} + {{- end }} + + + {{- with $l.APIKey}} + set $apikey_auth_local_map "{{ .MapName }}"; + set $header_query_value {{ makeHeaderQueryValue $l.APIKey | printf }}; + set $apikey_auth_token $apikey_auth_hash; + auth_request /_validate_apikey_njs; + {{- else }} + {{- with $s.APIKey }} + set $header_query_value {{ makeHeaderQueryValue $s.APIKey | printf }}; + {{- end }} + + {{- end }} + + {{- with $l.WAF }} + app_protect_enable {{ .Enable }}; + {{- if .ApPolicy }} + app_protect_policy_file {{ .ApPolicy }}; + {{- end }} + + {{- if .ApBundle }} + app_protect_policy_file {{ .ApBundle }}; + {{- end }} + + {{- if .ApSecurityLogEnable }} + app_protect_security_log_enable on; + {{- range $logconf := .ApLogConf }} + app_protect_security_log {{ $logconf }}; + {{- end }} + {{- end }} + {{- end }} + + {{- if $l.GRPCPass }} + 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; + {{- end }} + + {{- with $l.Dos }} + app_protect_dos_enable {{ .Enable }}; + + {{- if .Name }} + app_protect_dos_name "{{ .Name }}"; + {{- end }} + + {{- if .AllowListPath }} + app_protect_dos_access_file "{{ .AllowListPath }}"; + {{- end }} + + {{- if .ApDosPolicy }} + app_protect_dos_policy_file {{ .ApDosPolicy }}; + {{- end }} + + {{ if .ApDosSecurityLogEnable }} + app_protect_dos_security_log_enable on; + app_protect_dos_security_log {{ .ApDosLogConf }}; + {{ end }} + {{- if .ApDosAccessLogDest }} + set $loggable '0'; + # app-protect-dos module will set it to '1' if a request doesn't pass the rate limit + access_log {{ .ApDosAccessLogDest }} log_dos if=$loggable; + {{- end }} + + {{- if .ApDosMonitorURI }} + {{- if or .ApDosMonitorProtocol .ApDosMonitorTimeout}} + app_protect_dos_monitor uri={{ .ApDosMonitorURI }}{{if .ApDosMonitorProtocol}} protocol={{.ApDosMonitorProtocol}}{{end}}{{if .ApDosMonitorTimeout}} timeout={{.ApDosMonitorTimeout}}{{end}}; + {{- else}} + app_protect_dos_monitor "{{ .ApDosMonitorURI }}"; + {{- end}} + {{- end}} + {{- end }} + + {{- range $e := $l.ErrorPages }} + error_page {{ $e.Codes }} {{ if ne 0 $e.ResponseCode }}={{ $e.ResponseCode }}{{ end }} "{{ $e.Name }}"; + {{- end }} + + {{- if $l.ProxyInterceptErrors }} + {{ $proxyOrGRPC }}_intercept_errors on; + {{- end }} + + {{- if $l.InternalProxyPass }} + proxy_pass {{ $l.InternalProxyPass }}; + {{- end }} + set $default_connection_header {{ if $l.HasKeepalive }}""{{ else }}close{{ end }}; + {{- if or $l.ProxyPass $l.GRPCPass }} + {{- range $r := $l.Rewrites }} + rewrite {{ $r }}; + {{- end }} + {{ $proxyOrGRPC }}_connect_timeout {{ $l.ProxyConnectTimeout }}; + {{ $proxyOrGRPC }}_read_timeout {{ $l.ProxyReadTimeout }}; + {{ $proxyOrGRPC }}_send_timeout {{ $l.ProxySendTimeout }}; + client_max_body_size {{ $l.ClientMaxBodySize }}; + + {{- if $l.ProxyMaxTempFileSize }} + proxy_max_temp_file_size {{ $l.ProxyMaxTempFileSize }}; + {{- end }} + + proxy_buffering {{ if $l.ProxyBuffering }}on{{ else }}off{{ end }}; + {{- if $l.ProxyBuffers }} + proxy_buffers {{ $l.ProxyBuffers }}; + {{- end }} + {{- if $l.ProxyBufferSize }} + {{ $proxyOrGRPC }}_buffer_size {{ $l.ProxyBufferSize }}; + {{- end }} + {{- if not $l.GRPCPass }} + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers {{ if $l.ProxyPassRequestHeaders }}on{{ else }}off{{ end }}; + {{- end }} + + {{- $custom_headers := $l.ProxySetHeaders | headerListToCIMap }} + + {{- if not ($custom_headers | hasCIKey "X-Real-IP") }} + {{ $proxyOrGRPC }}_set_header X-Real-IP $remote_addr; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-For") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-Host") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-Host $host; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-Port") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-Port $server_port; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-Proto") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-Proto {{ with $s.TLSRedirect }}{{ .BasedOn }}{{ else }}$scheme{{ end }}; + {{- end }} + + {{- range $h := $l.ProxySetHeaders }} + {{ $proxyOrGRPC }}_set_header {{ $h.Name }} "{{ $h.Value }}"; + {{- end }} + + {{- range $h := $l.ProxyHideHeaders }} + {{ $proxyOrGRPC }}_hide_header {{ $h }}; + {{- end }} + {{- range $h := $l.ProxyPassHeaders }} + {{ $proxyOrGRPC }}_pass_header {{ $h }}; + {{- end }} + {{- with $l.ProxyIgnoreHeaders }} + {{ $proxyOrGRPC }}_ignore_headers {{ $l.ProxyIgnoreHeaders }}; + {{- end }} + {{- range $h := $l.AddHeaders }} + add_header {{ $h.Name }} "{{ $h.Value }}" {{ if $h.Always }}always{{ end }}; + {{- end }} + {{- if $.SpiffeClientCerts }} + {{ $proxyOrGRPC }}_ssl_certificate {{ makeSecretPath "/etc/nginx/secrets/spiffe_cert.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{ $proxyOrGRPC }}_ssl_certificate_key {{ makeSecretPath "/etc/nginx/secrets/spiffe_key.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{ $proxyOrGRPC }}_ssl_trusted_certificate /etc/nginx/secrets/spiffe_rootca.pem; + {{ $proxyOrGRPC }}_ssl_server_name on; + {{ $proxyOrGRPC }}_ssl_verify on; + {{ $proxyOrGRPC }}_ssl_verify_depth 25; + {{ $proxyOrGRPC }}_ssl_name {{ $l.ProxySSLName }}; + {{- end }} + {{- if $l.GRPCPass }} + grpc_pass {{ $l.GRPCPass }}; + {{- else }} + proxy_pass {{ $l.ProxyPass }}{{ $l.ProxyPassRewrite }}; + {{- end }} + {{ $proxyOrGRPC }}_next_upstream {{ $l.ProxyNextUpstream }}; + {{ $proxyOrGRPC }}_next_upstream_timeout {{ $l.ProxyNextUpstreamTimeout }}; + {{ $proxyOrGRPC }}_next_upstream_tries {{ $l.ProxyNextUpstreamTries }}; + {{- end }} + } + {{- end }} + + {{- with $ssl := $s.SSL }} + {{ if $ssl.HTTP2 }} + 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; + } + + {{ end }} + {{ end }} +}` + + // customTestTStemplate represents a custom TransportServer template passed via ConfigMap + customTestTStemplate = `# TEST CUSTOM TRANSPORTSERVER TEMPLATE +{{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version2.TransportServerConfig*/ -}} +{{- range $u := .Upstreams }} +upstream {{ $u.Name }} { + zone {{ $u.Name }} 512k; + {{- if $u.LoadBalancingMethod }} + {{ $u.LoadBalancingMethod }}; + {{- end }} + + {{- range $s := $u.Servers }} + server {{ $s.Address }} max_fails={{ $s.MaxFails }} fail_timeout={{ $s.FailTimeout }} max_conns={{ $s.MaxConnections }}{{ if $u.Resolve }} resolve{{ end }}; + {{- end }} + + {{- range $b := $u.BackupServers }} + server {{ $b.Address }} resolve backup; + {{- end }} +} +{{- end }} + +{{- range $snippet := .StreamSnippets }} +{{ $snippet }} +{{- end }} + +{{ with $m := .Match }} +match {{ $m.Name }} { + {{ if $m.Send }} + send "{{ $m.Send }}"; + {{ end }} + + {{ if $m.Expect }} + expect {{ $m.ExpectRegexModifier }} "{{ $m.Expect }}"; + {{ end }} +} +{{- end }} + +{{- $s := .Server }} +server { + {{- with $ssl := $s.SSL }} + {{- if $s.TLSPassthrough }} + listen {{ $s.UnixSocket }} proxy_protocol; + set_real_ip_from unix:; + {{- else }} + {{ makeTransportListener $s | printf }} + {{- with makeServerName $s }}{{ printf "\t%s" . }}{{- end }} + {{- end }} + + {{- if $ssl.Enabled }} + ssl_certificate {{ makeSecretPath $ssl.Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath $ssl.CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- end }} + + status_zone {{ $s.StatusZone }}; + + {{- if $s.ProxyRequests }} + proxy_requests {{ $s.ProxyRequests }}; + {{- end }} + {{- if $s.ProxyResponses }} + proxy_responses {{ $s.ProxyResponses }}; + {{- end }} + + {{- range $snippet := $s.ServerSnippets }} + {{ $snippet }} + {{- end }} + + proxy_pass {{ $s.ProxyPass }}; + + {{ if $s.HealthCheck }} + health_check interval={{ $s.HealthCheck.Interval }} {{ if $s.HealthCheck.Port }} port={{ $s.HealthCheck.Port }}{{ end }} + passes={{ $s.HealthCheck.Passes }} jitter={{ $s.HealthCheck.Jitter }} fails={{ $s.HealthCheck.Fails }}{{ if $s.UDP }} udp{{ end }}{{ if $s.HealthCheck.Match }} match={{ $s.HealthCheck.Match }}{{ end }}; + health_check_timeout {{ $s.HealthCheck.Timeout }}; + {{ end }} + + proxy_timeout {{ $s.ProxyTimeout }}; + proxy_connect_timeout {{ $s.ProxyConnectTimeout }}; + + {{- if $s.ProxyNextUpstream }} + proxy_next_upstream on; + proxy_next_upstream_timeout {{ $s.ProxyNextUpstreamTimeout }}; + proxy_next_upstream_tries {{ $s.ProxyNextUpstreamTries }}; + {{- end }} +}` ) diff --git a/internal/configs/version2/template_executor.go b/internal/configs/version2/template_executor.go index dabd3ff80..085e1e7b4 100644 --- a/internal/configs/version2/template_executor.go +++ b/internal/configs/version2/template_executor.go @@ -15,9 +15,11 @@ const tlsPassthroughHostsTemplateString = `# mapping between TLS Passthrough hos // TemplateExecutor executes NGINX configuration templates. type TemplateExecutor struct { - virtualServerTemplate *template.Template - transportServerTemplate *template.Template - tlsPassthroughHostsTemplate *template.Template + originalVirtualServerTemplate *template.Template + originalTrasportServerTemplate *template.Template + virtualServerTemplate *template.Template + transportServerTemplate *template.Template + tlsPassthroughHostsTemplate *template.Template } // NewTemplateExecutor creates a TemplateExecutor. @@ -40,9 +42,11 @@ func NewTemplateExecutor(virtualServerTemplatePath string, transportServerTempla } return &TemplateExecutor{ - virtualServerTemplate: vsTemplate, - transportServerTemplate: tsTemplate, - tlsPassthroughHostsTemplate: tlsPassthroughHostsTemplate, + originalVirtualServerTemplate: vsTemplate, + originalTrasportServerTemplate: tsTemplate, + virtualServerTemplate: vsTemplate, + transportServerTemplate: tsTemplate, + tlsPassthroughHostsTemplate: tlsPassthroughHostsTemplate, }, nil } @@ -53,7 +57,6 @@ func (te *TemplateExecutor) UpdateVirtualServerTemplate(templateString *string) return err } te.virtualServerTemplate = newTemplate - return nil } @@ -64,31 +67,45 @@ func (te *TemplateExecutor) UpdateTransportServerTemplate(templateString *string return err } te.transportServerTemplate = newTemplate - return nil } // ExecuteVirtualServerTemplate generates the content of an NGINX configuration file for a VirtualServer resource. func (te *TemplateExecutor) ExecuteVirtualServerTemplate(cfg *VirtualServerConfig) ([]byte, error) { var configBuffer bytes.Buffer - err := te.virtualServerTemplate.Execute(&configBuffer, cfg) - - return configBuffer.Bytes(), err + if err := te.virtualServerTemplate.Execute(&configBuffer, cfg); err != nil { + return nil, err + } + return configBuffer.Bytes(), nil } // ExecuteTransportServerTemplate generates the content of an NGINX configuration file for a TransportServer resource. func (te *TemplateExecutor) ExecuteTransportServerTemplate(cfg *TransportServerConfig) ([]byte, error) { var configBuffer bytes.Buffer - err := te.transportServerTemplate.Execute(&configBuffer, cfg) + if err := te.transportServerTemplate.Execute(&configBuffer, cfg); err != nil { + return nil, err + } + return configBuffer.Bytes(), nil +} + +// UseOriginalVStemplate updates template executor to +// use the original VS template parsed at startup. +func (te *TemplateExecutor) UseOriginalVStemplate() { + te.virtualServerTemplate = te.originalVirtualServerTemplate +} - return configBuffer.Bytes(), err +// UseOriginalTStemplate updates template executor to +// use the original TS template parsed at startup. +func (te *TemplateExecutor) UseOriginalTStemplate() { + te.transportServerTemplate = te.originalTrasportServerTemplate } // ExecuteTLSPassthroughHostsTemplate generates the content of an NGINX configuration file for mapping between // TLS Passthrough hosts and the corresponding unix sockets. func (te *TemplateExecutor) ExecuteTLSPassthroughHostsTemplate(cfg *TLSPassthroughHostsConfig) ([]byte, error) { var configBuffer bytes.Buffer - err := te.tlsPassthroughHostsTemplate.Execute(&configBuffer, cfg) - - return configBuffer.Bytes(), err + if err := te.tlsPassthroughHostsTemplate.Execute(&configBuffer, cfg); err != nil { + return nil, err + } + return configBuffer.Bytes(), nil } diff --git a/internal/configs/version2/template_executor_test.go b/internal/configs/version2/template_executor_test.go new file mode 100644 index 000000000..6a2c5953b --- /dev/null +++ b/internal/configs/version2/template_executor_test.go @@ -0,0 +1,906 @@ +package version2 + +import ( + "strings" + "testing" +) + +func TestTemplateExecutorUsesOriginalVStemplate(t *testing.T) { + t.Parallel() + + te := newTestTemplateExecutor(t) + + // Create default vsConfig + vsConfig, err := te.ExecuteVirtualServerTemplate(&virtualServerCfg) + if err != nil { + t.Fatal(err) + } + + prefix := "# TEST CUSTOM VIRTUALSERVER TEMPLATE" + if strings.HasPrefix(string(vsConfig), prefix) { + t.Fatalf("Virtual Server config starts with unwanted prefix: %s", prefix) + } + + err = te.UpdateVirtualServerTemplate(&customTestVStemplate) + if err != nil { + t.Fatal(err) + } + + // Execute updated template and verify the prefix is included in the template. + vsConfig, err = te.ExecuteVirtualServerTemplate(&virtualServerCfg) + if err != nil { + t.Fatal(err) + } + + if !strings.HasPrefix(string(vsConfig), prefix) { + t.Fatalf("Virtual Server config does not start with prefix: %s", prefix) + } + + // Revert to the original VS template + te.UseOriginalVStemplate() + + vsConfig, err = te.ExecuteVirtualServerTemplate(&virtualServerCfg) + if err != nil { + t.Fatal(err) + } + + if strings.HasPrefix(string(vsConfig), prefix) { + t.Errorf("Virtual Serwer config starts with invalid prefix: %s", prefix) + } + t.Logf("\n%s\n", string(vsConfig)) +} + +func TestTemplateExecutorUsesOriginalTStemplate(t *testing.T) { + t.Parallel() + + te := newTestTemplateExecutor(t) + tsConfig, err := te.ExecuteTransportServerTemplate(&transportServerCfg) + if err != nil { + t.Fatal(err) + } + + prefix := "# TEST CUSTOM TRANSPORTSERVER TEMPLATE" + + if strings.HasPrefix(string(tsConfig), prefix) { + t.Fatalf("Transport Server config starts with unwanted prefix: %s", prefix) + } + + err = te.UpdateTransportServerTemplate(&customTestTStemplate) + if err != nil { + t.Fatal(err) + } + + tsConfig, err = te.ExecuteTransportServerTemplate(&transportServerCfg) + if err != nil { + t.Fatal(err) + } + + if !strings.HasPrefix(string(tsConfig), prefix) { + t.Fatalf("Transport Server config does not start with prefix: %s", prefix) + } + + // Revert to the original TransportServer template + te.UseOriginalTStemplate() + + tsConfig, err = te.ExecuteTransportServerTemplate(&transportServerCfg) + if err != nil { + t.Fatal(err) + } + + if strings.HasPrefix(string(tsConfig), prefix) { + t.Errorf("Ingress config starts with unwanted the prefix: %s\n", prefix) + } + t.Logf("\n%s\n", string(tsConfig)) +} + +func newTestTemplateExecutor(t *testing.T) *TemplateExecutor { + t.Helper() + te, err := NewTemplateExecutor("nginx-plus.virtualserver.tmpl", "nginx-plus.transportserver.tmpl") + if err != nil { + t.Fatal(err) + } + return te +} + +// custom VStemplate represents the virtualserver template passed via ConfigMap +var customTestVStemplate = `# TEST CUSTOM VIRTUALSERVER TEMPLATE +{{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version2.VirtualServerConfig*/ -}} +{{ range $u := .Upstreams }} +upstream {{ $u.Name }} { + zone {{ $u.Name }} {{ if ne $u.UpstreamZoneSize "0" }}{{ $u.UpstreamZoneSize }}{{ else }}512k{{ end }}; + {{- if $u.LBMethod }} + {{ $u.LBMethod }}; + {{- end }} + + {{- range $s := $u.Servers }} + server {{ $s.Address }} max_fails={{ $u.MaxFails }} fail_timeout={{ $u.FailTimeout }}{{ if $u.SlowStart }} slow_start={{ $u.SlowStart }}{{ end }} max_conns={{ $u.MaxConns }}{{ if $u.Resolve }} resolve{{ end }}; + {{- end }} + + {{- range $b := $u.BackupServers }} + server {{ $b.Address }} backup resolve; + {{- end }} + + {{- if $u.Keepalive }} + keepalive {{ $u.Keepalive }}; + {{- end }} + + {{- if $u.Queue }} + queue {{ $u.Queue.Size }} timeout={{ $u.Queue.Timeout }}; + {{- end }} + + {{- with $u.SessionCookie }} + {{- if .Enable }} + sticky cookie {{ .Name }}{{ if .Expires }} expires={{ .Expires }}{{ end }}{{ if .Domain }} domain={{ .Domain }}{{ end }}{{ if .HTTPOnly }} httponly{{ end }}{{ if .SameSite}} samesite={{.SameSite | toLower }}{{ end }}{{ if .Secure }} secure{{ end }}{{ if .Path }} path={{ .Path }}{{ end }}; + {{- end }} + {{- end }} + + {{ if $u.NTLM }}ntlm;{{ end }} +} +{{ end }} + +{{- range $kvz := .KeyValZones }} +keyval_zone zone={{ $kvz.Name }}:{{ $kvz.Size}} state={{ $kvz.State }}; +{{- end }} + +{{- range $kv := .KeyVals }} +keyval {{ $kv.Key}} {{ $kv.Variable}} zone={{ $kv.ZoneName }}; +{{- end }} + +{{- range $sc := .SplitClients }} +split_clients {{ $sc.Source }} {{ $sc.Variable }} { + {{- range $d := $sc.Distributions }} + {{ $d.Weight }} {{ $d.Value }}; + {{- end }} +} +{{- end }} + +{{- range $m := .Maps }} +map {{ $m.Source }} {{ $m.Variable }} { + {{- range $p := $m.Parameters }} + {{ $p.Value }} {{ $p.Result }}; + {{- end }} +} +{{- end }} + +{{- range $snippet := .HTTPSnippets }} +{{ $snippet }} +{{- end }} + +{{- range $z := .LimitReqZones }} +limit_req_zone {{ $z.Key }} zone={{ $z.ZoneName }}:{{ $z.ZoneSize }} rate={{ $z.Rate }}; +{{- end }} + +{{- range $m := .StatusMatches }} +match {{ $m.Name }} { + status {{ $m.Code }}; +} +{{- end }} + +{{- $s := .Server }} + +{{- with $s.JWKSAuthEnabled }} +proxy_cache_path /var/cache/nginx/jwks_uri_{{$s.VSName}} levels=1 keys_zone=jwks_uri_{{$s.VSName}}:1m max_size=10m; +{{- end }} + +server { + {{- if $s.Gunzip }}gunzip on;{{end}} + {{ makeHTTPListener $s | printf }} + + server_name {{ $s.ServerName }}; + status_zone {{ $s.StatusZone }}; + set $resource_type "virtualserver"; + set $resource_name "{{$s.VSName}}"; + set $resource_namespace "{{$s.VSNamespace}}"; + + {{- with $oidc := $s.OIDC }} + include oidc/oidc.conf; + + set $oidc_pkce_enable 0; + set $oidc_logout_redirect "{{ $oidc.PostLogoutRedirectURI }}"; + set $oidc_hmac_key "{{ $s.VSName }}"; + set $zone_sync_leeway {{ $oidc.ZoneSyncLeeway }}; + + set $oidc_authz_endpoint "{{ $oidc.AuthEndpoint }}"; + set $oidc_authz_extra_args "{{ $oidc.AuthExtraArgs }}"; + set $oidc_token_endpoint "{{ $oidc.TokenEndpoint }}"; + set $oidc_end_session_endpoint "{{ $oidc.EndSessionEndpoint }}"; + set $oidc_jwt_keyfile "{{ $oidc.JwksURI }}"; + set $oidc_scopes "{{ $oidc.Scope }}"; + set $oidc_client "{{ $oidc.ClientID }}"; + set $oidc_client_secret "{{ $oidc.ClientSecret }}"; + set $redir_location "{{ $oidc.RedirectURI }}"; + {{- end }} + + {{- with $ssl := $s.SSL }} + {{- if $s.TLSPassthrough }} + listen unix:/var/lib/nginx/passthrough-https.sock proxy_protocol; + set_real_ip_from unix:; + real_ip_header proxy_protocol; + {{- else }} + {{ makeHTTPSListener $s | printf }} + {{- end }} + {{- if $ssl.HTTP2 }} + http2 on; + {{- end }} + + {{- if $ssl.RejectHandshake }} + ssl_reject_handshake on; + {{- else if $.SpiffeCerts }} + ssl_certificate {{ makeSecretPath "/etc/nginx/secrets/spiffe_cert.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath "/etc/nginx/secrets/spiffe_key.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- else }} + ssl_certificate {{ makeSecretPath $ssl.Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath $ssl.CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- else }} + {{- if $.SpiffeCerts }} + listen 443 ssl; + {{if not $s.DisableIPV6}}listen [::]:443 ssl;{{end}} + ssl_certificate {{ makeSecretPath "/etc/nginx/secrets/spiffe_cert.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath "/etc/nginx/secrets/spiffe_key.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- end }} + + {{- with $s.IngressMTLS }} + ssl_client_certificate {{ .ClientCert }}; + {{- if .ClientCrl }} + ssl_crl {{ .ClientCrl }}; + {{- end }} + ssl_verify_client {{ .VerifyClient }}; + ssl_verify_depth {{ .VerifyDepth }}; + {{- end }} + + {{- with $s.TLSRedirect }} + if ({{ .BasedOn }} = 'http') { + return {{ .Code }} https://$host$request_uri; + } + {{- end }} + + server_tokens "{{ $s.ServerTokens }}"; + + {{- range $setRealIPFrom := $s.SetRealIPFrom }} + set_real_ip_from {{ $setRealIPFrom }}; + {{- end }} + {{- if $s.RealIPHeader }} + real_ip_header {{ $s.RealIPHeader }}; + {{- end }} + {{- if $s.RealIPRecursive }} + real_ip_recursive on; + {{- end }} + + {{- with $s.PoliciesErrorReturn }} + return {{ .Code }}; + {{- end }} + + {{- range $allow := $s.Allow }} + allow {{ $allow }}; + {{- end }} + {{- if gt (len $s.Allow) 0 }} + deny all; + {{- end }} + + {{- range $deny := $s.Deny }} + deny {{ $deny }}; + {{- end }} + {{- if gt (len $s.Deny) 0 }} + allow all; + {{- end }} + + {{- if $s.LimitReqOptions.DryRun }} + limit_req_dry_run on; + {{- end }} + + {{- with $level := $s.LimitReqOptions.LogLevel }} + limit_req_log_level {{ $level }}; + {{- end }} + + {{- with $code := $s.LimitReqOptions.RejectCode }} + limit_req_status {{ $code }}; + {{- end }} + + {{- range $rl := $s.LimitReqs }} + limit_req zone={{ $rl.ZoneName }}{{ if $rl.Burst }} burst={{ $rl.Burst }}{{ end }} + {{ if $rl.Delay }} delay={{ $rl.Delay }}{{ end }}{{ if $rl.NoDelay }} nodelay{{ end }}; + {{- end }} + + {{- with $s.JWTAuth }} + auth_jwt "{{ .Realm }}"{{ if .Token }} token={{ .Token }}{{ end }}; + {{ if .Secret}}auth_jwt_key_file {{ .Secret }};{{ end }} + {{- if .JwksURI.JwksHost }} + {{ if .KeyCache }}auth_jwt_key_cache {{ .KeyCache }};{{ end }} + auth_jwt_key_request /_jwks_uri_server_{{ .Key }}; + {{- end }} + {{- end }} + + {{- range $index, $element := $s.JWTAuthList }} + location = /_jwks_uri_server_{{ .Key }} { + internal; + proxy_method GET; + proxy_set_header Content-Length ""; + {{- if .KeyCache }} + proxy_cache jwks_uri_{{ $s.VSName }}; + proxy_cache_valid 200 12h; + {{- end }} + {{- with .JwksURI }} + proxy_set_header Host {{ .JwksHost }}; + set $idp_backend {{ .JwksHost }}; + proxy_pass {{ .JwksScheme}}://$idp_backend{{ if .JwksPort }}:{{ .JwksPort }}{{ end }}{{ .JwksPath }}; + {{- end }} + } + {{- end }} + + {{- if $s.APIKeyEnabled}} + location = /_validate_apikey_njs { + internal; + js_content apikey_auth.validate; + } + {{- end }} + + {{- with $s.BasicAuth }} + auth_basic {{ printf "%q" .Realm }}; + auth_basic_user_file {{ .Secret }}; + {{- end }} + + {{- with $s.EgressMTLS }} + {{- if .Certificate }} + proxy_ssl_certificate {{ makeSecretPath .Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + proxy_ssl_certificate_key {{ makeSecretPath .CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- if .TrustedCert }} + proxy_ssl_trusted_certificate {{ .TrustedCert }}; + {{- end }} + + proxy_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + proxy_ssl_verify_depth {{ .VerifyDepth }}; + proxy_ssl_protocols {{ .Protocols }}; + proxy_ssl_ciphers {{ .Ciphers }}; + proxy_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + proxy_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + proxy_ssl_name {{ .SSLName }}; + {{- end }} + + {{- with $s.APIKey}} + js_var $header_query_value {{ makeHeaderQueryValue $s.APIKey | printf }}; + js_var $apikey_auth_local_map "{{ .MapName}}"; + js_var $apikey_auth_token $apikey_auth_hash; + auth_request /_validate_apikey_njs; + {{- end }} + + {{- with $s.WAF }} + app_protect_enable {{ .Enable }}; + {{ if .ApPolicy }} + app_protect_policy_file {{ .ApPolicy }}; + {{ end }} + + {{ if .ApBundle }} + app_protect_policy_file {{ .ApBundle }}; + {{ end }} + + {{ if .ApSecurityLogEnable }} + app_protect_security_log_enable on; + {{ range $logconf := .ApLogConf }} + app_protect_security_log {{ $logconf }}; + {{ end }} + {{ end }} + {{ end }} + + {{- with $s.Dos }} + app_protect_dos_enable {{ .Enable }}; + {{- if .Name }} + app_protect_dos_name "{{ .Name }}"; + {{- end }} + + {{- if .AllowListPath }} + app_protect_dos_access_file "{{ .AllowListPath }}"; + {{- end }} + + {{- if .ApDosPolicy }} + app_protect_dos_policy_file {{ .ApDosPolicy }}; + {{- end }} + + {{- if .ApDosSecurityLogEnable }} + app_protect_dos_security_log_enable on; + app_protect_dos_security_log {{ .ApDosLogConf }}; + {{- end }} + + {{- if .ApDosAccessLogDest }} + set $loggable '0'; + # app-protect-dos module will set it to '1' if a request doesn't pass the rate limit + access_log {{ .ApDosAccessLogDest }} log_dos if=$loggable; + {{- end }} + + {{- if .ApDosMonitorURI }} + {{- if or .ApDosMonitorProtocol .ApDosMonitorTimeout}} + app_protect_dos_monitor uri={{ .ApDosMonitorURI }}{{if .ApDosMonitorProtocol}} protocol={{.ApDosMonitorProtocol}}{{end}}{{if .ApDosMonitorTimeout}} timeout={{.ApDosMonitorTimeout}}{{end}}; + {{- else}} + app_protect_dos_monitor "{{ .ApDosMonitorURI }}"; + {{- end}} + {{- end}} + + {{- end }} + + {{- range $snippet := $s.Snippets }} + {{ $snippet }} + {{- end }} + + {{- range $l := $s.InternalRedirectLocations }} + location {{ $l.Path }} { + rewrite ^ {{ $l.Destination }} last; + } + {{- end }} + + {{- range $hc := $s.HealthChecks }} + location @hc-{{ $hc.Name }} { + {{ $proxyOrGRPC := "proxy" }}{{ if $hc.GRPCPass }}{{ $proxyOrGRPC = "grpc" }}{{ end }} + {{- range $n, $v := $hc.Headers }} + {{ $proxyOrGRPC }}_set_header {{ $n }} "{{ $v }}"; + {{- end }} + {{ $proxyOrGRPC }}_connect_timeout {{ $hc.ProxyConnectTimeout }}; + {{ $proxyOrGRPC }}_read_timeout {{ $hc.ProxyReadTimeout }}; + {{ $proxyOrGRPC }}_send_timeout {{ $hc.ProxySendTimeout }}; + {{- if $hc.GRPCPass }} + grpc_pass {{ $hc.GRPCPass }}; + {{- else }} + proxy_pass {{ $hc.ProxyPass }}; + {{- end }} + health_check{{- if $hc.URI }} uri={{ $hc.URI }} {{ end -}} + {{- if $hc.Port }} port={{ $hc.Port }} {{ end -}} + interval={{ $hc.Interval }} jitter={{ $hc.Jitter }} fails={{ $hc.Fails }} passes={{ $hc.Passes }} + {{- if $hc.Match }} match={{ $hc.Match }}{{- end -}} + {{- if $hc.Mandatory }} mandatory {{ end -}} + {{- if $hc.Persistent }} persistent {{ end -}} + {{- if not $hc.IsGRPC }} keepalive_time={{ $hc.KeepaliveTime }}{{ end -}} + {{- if $hc.GRPCPass }} type=grpc{{- if $hc.GRPCStatus }} grpc_status={{ $hc.GRPCStatus }}{{- end -}} + {{- if $hc.GRPCService }} grpc_service={{ $hc.GRPCService }}{{- end -}}{{ end -}}; + + } + {{- end }} + + {{- range $e := $s.ErrorPageLocations }} + location {{ $e.Name }} { + {{ if $e.DefaultType }} + default_type "{{ $e.DefaultType }}"; + {{ end }} + {{ range $h := $e.Headers }} + add_header {{ $h.Name }} "{{ $h.Value }}" always; + {{ end }} + # status code is ignored here, using 0 + return 0 "{{ $e.Return.Text }}"; + } + {{ end }} + + {{ range $l := $s.ReturnLocations }} + location {{ $l.Name }} { + default_type "{{ $l.DefaultType }}"; + {{ range $h := $l.Headers }} + add_header {{ $h.Name }} "{{ $h.Value }}" always; + {{ end }} + # status code is ignored here, using 0 + return 0 "{{ $l.Return.Text }}"; + } + {{ end }} + + {{ range $l := $s.Locations }} + location {{ $l.Path }} { + set $service "{{ $l.ServiceName }}"; + status_zone "{{ $l.ServiceName }}"; + {{- if $l.IsVSR }} + set $resource_type "virtualserverroute"; + set $resource_name "{{ $l.VSRName }}"; + set $resource_namespace "{{ $l.VSRNamespace }}"; + {{- end }} + {{- if $l.Internal }} + internal; + {{- end }} + {{- range $snippet := $l.Snippets }} + {{ $snippet }} + {{- end }} + + {{- with $l.PoliciesErrorReturn }} + return {{ .Code }}; + {{- end }} + + {{- range $allow := $l.Allow }} + allow {{ $allow }}; + {{- end }} + {{- if gt (len $l.Allow) 0 }} + deny all; + {{- end }} + + {{- range $deny := $l.Deny }} + deny {{ $deny }}; + {{- end }} + {{- if gt (len $l.Deny) 0 }} + allow all; + {{- end }} + + {{- if $l.LimitReqOptions.DryRun }} + limit_req_dry_run on; + {{- end }} + + {{- with $level := $l.LimitReqOptions.LogLevel }} + limit_req_log_level {{ $level }}; + {{- end }} + + {{- with $code := $l.LimitReqOptions.RejectCode }} + limit_req_status {{ $code }}; + {{- end }} + + {{- range $rl := $l.LimitReqs }} + limit_req zone={{ $rl.ZoneName }}{{ if $rl.Burst }} burst={{ $rl.Burst }}{{ end }} + {{ if $rl.Delay }} delay={{ $rl.Delay }}{{ end }}{{ if $rl.NoDelay }} nodelay{{ end }}; + {{- end }} + + {{- with $l.JWTAuth }} + auth_jwt "{{ .Realm }}"{{ if .Token }} token={{ .Token }}{{ end }}; + {{ if .Secret}}auth_jwt_key_file {{ .Secret }};{{ end }} + {{- if .JwksURI.JwksHost }} + {{ if .KeyCache }}auth_jwt_key_cache {{ .KeyCache }};{{ end }} + auth_jwt_key_request /_jwks_uri_server_{{ .Key }}; + {{- end }} + {{- end }} + + {{- with $l.BasicAuth }} + auth_basic {{ printf "%q" .Realm }}; + auth_basic_user_file {{ .Secret }}; + {{- end }} + + {{ $proxyOrGRPC := "proxy" }}{{ if $l.GRPCPass }}{{ $proxyOrGRPC = "grpc" }}{{ end }} + + {{- with $l.EgressMTLS }} + {{- if .Certificate }} + {{ $proxyOrGRPC }}_ssl_certificate {{ makeSecretPath .Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{ $proxyOrGRPC }}_ssl_certificate_key {{ makeSecretPath .CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{ if .TrustedCert }} + {{ $proxyOrGRPC }}_ssl_trusted_certificate {{ .TrustedCert }}; + {{- end }} + {{ $proxyOrGRPC }}_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + {{ $proxyOrGRPC }}_ssl_verify_depth {{ .VerifyDepth }}; + {{ $proxyOrGRPC }}_ssl_protocols {{ .Protocols }}; + {{ $proxyOrGRPC }}_ssl_ciphers {{ .Ciphers }}; + {{ $proxyOrGRPC }}_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + {{ $proxyOrGRPC }}_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + {{ $proxyOrGRPC }}_ssl_name {{ .SSLName }}; + {{- end }} + + {{- if $l.OIDC }} + auth_jwt "" token=$session_jwt; + error_page 401 = @do_oidc_flow; + auth_jwt_key_request /_jwks_uri; + {{- $proxyOrGRPC }}_set_header username $jwt_claim_sub; + {{- if $s.OIDC.AccessTokenEnable }} + {{ $proxyOrGRPC }}_set_header Authorization "Bearer $access_token"; + {{- end }} + {{- end }} + + + {{- with $l.APIKey}} + set $apikey_auth_local_map "{{ .MapName }}"; + set $header_query_value {{ makeHeaderQueryValue $l.APIKey | printf }}; + set $apikey_auth_token $apikey_auth_hash; + auth_request /_validate_apikey_njs; + {{- else }} + {{- with $s.APIKey }} + set $header_query_value {{ makeHeaderQueryValue $s.APIKey | printf }}; + {{- end }} + + {{- end }} + + {{- with $l.WAF }} + app_protect_enable {{ .Enable }}; + {{- if .ApPolicy }} + app_protect_policy_file {{ .ApPolicy }}; + {{- end }} + + {{- if .ApBundle }} + app_protect_policy_file {{ .ApBundle }}; + {{- end }} + + {{- if .ApSecurityLogEnable }} + app_protect_security_log_enable on; + {{- range $logconf := .ApLogConf }} + app_protect_security_log {{ $logconf }}; + {{- end }} + {{- end }} + {{- end }} + + {{- if $l.GRPCPass }} + 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; + {{- end }} + + {{- with $l.Dos }} + app_protect_dos_enable {{ .Enable }}; + + {{- if .Name }} + app_protect_dos_name "{{ .Name }}"; + {{- end }} + + {{- if .AllowListPath }} + app_protect_dos_access_file "{{ .AllowListPath }}"; + {{- end }} + + {{- if .ApDosPolicy }} + app_protect_dos_policy_file {{ .ApDosPolicy }}; + {{- end }} + + {{ if .ApDosSecurityLogEnable }} + app_protect_dos_security_log_enable on; + app_protect_dos_security_log {{ .ApDosLogConf }}; + {{ end }} + {{- if .ApDosAccessLogDest }} + set $loggable '0'; + # app-protect-dos module will set it to '1' if a request doesn't pass the rate limit + access_log {{ .ApDosAccessLogDest }} log_dos if=$loggable; + {{- end }} + + {{- if .ApDosMonitorURI }} + {{- if or .ApDosMonitorProtocol .ApDosMonitorTimeout}} + app_protect_dos_monitor uri={{ .ApDosMonitorURI }}{{if .ApDosMonitorProtocol}} protocol={{.ApDosMonitorProtocol}}{{end}}{{if .ApDosMonitorTimeout}} timeout={{.ApDosMonitorTimeout}}{{end}}; + {{- else}} + app_protect_dos_monitor "{{ .ApDosMonitorURI }}"; + {{- end}} + {{- end}} + {{- end }} + + {{- range $e := $l.ErrorPages }} + error_page {{ $e.Codes }} {{ if ne 0 $e.ResponseCode }}={{ $e.ResponseCode }}{{ end }} "{{ $e.Name }}"; + {{- end }} + + {{- if $l.ProxyInterceptErrors }} + {{ $proxyOrGRPC }}_intercept_errors on; + {{- end }} + + {{- if $l.InternalProxyPass }} + proxy_pass {{ $l.InternalProxyPass }}; + {{- end }} + set $default_connection_header {{ if $l.HasKeepalive }}""{{ else }}close{{ end }}; + {{- if or $l.ProxyPass $l.GRPCPass }} + {{- range $r := $l.Rewrites }} + rewrite {{ $r }}; + {{- end }} + {{ $proxyOrGRPC }}_connect_timeout {{ $l.ProxyConnectTimeout }}; + {{ $proxyOrGRPC }}_read_timeout {{ $l.ProxyReadTimeout }}; + {{ $proxyOrGRPC }}_send_timeout {{ $l.ProxySendTimeout }}; + client_max_body_size {{ $l.ClientMaxBodySize }}; + + {{- if $l.ProxyMaxTempFileSize }} + proxy_max_temp_file_size {{ $l.ProxyMaxTempFileSize }}; + {{- end }} + + proxy_buffering {{ if $l.ProxyBuffering }}on{{ else }}off{{ end }}; + {{- if $l.ProxyBuffers }} + proxy_buffers {{ $l.ProxyBuffers }}; + {{- end }} + {{- if $l.ProxyBufferSize }} + {{ $proxyOrGRPC }}_buffer_size {{ $l.ProxyBufferSize }}; + {{- end }} + {{- if not $l.GRPCPass }} + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers {{ if $l.ProxyPassRequestHeaders }}on{{ else }}off{{ end }}; + {{- end }} + + {{- $custom_headers := $l.ProxySetHeaders | headerListToCIMap }} + + {{- if not ($custom_headers | hasCIKey "X-Real-IP") }} + {{ $proxyOrGRPC }}_set_header X-Real-IP $remote_addr; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-For") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-Host") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-Host $host; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-Port") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-Port $server_port; + {{- end }} + + {{- if not ($custom_headers | hasCIKey "X-Forwarded-Proto") }} + {{ $proxyOrGRPC }}_set_header X-Forwarded-Proto {{ with $s.TLSRedirect }}{{ .BasedOn }}{{ else }}$scheme{{ end }}; + {{- end }} + + {{- range $h := $l.ProxySetHeaders }} + {{ $proxyOrGRPC }}_set_header {{ $h.Name }} "{{ $h.Value }}"; + {{- end }} + + {{- range $h := $l.ProxyHideHeaders }} + {{ $proxyOrGRPC }}_hide_header {{ $h }}; + {{- end }} + {{- range $h := $l.ProxyPassHeaders }} + {{ $proxyOrGRPC }}_pass_header {{ $h }}; + {{- end }} + {{- with $l.ProxyIgnoreHeaders }} + {{ $proxyOrGRPC }}_ignore_headers {{ $l.ProxyIgnoreHeaders }}; + {{- end }} + {{- range $h := $l.AddHeaders }} + add_header {{ $h.Name }} "{{ $h.Value }}" {{ if $h.Always }}always{{ end }}; + {{- end }} + {{- if $.SpiffeClientCerts }} + {{ $proxyOrGRPC }}_ssl_certificate {{ makeSecretPath "/etc/nginx/secrets/spiffe_cert.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{ $proxyOrGRPC }}_ssl_certificate_key {{ makeSecretPath "/etc/nginx/secrets/spiffe_key.pem" $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{ $proxyOrGRPC }}_ssl_trusted_certificate /etc/nginx/secrets/spiffe_rootca.pem; + {{ $proxyOrGRPC }}_ssl_server_name on; + {{ $proxyOrGRPC }}_ssl_verify on; + {{ $proxyOrGRPC }}_ssl_verify_depth 25; + {{ $proxyOrGRPC }}_ssl_name {{ $l.ProxySSLName }}; + {{- end }} + {{- if $l.GRPCPass }} + grpc_pass {{ $l.GRPCPass }}; + {{- else }} + proxy_pass {{ $l.ProxyPass }}{{ $l.ProxyPassRewrite }}; + {{- end }} + {{ $proxyOrGRPC }}_next_upstream {{ $l.ProxyNextUpstream }}; + {{ $proxyOrGRPC }}_next_upstream_timeout {{ $l.ProxyNextUpstreamTimeout }}; + {{ $proxyOrGRPC }}_next_upstream_tries {{ $l.ProxyNextUpstreamTries }}; + {{- end }} + } + {{- end }} + + {{- with $ssl := $s.SSL }} + {{ if $ssl.HTTP2 }} + 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; + } + + {{ end }} + {{ end }} +}` + +// custom TStemplate represents the transportserver template passed via ConfigMap +var customTestTStemplate = `# TEST CUSTOM TRANSPORTSERVER TEMPLATE +{{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version2.TransportServerConfig*/ -}} +{{- range $u := .Upstreams }} +upstream {{ $u.Name }} { + zone {{ $u.Name }} 512k; + {{- if $u.LoadBalancingMethod }} + {{ $u.LoadBalancingMethod }}; + {{- end }} + + {{- range $s := $u.Servers }} + server {{ $s.Address }} max_fails={{ $s.MaxFails }} fail_timeout={{ $s.FailTimeout }} max_conns={{ $s.MaxConnections }}{{ if $u.Resolve }} resolve{{ end }}; + {{- end }} + + {{- range $b := $u.BackupServers }} + server {{ $b.Address }} resolve backup; + {{- end }} +} +{{- end }} + +{{- range $snippet := .StreamSnippets }} +{{ $snippet }} +{{- end }} + +{{ with $m := .Match }} +match {{ $m.Name }} { + {{ if $m.Send }} + send "{{ $m.Send }}"; + {{ end }} + + {{ if $m.Expect }} + expect {{ $m.ExpectRegexModifier }} "{{ $m.Expect }}"; + {{ end }} +} +{{- end }} + +{{- $s := .Server }} +server { + {{- with $ssl := $s.SSL }} + {{- if $s.TLSPassthrough }} + listen {{ $s.UnixSocket }} proxy_protocol; + set_real_ip_from unix:; + {{- else }} + {{ makeTransportListener $s | printf }} + {{- with makeServerName $s }}{{ printf "\t%s" . }}{{- end }} + {{- end }} + + {{- if $ssl.Enabled }} + ssl_certificate {{ makeSecretPath $ssl.Certificate $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + ssl_certificate_key {{ makeSecretPath $ssl.CertificateKey $.StaticSSLPath "$secret_dir_path" $.DynamicSSLReloadEnabled }}; + {{- end }} + {{- end }} + + status_zone {{ $s.StatusZone }}; + + {{- if $s.ProxyRequests }} + proxy_requests {{ $s.ProxyRequests }}; + {{- end }} + {{- if $s.ProxyResponses }} + proxy_responses {{ $s.ProxyResponses }}; + {{- end }} + + {{- range $snippet := $s.ServerSnippets }} + {{ $snippet }} + {{- end }} + + proxy_pass {{ $s.ProxyPass }}; + + {{ if $s.HealthCheck }} + health_check interval={{ $s.HealthCheck.Interval }} {{ if $s.HealthCheck.Port }} port={{ $s.HealthCheck.Port }}{{ end }} + passes={{ $s.HealthCheck.Passes }} jitter={{ $s.HealthCheck.Jitter }} fails={{ $s.HealthCheck.Fails }}{{ if $s.UDP }} udp{{ end }}{{ if $s.HealthCheck.Match }} match={{ $s.HealthCheck.Match }}{{ end }}; + health_check_timeout {{ $s.HealthCheck.Timeout }}; + {{ end }} + + proxy_timeout {{ $s.ProxyTimeout }}; + proxy_connect_timeout {{ $s.ProxyConnectTimeout }}; + + {{- if $s.ProxyNextUpstream }} + proxy_next_upstream on; + proxy_next_upstream_timeout {{ $s.ProxyNextUpstreamTimeout }}; + proxy_next_upstream_tries {{ $s.ProxyNextUpstreamTries }}; + {{- end }} +}`