From ce60d933cab587904643ed66304628d38ba05b4d Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Tue, 18 Jul 2023 17:34:39 +0100 Subject: [PATCH 01/11] Generate config for path-regex annotation --- .../configs/version1/nginx-plus.ingress.tmpl | 6 +- internal/configs/version1/nginx.ingress.tmpl | 6 +- internal/configs/version1/template_helper.go | 29 +- .../configs/version1/template_helper_test.go | 95 +++ internal/configs/version1/templates_test.go | 553 +++++++++++++----- 5 files changed, 534 insertions(+), 155 deletions(-) create mode 100644 internal/configs/version1/template_helper_test.go diff --git a/internal/configs/version1/nginx-plus.ingress.tmpl b/internal/configs/version1/nginx-plus.ingress.tmpl index 2ceb881fec..54d4e980c9 100644 --- a/internal/configs/version1/nginx-plus.ingress.tmpl +++ b/internal/configs/version1/nginx-plus.ingress.tmpl @@ -177,7 +177,11 @@ server { {{end -}} {{range $location := $server.Locations}} - location {{$location.Path}} { + {{ if index $.Ingress.Annotations "nginx.org/path-regex" }} + location {{ makePathRegex $location.Path $.Ingress.Annotations | printf }} { + {{ else }} + location {{ $location.Path }} { + {{ end }} set $service "{{$location.ServiceName}}"; status_zone "{{ $location.ServiceName }}"; {{with $location.MinionIngress}} diff --git a/internal/configs/version1/nginx.ingress.tmpl b/internal/configs/version1/nginx.ingress.tmpl index e473f7731a..8c544a9581 100644 --- a/internal/configs/version1/nginx.ingress.tmpl +++ b/internal/configs/version1/nginx.ingress.tmpl @@ -102,7 +102,11 @@ server { {{- end}} {{range $location := $server.Locations}} - location {{$location.Path}} { + {{ if index $.Ingress.Annotations "nginx.org/path-regex" }} + location {{ makePathRegex $location.Path $.Ingress.Annotations | printf }} { + {{ else }} + location {{ $location.Path }} { + {{ end }} set $service "{{$location.ServiceName}}"; {{with $location.MinionIngress}} # location for minion {{$location.MinionIngress.Namespace}}/{{$location.MinionIngress.Name}} diff --git a/internal/configs/version1/template_helper.go b/internal/configs/version1/template_helper.go index c2b09f5c61..4857fd155e 100644 --- a/internal/configs/version1/template_helper.go +++ b/internal/configs/version1/template_helper.go @@ -1,6 +1,7 @@ package version1 import ( + "fmt" "strings" "text/template" ) @@ -13,7 +14,31 @@ func trim(s string) string { return strings.TrimSpace(s) } +// makePathRegex takes a string representing a location path +// and a map representing Ingress annotations. +// It returns a location path with added regular expression modifier. +// See [Location Directive]. +// +// [Location Directive]: https://nginx.org/en/docs/http/ngx_http_core_module.html#location +func makePathRegex(path string, annotations map[string]string) string { + p, ok := annotations["nginx.org/path-regex"] + if !ok { + return path + } + switch p { + case "case_sensitive": + return fmt.Sprintf("~ \"^%s\"", path) + case "case_insensitive": + return fmt.Sprintf("~* \"^%s\"", path) + case "exact": + return fmt.Sprintf("= \"%s\"", path) + default: + return path + } +} + var helperFunctions = template.FuncMap{ - "split": split, - "trim": trim, + "split": split, + "trim": trim, + "makePathRegex": makePathRegex, } diff --git a/internal/configs/version1/template_helper_test.go b/internal/configs/version1/template_helper_test.go new file mode 100644 index 0000000000..a18967737a --- /dev/null +++ b/internal/configs/version1/template_helper_test.go @@ -0,0 +1,95 @@ +package version1 + +import ( + "bytes" + "testing" + "text/template" +) + +func TestWithPathRegex_MatchesCaseSensitiveModifier(t *testing.T) { + t.Parallel() + + want := "~ \"^/coffee/[A-Z0-9]{3}\"" + got := makePathRegex("/coffee/[A-Z0-9]{3}", map[string]string{"nginx.org/path-regex": "case_sensitive"}) + if got != want { + t.Errorf("got: %s, want: %s", got, want) + } +} + +func TestWithPathRegex_MatchesCaseInsensitiveModifier(t *testing.T) { + t.Parallel() + + want := "~* \"^/coffee/[A-Z0-9]{3}\"" + got := makePathRegex("/coffee/[A-Z0-9]{3}", map[string]string{"nginx.org/path-regex": "case_insensitive"}) + if got != want { + t.Errorf("got: %s, want: %s", got, want) + } +} + +func TestWithPathReqex_MatchesExactModifier(t *testing.T) { + t.Parallel() + + want := "= \"/coffee\"" + got := makePathRegex("/coffee", map[string]string{"nginx.org/path-regex": "exact"}) + if got != want { + t.Errorf("got: %s, want: %s", got, want) + } +} + +func TestWithPathReqex_DoesNotMatchModifier(t *testing.T) { + t.Parallel() + + want := "/coffee" + got := makePathRegex("/coffee", map[string]string{"nginx.org/path-regex": ""}) + if got != want { + t.Errorf("got: %s, want: %s", got, want) + } +} + +func TestSplitHelperFunction(t *testing.T) { + t.Parallel() + const tpl = `{{range $n := split . ","}}{{$n}} {{end}}` + + tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(tpl) + if err != nil { + t.Fatalf("Failed to parse template: %v", err) + } + + var buf bytes.Buffer + + input := "foo,bar" + expected := "foo bar " + + err = tmpl.Execute(&buf, input) + if err != nil { + t.Fatalf("Failed to execute the template %v", err) + } + + if buf.String() != expected { + t.Fatalf("Template generated wrong config, got %v but expected %v.", buf.String(), expected) + } +} + +func TestTrimHelperFunction(t *testing.T) { + t.Parallel() + const tpl = `{{trim .}}` + + tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(tpl) + if err != nil { + t.Fatalf("Failed to parse template: %v", err) + } + + var buf bytes.Buffer + + input := " foobar " + expected := "foobar" + + err = tmpl.Execute(&buf, input) + if err != nil { + t.Fatalf("Failed to execute the template %v", err) + } + + if buf.String() != expected { + t.Fatalf("Template generated wrong config, got %v but expected %v.", buf.String(), expected) + } +} diff --git a/internal/configs/version1/templates_test.go b/internal/configs/version1/templates_test.go index 5b5c9417f9..137be17578 100644 --- a/internal/configs/version1/templates_test.go +++ b/internal/configs/version1/templates_test.go @@ -2,149 +2,118 @@ package version1 import ( "bytes" + "strings" "testing" "text/template" ) -const ( - nginxIngressTmpl = "nginx.ingress.tmpl" - nginxMainTmpl = "nginx.tmpl" - nginxPlusIngressTmpl = "nginx-plus.ingress.tmpl" - nginxPlusMainTmpl = "nginx-plus.tmpl" -) - -var testUps = Upstream{ - Name: "test", - UpstreamZoneSize: "256k", - UpstreamServers: []UpstreamServer{ - { - Address: "127.0.0.1:8181", - MaxFails: 0, - MaxConns: 0, - FailTimeout: "1s", - SlowStart: "5s", - }, - }, +func makeTemplateNGINXPlus(t *testing.T) *template.Template { + t.Helper() + tmpl, err := template.New(nginxPlusIngressTmpl).Funcs(helperFunctions).ParseFiles(nginxPlusIngressTmpl) + if err != nil { + t.Fatal(err) + } + return tmpl } -var ( - headers = map[string]string{"Test-Header": "test-header-value"} - healthCheck = HealthCheck{ - UpstreamName: "test", - Fails: 1, - Interval: 1, - Passes: 1, - Headers: headers, +func makeTemplateNGINX(t *testing.T) *template.Template { + t.Helper() + tmpl, err := template.New(nginxIngressTmpl).Funcs(helperFunctions).ParseFiles(nginxIngressTmpl) + if err != nil { + t.Fatal(err) } -) + return tmpl +} -var ingCfg = IngressNginxConfig{ - Servers: []Server{ - { - Name: "test.example.com", - ServerTokens: "off", - StatusZone: "test.example.com", - JWTAuth: &JWTAuth{ - Key: "/etc/nginx/secrets/key.jwk", - Realm: "closed site", - Token: "$cookie_auth_token", - RedirectLocationName: "@login_url-default-cafe-ingress", - }, - SSL: true, - SSLCertificate: "secret.pem", - SSLCertificateKey: "secret.pem", - SSLPorts: []int{443}, - SSLRedirect: true, - Locations: []Location{ - { - Path: "/tea", - Upstream: testUps, - ProxyConnectTimeout: "10s", - ProxyReadTimeout: "10s", - ProxySendTimeout: "10s", - ClientMaxBodySize: "2m", - JWTAuth: &JWTAuth{ - Key: "/etc/nginx/secrets/location-key.jwk", - Realm: "closed site", - Token: "$cookie_auth_token", - }, - MinionIngress: &Ingress{ - Name: "tea-minion", - Namespace: "default", - }, - }, - }, - HealthChecks: map[string]HealthCheck{"test": healthCheck}, - JWTRedirectLocations: []JWTRedirectLocation{ - { - Name: "@login_url-default-cafe-ingress", - LoginURL: "https://test.example.com/login", - }, - }, - }, - }, - Upstreams: []Upstream{testUps}, - Keepalive: "16", - Ingress: Ingress{ - Name: "cafe-ingress", - Namespace: "default", - }, +func TestIngressForNGINXPlus(t *testing.T) { + t.Parallel() + tmpl := makeTemplateNGINXPlus(t) + buf := &bytes.Buffer{} + err := tmpl.Execute(buf, ingressCfg) + t.Log(buf.String()) + if err != nil { + t.Fatalf("Failed to write template %v", err) + } } -var mainCfg = MainConfig{ - ServerNamesHashMaxSize: "512", - ServerTokens: "off", - WorkerProcesses: "auto", - WorkerCPUAffinity: "auto", - WorkerShutdownTimeout: "1m", - WorkerConnections: "1024", - WorkerRlimitNofile: "65536", - LogFormat: []string{"$remote_addr", "$remote_user"}, - LogFormatEscaping: "default", - StreamSnippets: []string{"# comment"}, - StreamLogFormat: []string{"$remote_addr", "$remote_user"}, - StreamLogFormatEscaping: "none", - ResolverAddresses: []string{"example.com", "127.0.0.1"}, - ResolverIPV6: false, - ResolverValid: "10s", - ResolverTimeout: "15s", - KeepaliveTimeout: "65s", - KeepaliveRequests: 100, - VariablesHashBucketSize: 256, - VariablesHashMaxSize: 1024, - TLSPassthrough: true, +func TestIngressForNGINX(t *testing.T) { + t.Parallel() + tmpl := makeTemplateNGINX(t) + buf := &bytes.Buffer{} + + err := tmpl.Execute(buf, ingressCfg) + t.Log(buf.String()) + if err != nil { + t.Fatalf("Failed to write template %v", err) + } } -func TestIngressForNGINXPlus(t *testing.T) { +func TestExecuteTemplate_ForIngressForNGINXPlusWithRegExAnnotationCaseSensitive(t *testing.T) { t.Parallel() - tmpl, err := template.New(nginxPlusIngressTmpl).Funcs(helperFunctions).ParseFiles(nginxPlusIngressTmpl) + tmpl := makeTemplateNGINXPlus(t) + buf := &bytes.Buffer{} + + err := tmpl.Execute(buf, ingressCfgWithRegExAnnotationCaseSensitive) + t.Log(buf.String()) if err != nil { - t.Fatalf("Failed to parse template file: %v", err) + t.Fatalf("Failed to write template %v", err) } - var buf bytes.Buffer + wantLocation := "~ \"^/tea/[A-Z0-9]{3}\"" + if !strings.Contains(buf.String(), wantLocation) { + t.Errorf("want %q in generated config", wantLocation) + } +} - err = tmpl.Execute(&buf, ingCfg) +func TestExecuteTemplate_ForIngressForNGINXPlusWithRegExAnnotationCaseInsensitive(t *testing.T) { + t.Parallel() + tmpl := makeTemplateNGINXPlus(t) + buf := &bytes.Buffer{} + + err := tmpl.Execute(buf, ingressCfgWithRegExAnnotationCaseInsensitive) t.Log(buf.String()) if err != nil { t.Fatalf("Failed to write template %v", err) } + + wantLocation := "~* \"^/tea/[A-Z0-9]{3}\"" + if !strings.Contains(buf.String(), wantLocation) { + t.Errorf("want %q in generated config", wantLocation) + } } -func TestIngressForNGINX(t *testing.T) { +func TestExecuteTemplate_ForIngressForNGINXPlusWithRegExAnnotationExactMatch(t *testing.T) { t.Parallel() - tmpl, err := template.New(nginxIngressTmpl).Funcs(helperFunctions).ParseFiles(nginxIngressTmpl) + tmpl := makeTemplateNGINXPlus(t) + buf := &bytes.Buffer{} + + err := tmpl.Execute(buf, ingressCfgWithRegExAnnotationExactMatch) + t.Log(buf.String()) if err != nil { - t.Fatalf("Failed to parse template file: %v", err) + t.Fatalf("Failed to write template %v", err) } - var buf bytes.Buffer + wantLocation := "= \"/tea\"" + if !strings.Contains(buf.String(), wantLocation) { + t.Errorf("want %q in generated config", wantLocation) + } +} + +func TestExecuteTemplate_ForIngressForNGINXPlusWithRegExAnnotationEmpty(t *testing.T) { + t.Parallel() + tmpl := makeTemplateNGINXPlus(t) + buf := &bytes.Buffer{} - err = tmpl.Execute(&buf, ingCfg) + err := tmpl.Execute(buf, ingressCfgWithRegExAnnotationEmptyString) t.Log(buf.String()) if err != nil { t.Fatalf("Failed to write template %v", err) } + + wantLocation := "/tea" + if !strings.Contains(buf.String(), wantLocation) { + t.Errorf("want %q in generated config", wantLocation) + } } func TestMainForNGINXPlus(t *testing.T) { @@ -153,10 +122,9 @@ func TestMainForNGINXPlus(t *testing.T) { if err != nil { t.Fatalf("Failed to parse template file: %v", err) } + buf := &bytes.Buffer{} - var buf bytes.Buffer - - err = tmpl.Execute(&buf, mainCfg) + err = tmpl.Execute(buf, mainCfg) t.Log(buf.String()) if err != nil { t.Fatalf("Failed to write template %v", err) @@ -169,60 +137,343 @@ func TestMainForNGINX(t *testing.T) { if err != nil { t.Fatalf("Failed to parse template file: %v", err) } + buf := &bytes.Buffer{} - var buf bytes.Buffer - - err = tmpl.Execute(&buf, mainCfg) + err = tmpl.Execute(buf, mainCfg) t.Log(buf.String()) if err != nil { t.Fatalf("Failed to write template %v", err) } } -func TestSplitHelperFunction(t *testing.T) { - t.Parallel() - const tpl = `{{range $n := split . ","}}{{$n}} {{end}}` - - tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(tpl) - if err != nil { - t.Fatalf("Failed to parse template: %v", err) +var ( + // Ingress Config example without added annotations + ingressCfg = IngressNginxConfig{ + Servers: []Server{ + { + Name: "test.example.com", + ServerTokens: "off", + StatusZone: "test.example.com", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + RedirectLocationName: "@login_url-default-cafe-ingress", + }, + SSL: true, + SSLCertificate: "secret.pem", + SSLCertificateKey: "secret.pem", + SSLPorts: []int{443}, + SSLRedirect: true, + Locations: []Location{ + { + Path: "/tea", + Upstream: testUpstream, + ProxyConnectTimeout: "10s", + ProxyReadTimeout: "10s", + ProxySendTimeout: "10s", + ClientMaxBodySize: "2m", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/location-key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + }, + MinionIngress: &Ingress{ + Name: "tea-minion", + Namespace: "default", + }, + }, + }, + HealthChecks: map[string]HealthCheck{"test": healthCheck}, + JWTRedirectLocations: []JWTRedirectLocation{ + { + Name: "@login_url-default-cafe-ingress", + LoginURL: "https://test.example.com/login", + }, + }, + }, + }, + Upstreams: []Upstream{testUpstream}, + Keepalive: "16", + Ingress: Ingress{ + Name: "cafe-ingress", + Namespace: "default", + }, } - var buf bytes.Buffer - - input := "foo,bar" - expected := "foo bar " - - err = tmpl.Execute(&buf, input) - if err != nil { - t.Fatalf("Failed to execute the template %v", err) + // Ingress Config example with path-regex annotation value "case_sensitive" + ingressCfgWithRegExAnnotationCaseSensitive = IngressNginxConfig{ + Servers: []Server{ + { + Name: "test.example.com", + ServerTokens: "off", + StatusZone: "test.example.com", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + RedirectLocationName: "@login_url-default-cafe-ingress", + }, + SSL: true, + SSLCertificate: "secret.pem", + SSLCertificateKey: "secret.pem", + SSLPorts: []int{443}, + SSLRedirect: true, + Locations: []Location{ + { + Path: "/tea/[A-Z0-9]{3}", + Upstream: testUpstream, + ProxyConnectTimeout: "10s", + ProxyReadTimeout: "10s", + ProxySendTimeout: "10s", + ClientMaxBodySize: "2m", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/location-key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + }, + MinionIngress: &Ingress{ + Name: "tea-minion", + Namespace: "default", + }, + }, + }, + HealthChecks: map[string]HealthCheck{"test": healthCheck}, + JWTRedirectLocations: []JWTRedirectLocation{ + { + Name: "@login_url-default-cafe-ingress", + LoginURL: "https://test.example.com/login", + }, + }, + }, + }, + Upstreams: []Upstream{testUpstream}, + Keepalive: "16", + Ingress: Ingress{ + Name: "cafe-ingress", + Namespace: "default", + Annotations: map[string]string{"nginx.org/path-regex": "case_sensitive"}, + }, } - if buf.String() != expected { - t.Fatalf("Template generated wrong config, got %v but expected %v.", buf.String(), expected) + // Ingress Config example with path-regex annotation value "case_insensitive" + ingressCfgWithRegExAnnotationCaseInsensitive = IngressNginxConfig{ + Servers: []Server{ + { + Name: "test.example.com", + ServerTokens: "off", + StatusZone: "test.example.com", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + RedirectLocationName: "@login_url-default-cafe-ingress", + }, + SSL: true, + SSLCertificate: "secret.pem", + SSLCertificateKey: "secret.pem", + SSLPorts: []int{443}, + SSLRedirect: true, + Locations: []Location{ + { + Path: "/tea/[A-Z0-9]{3}", + Upstream: testUpstream, + ProxyConnectTimeout: "10s", + ProxyReadTimeout: "10s", + ProxySendTimeout: "10s", + ClientMaxBodySize: "2m", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/location-key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + }, + MinionIngress: &Ingress{ + Name: "tea-minion", + Namespace: "default", + }, + }, + }, + HealthChecks: map[string]HealthCheck{"test": healthCheck}, + JWTRedirectLocations: []JWTRedirectLocation{ + { + Name: "@login_url-default-cafe-ingress", + LoginURL: "https://test.example.com/login", + }, + }, + }, + }, + Upstreams: []Upstream{testUpstream}, + Keepalive: "16", + Ingress: Ingress{ + Name: "cafe-ingress", + Namespace: "default", + Annotations: map[string]string{"nginx.org/path-regex": "case_insensitive"}, + }, } -} -func TestTrimHelperFunction(t *testing.T) { - t.Parallel() - const tpl = `{{trim .}}` + // Ingress Config example with path-regex annotation value "exact" + ingressCfgWithRegExAnnotationExactMatch = IngressNginxConfig{ + Servers: []Server{ + { + Name: "test.example.com", + ServerTokens: "off", + StatusZone: "test.example.com", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + RedirectLocationName: "@login_url-default-cafe-ingress", + }, + SSL: true, + SSLCertificate: "secret.pem", + SSLCertificateKey: "secret.pem", + SSLPorts: []int{443}, + SSLRedirect: true, + Locations: []Location{ + { + Path: "/tea", + Upstream: testUpstream, + ProxyConnectTimeout: "10s", + ProxyReadTimeout: "10s", + ProxySendTimeout: "10s", + ClientMaxBodySize: "2m", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/location-key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + }, + MinionIngress: &Ingress{ + Name: "tea-minion", + Namespace: "default", + }, + }, + }, + HealthChecks: map[string]HealthCheck{"test": healthCheck}, + JWTRedirectLocations: []JWTRedirectLocation{ + { + Name: "@login_url-default-cafe-ingress", + LoginURL: "https://test.example.com/login", + }, + }, + }, + }, + Upstreams: []Upstream{testUpstream}, + Keepalive: "16", + Ingress: Ingress{ + Name: "cafe-ingress", + Namespace: "default", + Annotations: map[string]string{"nginx.org/path-regex": "exact"}, + }, + } - tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(tpl) - if err != nil { - t.Fatalf("Failed to parse template: %v", err) + // Ingress Config example with path-regex annotation value of an empty string + ingressCfgWithRegExAnnotationEmptyString = IngressNginxConfig{ + Servers: []Server{ + { + Name: "test.example.com", + ServerTokens: "off", + StatusZone: "test.example.com", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + RedirectLocationName: "@login_url-default-cafe-ingress", + }, + SSL: true, + SSLCertificate: "secret.pem", + SSLCertificateKey: "secret.pem", + SSLPorts: []int{443}, + SSLRedirect: true, + Locations: []Location{ + { + Path: "/tea", + Upstream: testUpstream, + ProxyConnectTimeout: "10s", + ProxyReadTimeout: "10s", + ProxySendTimeout: "10s", + ClientMaxBodySize: "2m", + JWTAuth: &JWTAuth{ + Key: "/etc/nginx/secrets/location-key.jwk", + Realm: "closed site", + Token: "$cookie_auth_token", + }, + MinionIngress: &Ingress{ + Name: "tea-minion", + Namespace: "default", + }, + }, + }, + HealthChecks: map[string]HealthCheck{"test": healthCheck}, + JWTRedirectLocations: []JWTRedirectLocation{ + { + Name: "@login_url-default-cafe-ingress", + LoginURL: "https://test.example.com/login", + }, + }, + }, + }, + Upstreams: []Upstream{testUpstream}, + Keepalive: "16", + Ingress: Ingress{ + Name: "cafe-ingress", + Namespace: "default", + Annotations: map[string]string{"nginx.org/path-regex": ""}, + }, } - var buf bytes.Buffer + mainCfg = MainConfig{ + ServerNamesHashMaxSize: "512", + ServerTokens: "off", + WorkerProcesses: "auto", + WorkerCPUAffinity: "auto", + WorkerShutdownTimeout: "1m", + WorkerConnections: "1024", + WorkerRlimitNofile: "65536", + LogFormat: []string{"$remote_addr", "$remote_user"}, + LogFormatEscaping: "default", + StreamSnippets: []string{"# comment"}, + StreamLogFormat: []string{"$remote_addr", "$remote_user"}, + StreamLogFormatEscaping: "none", + ResolverAddresses: []string{"example.com", "127.0.0.1"}, + ResolverIPV6: false, + ResolverValid: "10s", + ResolverTimeout: "15s", + KeepaliveTimeout: "65s", + KeepaliveRequests: 100, + VariablesHashBucketSize: 256, + VariablesHashMaxSize: 1024, + TLSPassthrough: true, + } +) - input := " foobar " - expected := "foobar" +const ( + nginxIngressTmpl = "nginx.ingress.tmpl" + nginxMainTmpl = "nginx.tmpl" + nginxPlusIngressTmpl = "nginx-plus.ingress.tmpl" + nginxPlusMainTmpl = "nginx-plus.tmpl" +) - err = tmpl.Execute(&buf, input) - if err != nil { - t.Fatalf("Failed to execute the template %v", err) - } +var testUpstream = Upstream{ + Name: "test", + UpstreamZoneSize: "256k", + UpstreamServers: []UpstreamServer{ + { + Address: "127.0.0.1:8181", + MaxFails: 0, + MaxConns: 0, + FailTimeout: "1s", + SlowStart: "5s", + }, + }, +} - if buf.String() != expected { - t.Fatalf("Template generated wrong config, got %v but expected %v.", buf.String(), expected) +var ( + headers = map[string]string{"Test-Header": "test-header-value"} + healthCheck = HealthCheck{ + UpstreamName: "test", + Fails: 1, + Interval: 1, + Passes: 1, + Headers: headers, } -} +) From 1df5ae213f91090c0fc610bc6ace3bfe97c8405c Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Thu, 20 Jul 2023 09:44:36 +0100 Subject: [PATCH 02/11] Add validation for path-regex annotation --- internal/configs/annotations.go | 16 ++ internal/k8s/validation.go | 13 ++ internal/k8s/validation_test.go | 266 ++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) diff --git a/internal/configs/annotations.go b/internal/configs/annotations.go index 120e440f10..5dbcd662f0 100644 --- a/internal/configs/annotations.go +++ b/internal/configs/annotations.go @@ -10,6 +10,9 @@ const JWTKeyAnnotation = "nginx.com/jwt-key" // BasicAuthSecretAnnotation is the annotation where the Secret with the HTTP basic user list const BasicAuthSecretAnnotation = "nginx.org/basic-auth-secret" // #nosec G101 +// PathRegexAnnotation is the annotation where the regex location (path) modifier is specified. +const PathRegexAnnotation = "nginx.org/path-regex" + // AppProtectPolicyAnnotation is where the NGINX App Protect policy is specified const AppProtectPolicyAnnotation = "appprotect.f5.com/app-protect-policy" @@ -73,6 +76,12 @@ var minionInheritanceList = map[string]bool{ "nginx.org/fail-timeout": true, } +var validPathRegex = map[string]bool{ + "case_sensitive": true, + "case_insensitive": true, + "exact": true, +} + func parseAnnotations(ingEx *IngressEx, baseCfgParams *ConfigParams, isPlus bool, hasAppProtect bool, hasAppProtectDos bool, enableInternalRoutes bool) ConfigParams { cfgParams := *baseCfgParams @@ -385,6 +394,13 @@ func parseAnnotations(ingEx *IngressEx, baseCfgParams *ConfigParams, isPlus bool } } } + + if pathRegex, exists := ingEx.Ingress.Annotations[PathRegexAnnotation]; exists { + _, ok := validPathRegex[pathRegex] + if !ok { + glog.Errorf("Ingress %s/%s: Invalid value nginx.org/path-regex: got %q. Allowed values: 'case_sensitive', 'case_insensitive', 'exact'", ingEx.Ingress.GetNamespace(), ingEx.Ingress.GetName(), pathRegex) + } + } return cfgParams } diff --git a/internal/k8s/validation.go b/internal/k8s/validation.go index 1276c38d39..2690a5c6d0 100644 --- a/internal/k8s/validation.go +++ b/internal/k8s/validation.go @@ -67,6 +67,7 @@ const ( grpcServicesAnnotation = "nginx.org/grpc-services" rewritesAnnotation = "nginx.org/rewrites" stickyCookieServicesAnnotation = "nginx.com/sticky-cookie-services" + pathRegexAnnotation = "nginx.org/path-regex" ) const ( @@ -327,10 +328,22 @@ var ( validateRequiredAnnotation, validateStickyServiceListAnnotation, }, + pathRegexAnnotation: { + validatePathRegex, + }, } annotationNames = sortedAnnotationNames(annotationValidations) ) +func validatePathRegex(context *annotationValidationContext) field.ErrorList { + switch context.value { + case "case_sensitive", "case_insensitive", "exact": + return nil + default: + return field.ErrorList{field.Invalid(context.fieldPath, context.value, "allowed values: 'case_sensitive', 'case_insensitive' or 'exact'")} + } +} + func validateJWTLoginURLAnnotation(context *annotationValidationContext) field.ErrorList { allErrs := field.ErrorList{} diff --git a/internal/k8s/validation_test.go b/internal/k8s/validation_test.go index cc48a12d71..2080dc1b0c 100644 --- a/internal/k8s/validation_test.go +++ b/internal/k8s/validation_test.go @@ -12,6 +12,272 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) +func TestValidateIngress_WithValidPathRegexValuesForNGINXPlus(t *testing.T) { + t.Parallel() + tt := []struct { + name string + ingress *networking.Ingress + isPlus bool + }{ + { + name: "case sensitive path regex", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "case_sensitive", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: true, + }, + { + name: "case insensitive path regex", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "case_insensitive", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: true, + }, + { + name: "exact path regex", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "exact", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: true, + }, + } + + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + allErrs := validateIngress(tc.ingress, tc.isPlus, false, false, false, false) + if len(allErrs) != 0 { + t.Errorf("want no errors, got %+v\n", allErrs) + } + }) + } +} + +func TestValidateIngress_WithValidPathRegexValuesForNGINX(t *testing.T) { + t.Parallel() + tt := []struct { + name string + ingress *networking.Ingress + isPlus bool + }{ + { + name: "case sensitive path regex", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "case_sensitive", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: false, + }, + { + name: "case insensitive path regex", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "case_insensitive", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: false, + }, + { + name: "exact path regex", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "exact", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: false, + }, + } + + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + allErrs := validateIngress(tc.ingress, tc.isPlus, false, false, false, false) + if len(allErrs) != 0 { + t.Errorf("want no errors, got %+v\n", allErrs) + } + }) + } +} + +func TestValidateIngress_WithInvalidPathRegexValuesForNGINXPlus(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + ingress *networking.Ingress + isPlus bool + }{ + { + name: "bogus not empty path regex string", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "bogus", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: true, + }, + { + name: "bogus empty path regex string", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: true, + }, + } + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + allErrs := validateIngress(tc.ingress, tc.isPlus, false, false, false, false) + if len(allErrs) == 0 { + t.Error("want errors on invalid path regex values") + } + t.Log(allErrs) + }) + } +} + +func TestValidateIngress_WithInvalidPathRegexValuesForNGINX(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + ingress *networking.Ingress + isPlus bool + }{ + { + name: "bogus not empty path regex string", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "bogus", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: false, + }, + { + name: "bogus empty path regex string", + ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "nginx.org/path-regex": "", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "example.com", + }, + }, + }, + }, + isPlus: false, + }, + } + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + allErrs := validateIngress(tc.ingress, tc.isPlus, false, false, false, false) + if len(allErrs) == 0 { + t.Error("want errors on invalid path regex values") + } + t.Log(allErrs) + }) + } +} + func TestValidateIngress(t *testing.T) { t.Parallel() tests := []struct { From 61d40ab06b53c458a211601ca0f20ae9868efdd0 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Thu, 20 Jul 2023 14:06:00 +0100 Subject: [PATCH 03/11] Add documentation for path-regex annotation --- ...advanced-configuration-with-annotations.md | 1 + .../ingress-resources/path-regex/README.md | 158 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 examples/ingress-resources/path-regex/README.md diff --git a/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md b/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md index fa12b02ba9..f26e4f8003 100644 --- a/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md +++ b/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md @@ -112,6 +112,7 @@ The table below summarizes the available annotations. |``nginx.org/proxy-buffer-size`` | ``proxy-buffer-size`` | Sets the value of the [proxy_buffer_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) and [grpc_buffer_size](https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size) directives. | Depends on the platform. | | |``nginx.org/proxy-max-temp-file-size`` | ``proxy-max-temp-file-size`` | Sets the value of the [proxy_max_temp_file_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size) directive. | ``1024m`` | | |``nginx.org/server-tokens`` | ``server-tokens`` | Enables or disables the [server_tokens](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens) directive. Additionally, with the NGINX Plus, you can specify a custom string value, including the empty string value, which disables the emission of the “Server” field. | ``True`` | | +|``nginx.org/path-regex`` | N/A | Enables regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. You can specify one of values: "case_sensitive", "case_insensitive" or "exact". | | [Path Regex](https://github.com/nginxinc/kubernetes-ingress/tree/examples/ingress-resources/path-regex). | {{% /table %}} ### Request URI/Header Manipulation diff --git a/examples/ingress-resources/path-regex/README.md b/examples/ingress-resources/path-regex/README.md new file mode 100644 index 0000000000..d87fdeb638 --- /dev/null +++ b/examples/ingress-resources/path-regex/README.md @@ -0,0 +1,158 @@ +# Support for path regular expressions + +NGINX and NGINX Plus support regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. + +The NGINX Ingress Controller provides the following annotations for configuring regular expression support: + +* Optional: ```nginx.com/path-regex: "case_sensitive"``` -- specifies a preceding regex modifier to be case sensitive (`~*`). +* Optional: ```nginx.com/path-regex: "case_insensitive"``` -- specifies a preceding regex modifier to be case sensitive (`~`). +* Optional: ```nginx.com/path-regex: "exact"``` -- specifies exact match preceding modifier (`=`). + +## Example 1: Case Sensitive RegEx + +In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `case_sensitive`. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cafe-ingress + annotations: + nginx.org/path-regex: "case_sensitive" +spec: + tls: + - hosts: + - cafe.example.com + secretName: cafe-secret + rules: + - host: cafe.example.com + http: + paths: + - path: /tea/[A-Z0-9] + backend: + serviceName: tea-svc + servicePort: 80 + - path: /coffee/[A-Z0-9] + backend: + serviceName: coffee-svc + servicePort: 80 +``` + +Corresponding NGINX config file snippet: + +```bash +... + + location ~ "^/tea/[A-Z0-9]" { + + set $service "tea-svc"; + status_zone "tea-svc"; + +... + + location ~ "^/coffee/[A-Z0-9]" { + + set $service "coffee-svc"; + status_zone "coffee-svc"; + +... +``` + +## Example 2: Case Insensitive RegEx + +In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `case_insensitive`. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cafe-ingress + annotations: + nginx.org/path-regex: "case_insensitive" +spec: + tls: + - hosts: + - cafe.example.com + secretName: cafe-secret + rules: + - host: cafe.example.com + http: + paths: + - path: /tea/[A-Z0-9] + backend: + serviceName: tea-svc + servicePort: 80 + - path: /coffee/[A-Z0-9] + backend: + serviceName: coffee-svc + servicePort: 80 +``` + +Corresponding NGINX config file snippet: + +```bash +... + + location ~* "^/tea/[A-Z0-9]" { + + set $service "tea-svc"; + status_zone "tea-svc"; + +... + + location ~* "^/coffee/[A-Z0-9]" { + + set $service "coffee-svc"; + status_zone "coffee-svc"; + +... +``` + +## Example 3: Exact RegEx + +In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `exact` match. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cafe-ingress + annotations: + nginx.org/path-regex: "exact" +spec: + tls: + - hosts: + - cafe.example.com + secretName: cafe-secret + rules: + - host: cafe.example.com + http: + paths: + - path: /tea/ + backend: + serviceName: tea-svc + servicePort: 80 + - path: /coffee/ + backend: + serviceName: coffee-svc + servicePort: 80 +``` + +Corresponding NGINX config file snippet: + +```bash +... + + location = "/tea" { + + set $service "tea-svc"; + status_zone "tea-svc"; + +... + + location = "/coffee" { + + set $service "coffee-svc"; + status_zone "coffee-svc"; +... +``` From 651d8e14659361eec856df4e728850aed6249f46 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Thu, 20 Jul 2023 14:14:27 +0100 Subject: [PATCH 04/11] Add tests for invalid path --- .../configs/version1/template_helper_test.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/configs/version1/template_helper_test.go b/internal/configs/version1/template_helper_test.go index a18967737a..3304f50d8b 100644 --- a/internal/configs/version1/template_helper_test.go +++ b/internal/configs/version1/template_helper_test.go @@ -39,6 +39,16 @@ func TestWithPathReqex_MatchesExactModifier(t *testing.T) { func TestWithPathReqex_DoesNotMatchModifier(t *testing.T) { t.Parallel() + want := "/coffee" + got := makePathRegex("/coffee", map[string]string{"nginx.org/path-regex": "bogus"}) + if got != want { + t.Errorf("got: %s, want: %s", got, want) + } +} + +func TestWithPathReqex_DoesNotMatchEmptyModifier(t *testing.T) { + t.Parallel() + want := "/coffee" got := makePathRegex("/coffee", map[string]string{"nginx.org/path-regex": ""}) if got != want { @@ -46,6 +56,16 @@ func TestWithPathReqex_DoesNotMatchModifier(t *testing.T) { } } +func TestWithPathReqex_DoesNotMatchBogusAnnotationName(t *testing.T) { + t.Parallel() + + want := "/coffee" + got := makePathRegex("/coffee", map[string]string{"nginx.org/bogus-annotation": ""}) + if got != want { + t.Errorf("got: %s, want: %s", got, want) + } +} + func TestSplitHelperFunction(t *testing.T) { t.Parallel() const tpl = `{{range $n := split . ","}}{{$n}} {{end}}` From a05f9fb133b15305e7896a40cae80b3159fbbe26 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Thu, 20 Jul 2023 17:20:07 +0100 Subject: [PATCH 05/11] Update docs --- .../ingress-resources/path-regex/README.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/examples/ingress-resources/path-regex/README.md b/examples/ingress-resources/path-regex/README.md index d87fdeb638..9292e26041 100644 --- a/examples/ingress-resources/path-regex/README.md +++ b/examples/ingress-resources/path-regex/README.md @@ -8,6 +8,27 @@ The NGINX Ingress Controller provides the following annotations for configuring * Optional: ```nginx.com/path-regex: "case_insensitive"``` -- specifies a preceding regex modifier to be case sensitive (`~`). * Optional: ```nginx.com/path-regex: "exact"``` -- specifies exact match preceding modifier (`=`). +[NGINX documentation](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) provides additional information about how NGINX and NGINX Plus resolve location priority. Read [it](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) before using the ``path-regex`` annotation. + +Nginx uses a specific syntax to decide which location block to use to handle a request. Location blocks live within server blocks (or other location blocks) and are used to decide how to process the request URI, for example: + +```bash +location optional_modifier location_match { + ... +} +``` + +The ``location_match`` defines what NGINX checks the request URI against. The existence or nonexistence of the modifier in the example affects the way that the Nginx attempts to match the location block. The modifiers you can apply using the ``path-regex`` annotation will cause the associated location block to be interpreted as follows: + +* **no modifier** : No modifiers (no annotation applied) - the location is interpreted as a prefix match. This means that the location given will be matched against the beginning of the request URI to determine a match + +* **~** : Tilde modifier (annotation value ``case_sensitive``) - the location is interpreted as a case-sensitive regular expression match + + +* **~***: Tilde and asterisk modifier (annotation value ``case_insensitive`) - the location is interpreted as a case-insensitive regular expression match + +* **=** : Equal sign modifier (annotation value ``exact``) - the location is considered a match if the request URI exactly matches the location provided. + ## Example 1: Case Sensitive RegEx In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `case_sensitive`. From d53949cf2360575c0c9e40ebd31a6164b9f97efc Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Thu, 20 Jul 2023 17:25:30 +0100 Subject: [PATCH 06/11] Update docs --- examples/ingress-resources/path-regex/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/ingress-resources/path-regex/README.md b/examples/ingress-resources/path-regex/README.md index 9292e26041..b847babca8 100644 --- a/examples/ingress-resources/path-regex/README.md +++ b/examples/ingress-resources/path-regex/README.md @@ -24,7 +24,6 @@ The ``location_match`` defines what NGINX checks the request URI against. The ex * **~** : Tilde modifier (annotation value ``case_sensitive``) - the location is interpreted as a case-sensitive regular expression match - * **~***: Tilde and asterisk modifier (annotation value ``case_insensitive`) - the location is interpreted as a case-insensitive regular expression match * **=** : Equal sign modifier (annotation value ``exact``) - the location is considered a match if the request URI exactly matches the location provided. From 87b88cbae13067b8eb3cfefe326a4e91449f673f Mon Sep 17 00:00:00 2001 From: Jakub Jarosz <99677300+jjngx@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:00:32 +0100 Subject: [PATCH 07/11] Update docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md Co-authored-by: Shaun Signed-off-by: Jakub Jarosz <99677300+jjngx@users.noreply.github.com> --- .../advanced-configuration-with-annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md b/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md index f26e4f8003..08fc8f5d46 100644 --- a/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md +++ b/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md @@ -112,7 +112,7 @@ The table below summarizes the available annotations. |``nginx.org/proxy-buffer-size`` | ``proxy-buffer-size`` | Sets the value of the [proxy_buffer_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) and [grpc_buffer_size](https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size) directives. | Depends on the platform. | | |``nginx.org/proxy-max-temp-file-size`` | ``proxy-max-temp-file-size`` | Sets the value of the [proxy_max_temp_file_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size) directive. | ``1024m`` | | |``nginx.org/server-tokens`` | ``server-tokens`` | Enables or disables the [server_tokens](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens) directive. Additionally, with the NGINX Plus, you can specify a custom string value, including the empty string value, which disables the emission of the “Server” field. | ``True`` | | -|``nginx.org/path-regex`` | N/A | Enables regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. You can specify one of values: "case_sensitive", "case_insensitive" or "exact". | | [Path Regex](https://github.com/nginxinc/kubernetes-ingress/tree/examples/ingress-resources/path-regex). | +|``nginx.org/path-regex`` | N/A | Enables regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. You can specify one of these values: "case_sensitive", "case_insensitive" or "exact". | | [Path Regex](https://github.com/nginxinc/kubernetes-ingress/tree/examples/ingress-resources/path-regex). | {{% /table %}} ### Request URI/Header Manipulation From f1e7293a798b04a17dc04e41bac82def1d5414e5 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz <99677300+jjngx@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:00:44 +0100 Subject: [PATCH 08/11] Update examples/ingress-resources/path-regex/README.md Co-authored-by: Shaun Signed-off-by: Jakub Jarosz <99677300+jjngx@users.noreply.github.com> --- examples/ingress-resources/path-regex/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ingress-resources/path-regex/README.md b/examples/ingress-resources/path-regex/README.md index b847babca8..8eb3fc743d 100644 --- a/examples/ingress-resources/path-regex/README.md +++ b/examples/ingress-resources/path-regex/README.md @@ -24,7 +24,7 @@ The ``location_match`` defines what NGINX checks the request URI against. The ex * **~** : Tilde modifier (annotation value ``case_sensitive``) - the location is interpreted as a case-sensitive regular expression match -* **~***: Tilde and asterisk modifier (annotation value ``case_insensitive`) - the location is interpreted as a case-insensitive regular expression match +* **~***: Tilde and asterisk modifier (annotation value ``case_insensitive``) - the location is interpreted as a case-insensitive regular expression match * **=** : Equal sign modifier (annotation value ``exact``) - the location is considered a match if the request URI exactly matches the location provided. From 355572dff34725c52fbcd5da1c3a3af1b5a31dbc Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Mon, 24 Jul 2023 19:15:44 +0100 Subject: [PATCH 09/11] Update annotation name --- examples/ingress-resources/path-regex/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/ingress-resources/path-regex/README.md b/examples/ingress-resources/path-regex/README.md index 8eb3fc743d..6dc6c86a3d 100644 --- a/examples/ingress-resources/path-regex/README.md +++ b/examples/ingress-resources/path-regex/README.md @@ -4,9 +4,9 @@ NGINX and NGINX Plus support regular expression modifiers for [location](https:/ The NGINX Ingress Controller provides the following annotations for configuring regular expression support: -* Optional: ```nginx.com/path-regex: "case_sensitive"``` -- specifies a preceding regex modifier to be case sensitive (`~*`). -* Optional: ```nginx.com/path-regex: "case_insensitive"``` -- specifies a preceding regex modifier to be case sensitive (`~`). -* Optional: ```nginx.com/path-regex: "exact"``` -- specifies exact match preceding modifier (`=`). +* Optional: ```nginx.org/path-regex: "case_sensitive"``` - specifies a preceding regex modifier to be case sensitive (`~*`). +* Optional: ```nginx.org/path-regex: "case_insensitive"``` - specifies a preceding regex modifier to be case sensitive (`~`). +* Optional: ```nginx.org/path-regex: "exact"``` - specifies exact match preceding modifier (`=`). [NGINX documentation](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) provides additional information about how NGINX and NGINX Plus resolve location priority. Read [it](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) before using the ``path-regex`` annotation. @@ -30,7 +30,7 @@ The ``location_match`` defines what NGINX checks the request URI against. The ex ## Example 1: Case Sensitive RegEx -In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `case_sensitive`. +In the following example you enable path regex annotation ``nginx.org/path-regex`` and set its value to `case_sensitive`. ```yaml apiVersion: networking.k8s.io/v1 @@ -80,7 +80,7 @@ Corresponding NGINX config file snippet: ## Example 2: Case Insensitive RegEx -In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `case_insensitive`. +In the following example you enable path regex annotation ``nginx.org/path-regex`` and set its value to `case_insensitive`. ```yaml apiVersion: networking.k8s.io/v1 @@ -130,7 +130,7 @@ Corresponding NGINX config file snippet: ## Example 3: Exact RegEx -In the following example you enable path regex annotation ``nginx.com/path-regex`` and set its value to `exact` match. +In the following example you enable path regex annotation ``nginx.org/path-regex`` and set its value to `exact` match. ```yaml apiVersion: networking.k8s.io/v1 From fab89aaaaaec731acd97c69d53ae7658fe04132a Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Tue, 25 Jul 2023 10:50:10 +0100 Subject: [PATCH 10/11] Update docs --- .../advanced-configuration-with-annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md b/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md index 08fc8f5d46..a86d09700e 100644 --- a/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md +++ b/docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md @@ -112,7 +112,7 @@ The table below summarizes the available annotations. |``nginx.org/proxy-buffer-size`` | ``proxy-buffer-size`` | Sets the value of the [proxy_buffer_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) and [grpc_buffer_size](https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size) directives. | Depends on the platform. | | |``nginx.org/proxy-max-temp-file-size`` | ``proxy-max-temp-file-size`` | Sets the value of the [proxy_max_temp_file_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size) directive. | ``1024m`` | | |``nginx.org/server-tokens`` | ``server-tokens`` | Enables or disables the [server_tokens](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens) directive. Additionally, with the NGINX Plus, you can specify a custom string value, including the empty string value, which disables the emission of the “Server” field. | ``True`` | | -|``nginx.org/path-regex`` | N/A | Enables regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. You can specify one of these values: "case_sensitive", "case_insensitive" or "exact". | | [Path Regex](https://github.com/nginxinc/kubernetes-ingress/tree/examples/ingress-resources/path-regex). | +|``nginx.org/path-regex`` | N/A | Enables regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. You can specify one of these values: "case_sensitive", "case_insensitive" or "exact". The annotation is applied to the entire Ingress resource and its paths. | | [Path Regex](https://github.com/nginxinc/kubernetes-ingress/tree/examples/ingress-resources/path-regex). | {{% /table %}} ### Request URI/Header Manipulation From 92a4fa67f1f7f2d813d60197b2f13bbc91d05724 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Wed, 26 Jul 2023 09:59:21 +0100 Subject: [PATCH 11/11] Update readme doc --- .../ingress-resources/path-regex/README.md | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/examples/ingress-resources/path-regex/README.md b/examples/ingress-resources/path-regex/README.md index 6dc6c86a3d..b308d969fe 100644 --- a/examples/ingress-resources/path-regex/README.md +++ b/examples/ingress-resources/path-regex/README.md @@ -1,16 +1,22 @@ # Support for path regular expressions -NGINX and NGINX Plus support regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) directive. +NGINX and NGINX Plus support regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) + directive. The NGINX Ingress Controller provides the following annotations for configuring regular expression support: -* Optional: ```nginx.org/path-regex: "case_sensitive"``` - specifies a preceding regex modifier to be case sensitive (`~*`). -* Optional: ```nginx.org/path-regex: "case_insensitive"``` - specifies a preceding regex modifier to be case sensitive (`~`). -* Optional: ```nginx.org/path-regex: "exact"``` - specifies exact match preceding modifier (`=`). +- Optional: ```nginx.org/path-regex: "case_sensitive"``` - specifies a preceding regex modifier to be case sensitive (`~*`). +- Optional: ```nginx.org/path-regex: "case_insensitive"``` - specifies a preceding regex modifier to be case sensitive (`~`). +- Optional: ```nginx.org/path-regex: "exact"``` - specifies exact match preceding modifier (`=`). -[NGINX documentation](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) provides additional information about how NGINX and NGINX Plus resolve location priority. Read [it](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) before using the ``path-regex`` annotation. +[NGINX documentation](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) provides +additional information about how NGINX and NGINX Plus resolve location priority. +Read [it](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) before using + the ``path-regex`` annotation. -Nginx uses a specific syntax to decide which location block to use to handle a request. Location blocks live within server blocks (or other location blocks) and are used to decide how to process the request URI, for example: +Nginx uses a specific syntax to decide which location block to use to handle a request. +Location blocks live within server blocks (or other location blocks) and are used to decide how to process + the request URI, for example: ```bash location optional_modifier location_match { @@ -18,15 +24,22 @@ location optional_modifier location_match { } ``` -The ``location_match`` defines what NGINX checks the request URI against. The existence or nonexistence of the modifier in the example affects the way that the Nginx attempts to match the location block. The modifiers you can apply using the ``path-regex`` annotation will cause the associated location block to be interpreted as follows: +The ``location_match`` defines what NGINX checks the request URI against. The existence or nonexistence of the modifier + in the example affects the way that the Nginx attempts to match the location block. + The modifiers you can apply using the ``path-regex`` annotation will cause the associated location block + to be interpreted as follows: -* **no modifier** : No modifiers (no annotation applied) - the location is interpreted as a prefix match. This means that the location given will be matched against the beginning of the request URI to determine a match +- **no modifier** : No modifiers (no annotation applied) - the location is interpreted as a prefix match. +This means that the location given will be matched against the beginning of the request URI to determine a match -* **~** : Tilde modifier (annotation value ``case_sensitive``) - the location is interpreted as a case-sensitive regular expression match +- **~** : Tilde modifier (annotation value ``case_sensitive``) - the location is interpreted as a case-sensitive +regular expression match -* **~***: Tilde and asterisk modifier (annotation value ``case_insensitive``) - the location is interpreted as a case-insensitive regular expression match +- **~***: Tilde and asterisk modifier (annotation value ``case_insensitive``) - the location is interpreted +as a case-insensitive regular expression match -* **=** : Equal sign modifier (annotation value ``exact``) - the location is considered a match if the request URI exactly matches the location provided. +- **=** : Equal sign modifier (annotation value ``exact``) - the location is considered a match if the request + URI exactly matches the location provided. ## Example 1: Case Sensitive RegEx