8000 Add path-regex annotation for ingress (#4127) · nginx/kubernetes-ingress@eef59d2 · GitHub
[go: up one dir, main page]

Skip to content

Commit eef59d2

Browse files
authored
Add path-regex annotation for ingress (#4127)
1 parent 0a02259 commit eef59d2

File tree

10 files changed

+1041
-155
lines changed

10 files changed

+1041
-155
lines changed

docs/content/configuration/ingress-resources/advanced-configuration-with-annotations.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ The table below summarizes the available annotations.
112112
|``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. | |
113113
|``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`` | |
114114
|``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`` | |
115+
|``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). |
115116
{{% /table %}}
116117

117118
### Request URI/Header Manipulation
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Support for path regular expressions
2+
3+
NGINX and NGINX Plus support regular expression modifiers for [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location)
4+
directive.
5+
6+
The NGINX Ingress Controller provides the following annotations for configuring regular expression support:
7+
8+
- Optional: ```nginx.org/path-regex: "case_sensitive"``` - specifies a preceding regex modifier to be case sensitive (`~*`).
9+
- Optional: ```nginx.org/path-regex: "case_insensitive"``` - specifies a preceding regex modifier to be case sensitive (`~`).
10+
- Optional: ```nginx.org/path-regex: "exact"``` - specifies exact match preceding modifier (`=`).
11+
12+
[NGINX documentation](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) provides
13+
additional information about how NGINX and NGINX Plus resolve location priority.
14+
Read [it](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#nginx-location-priority) before using
15+
the ``path-regex`` annotation.
16+
17+
Nginx uses a specific syntax to decide which location block to use to handle a request.
18+
Location blocks live within server blocks (or other location blocks) and are used to decide how to process
19+
the request URI, for example:
20+
21+
```bash
22+
location optional_modifier location_match {
23+
...
24+
}
25+
```
26+
27+
The ``location_match`` defines what NGINX checks the request URI against. The existence or nonexistence of the modifier
28+
in the example affects the way that the Nginx attempts to match the location block.
29+
The modifiers you can apply using the ``path-regex`` annotation will cause the associated location block
30+
to be interpreted as follows:
31+
32+
- **no modifier** : No modifiers (no annotation applied) - the location is interpreted as a prefix match.
33+
This means that the location given will be matched against the beginning of the request URI to determine a match
34+
35+
- **~** : Tilde modifier (annotation value ``case_sensitive``) - the location is interpreted as a case-sensitive
36+
regular expression match
37+
38+
- **~***: Tilde and asterisk modifier (annotation value ``case_insensitive``) - the location is interpreted
39+
as a case-insensitive regular expression match
40+
41+
- **=** : Equal sign modifier (annotation value ``exact``) - the location is considered a match if the request
42+
URI exactly matches the location provided.
43+
44+
## Example 1: Case Sensitive RegEx
45+
46+
In the following example you enable path regex annotation ``nginx.org/path-regex`` and set its value to `case_sensitive`.
47+
48+
```yaml
49+
apiVersion: networking.k8s.io/v1
50+
kind: Ingress
51+
metadata:
52+
name: cafe-ingress
53+
annotations:
54+
nginx.org/path-regex: "case_sensitive"
55+
spec:
56+
tls:
57+
- hosts:
58+
- cafe.example.com
59+
secretName: cafe-secret
60+
rules:
61+
- host: cafe.example.com
62+
http:
63+
paths:
64+
- path: /tea/[A-Z0-9]
65+
backend:
66+
serviceName: tea-svc
67+
servicePort: 80
68+
- path: /coffee/[A-Z0-9]
69+
backend:
70+
serviceName: coffee-svc
71+
servicePort: 80
72+
```
73+
74+
Corresponding NGINX config file snippet:
75+
76+
```bash
77+
...
78+
79+
location ~ "^/tea/[A-Z0-9]" {
80+
81+
set $service "tea-svc";
82+
status_zone "tea-svc";
83+
84+
...
85+
86+
location ~ "^/coffee/[A-Z0-9]" {
87+
88+
set $service "coffee-svc";
89+
status_zone "coffee-svc";
90+
91+
...
92+
```
93+
94+
## Example 2: Case Insensitive RegEx
95+
96+
In the following example you enable path regex annotation ``nginx.org/path-regex`` and set its value to `case_insensitive`.
97+
98+
```yaml
99+
apiVersion: networking.k8s.io/v1
100+
kind: Ingress
101+
metadata:
102+
name: cafe-ingress
103+
annotations:
104+
nginx.org/path-regex: "case_insensitive"
105+
spec:
106+
tls:
107+
- hosts:
108+
- cafe.example.com
109+
secretName: cafe-secret
110+
rules:
111+
- host: cafe.example.com
112+
http:
113+
paths:
114+
- path: /tea/[A-Z0-9]
115+
backend:
116+
serviceName: tea-svc
117+
servicePort: 80
118+
- path: /coffee/[A-Z0-9]
119+
backend:
120+
serviceName: coffee-svc
121+
servicePort: 80
122+
```
123+
124+
Corresponding NGINX config file snippet:
125+
126+
```bash
127+
...
128+
129+
location ~* "^/tea/[A-Z0-9]" {
130+
131+
set $service "tea-svc";
132+
status_zone "tea-svc";
133+
134+
...
135+
136+
location ~* "^/coffee/[A-Z0-9]" {
137+
138+
set $service "coffee-svc";
139+
status_zone "coffee-svc";
140+
141+
...
142+
```
143+
144+
## Example 3: Exact RegEx
145+
146+
In the following example you enable path regex annotation ``nginx.org/path-regex`` and set its value to `exact` match.
147+
148+
```yaml
149+
apiVersion: networking.k8s.io/v1
150+
kind: Ingress
151+
metadata:
152+
name: cafe-ingress
153+
annotations:
154+
nginx.org/path-regex: "exact"
155+
spec:
156+
tls:
157+
- hosts:
158+
- cafe.example.com
159+
secretName: cafe-secret
160+
rules:
161+
- host: cafe.example.com
162+
http:
163+
paths:
164+
- path: /tea/
165+
backend:
166+
serviceName: tea-svc
167+
servicePort: 80
168+
- path: /coffee/
169+
backend:
170+
serviceName: coffee-svc
171+
servicePort: 80
172+
```
173+
174+
Corresponding NGINX config file snippet:
175+
176+
```bash
177+
...
178+
179+
location = "/tea" {
180+
181+
set $service "tea-svc";
182+
status_zone "tea-svc";
183+
184+
...
185+
186+
location = "/coffee" {
187+
188+
set $service "coffee-svc";
189+
status_zone "coffee-svc";
190+
...
191+
```

internal/configs/annotations.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const JWTKeyAnnotation = "nginx.com/jwt-key"
1 65CE 010
// BasicAuthSecretAnnotation is the annotation where the Secret with the HTTP basic user list
1111
const BasicAuthSecretAnnotation = "nginx.org/basic-auth-secret" // #nosec G101
1212

13+
// PathRegexAnnotation is the annotation where the regex location (path) modifier is specified.
14+
const PathRegexAnnotation = "nginx.org/path-regex"
15+
1316
// AppProtectPolicyAnnotation is where the NGINX App Protect policy is specified
1417
const AppProtectPolicyAnnotation = "appprotect.f5.com/app-protect-policy"
1518

@@ -73,6 +76,12 @@ var minionInheritanceList = map[string]bool{
7376
"nginx.org/fail-timeout": true,
7477
}
7578

79+
var validPathRegex = map[string]bool{
80+
"case_sensitive": true,
81+
"case_insensitive": true,
82+
"exact": true,
83+
}
84+
7685
func parseAnnotations(ingEx *IngressEx, baseCfgParams *ConfigParams, isPlus bool, hasAppProtect bool, hasAppProtectDos bool, enableInternalRoutes bool) ConfigParams {
7786
cfgParams := *baseCfgParams
7887

@@ -385,6 +394,13 @@ func parseAnnotations(ingEx *IngressEx, baseCfgParams *ConfigParams, isPlus bool
385394
}
386395
}
387396
}
397+
398+
if pathRegex, exists := ingEx.Ingress.Annotations[PathRegexAnnotation]; exists {
399+
_, ok := validPathRegex[pathRegex]
400+
if !ok {
401+
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)
402+
}
403+
}
388404
return cfgParams
389405
}
390406

internal/configs/version1/nginx-plus.ingress.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ server {
177177
{{end -}}
178178

179179
{{range $location := $server.Locations}}
180-
location {{$location.Path}} {
180+
{{ if index $.Ingress.Annotations "nginx.org/path-regex" }}
181+
location {{ makePathRegex $location.Path $.Ingress.Annotations | printf }} {
182+
{{ else }}
183+
location {{ $location.Path }} {
184+
{{ end }}
181185
set $service "{{$location.ServiceName}}";
182186
status_zone "{{ $location.ServiceName }}";
183187
{{with $location.MinionIngress}}

internal/configs/version1/nginx.ingress.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ server {
102102
{{- end}}
103103

104104
{{range $location := $server.Locations}}
105-
location {{$location.Path}} {
105+
{{ if index $.Ingress.Annotations "nginx.org/path-regex" }}
106+
location {{ makePathRegex $location.Path $.Ingress.Annotations | printf }} {
107+
{{ else }}
108+
location {{ $location.Path }} {
109+
{{ end }}
106110
set $service "{{$location.ServiceName}}";
107111
{{with $location.MinionIngress}}
108112
# location for minion {{$location.MinionIngress.Namespace}}/{{$location.MinionIngress.Name}}

internal/configs/version1/template_helper.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package version1
22

33
import (
4+
"fmt"
45
"strings"
56
"text/template"
67
)
@@ -13,7 +14,31 @@ func trim(s string) string {
1314
return strings.TrimSpace(s)
1415
}
1516

17+
// makePathRegex takes a string representing a location path
18+
// and a map representing Ingress annotations.
19+
// It returns a location path with added regular expression modifier.
20+
// See [Location Directive].
21+
//
22+
// [Location Directive]: https://nginx.org/en/docs/http/ngx_http_core_module.html#location
23+
func makePathRegex(path string, annotations map[string]string) string {
24+
p, ok := annotations["nginx.org/path-regex"]
25+
if !ok {
26+
return path
27+
}
28+
switch p {
29+
case "case_sensitive":
30+
return fmt.Sprintf("~ \"^%s\"", path)
31+
case "case_insensitive":
32+
return fmt.Sprintf("~* \"^%s\"", path)
33+
case "exact":
34+
return fmt.Sprintf("= \"%s\"", path)
35+
default:
36+
return path
37+
}
38+
}
39+
1640
var helperFunctions = template.FuncMap{
17-
"split": split,
18-
"trim": trim,
41+
"split": split,
42+
"trim": trim,
43+
"makePathRegex": makePathRegex,
1944
}

0 commit comments

Comments
 (0)
0