Skip to content

Commit 14b4b0c

Browse files
authored
add rewrite-target annotation (#8508)
1 parent e9d7c1c commit 14b4b0c

File tree

19 files changed

+1969
-3
lines changed

19 files changed

+1969
-3
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Support for Rewrite Target
2+
3+
The `nginx.org/rewrite-target` annotation enables URL path rewriting by specifying a target path that requests should be rewritten to. This annotation works with regular expression capture groups from the Ingress path to create dynamic rewrites.
4+
5+
The annotation is mutually exclusive with `nginx.org/rewrites`. If both are present, `nginx.org/rewrites` takes precedence.
6+
7+
## Running the Example
8+
9+
## 1. Deploy the Ingress Controller
10+
11+
1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) instructions to deploy the Ingress Controller.
12+
13+
2. Save the public IP address of the Ingress Controller into a shell variable:
14+
```console
15+
IC_IP=XXX.YYY.ZZZ.III
16+
```
17+
18+
3. Save the HTTP port of the Ingress Controller into a shell variable:
19+
```console
20+
IC_HTTP_PORT=<port number>
21+
```
22+
23+
## 2. Deploy the Cafe Application
24+
25+
Create the coffee and tea deployments and services:
26+
27+
```console
28+
kubectl create -f cafe.yaml
29+
```
30+
31+
## 3. Configure Rewrite Examples
32+
33+
### Example 1: Simple Static Rewrite
34+
35+
Create an Ingress resource with basic rewrite functionality:
36+
37+
```console
38+
kubectl create -f simple-rewrite.yaml
39+
```
40+
41+
This configures rewriting from `/coffee` to `/beverages/coffee`.
42+
43+
### Example 2: Dynamic Rewrite with Regex
44+
45+
Create an Ingress resource with regular expression-based rewriting:
46+
47+
```console
48+
kubectl create -f regex-rewrite.yaml
49+
```
50+
51+
This configures dynamic rewriting using capture groups from `/menu/([^/]+)/([^/]+)` to `/beverages/$1/$2`.
52+
53+
## 4. Test the Application
54+
55+
### Test Simple Rewrite
56+
57+
Access the coffee service through the rewritten path:
58+
59+
```console
60+
curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP http://cafe.example.com:$IC_HTTP_PORT/coffee --insecure
61+
```
62+
63+
```text
64+
Server address: 10.16.0.16:8080
65+
Server name: coffee-676c9f8944-n2bmb
66+
Date: 07/Nov/2025:11:23:09 +0000
67+
URI: /beverages/coffee
68+
Request ID: c224b3e06d79b66f8f33e86cef046c32
69+
```
70+
71+
The request to `/coffee` is rewritten to `/beverages/coffee`.
72+
73+
### Test Regex Rewrite
74+
75+
Access the service using the menu path with dynamic rewriting:
76+
77+
```console
78+
curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP http://cafe.example.com:$IC_HTTP_PORT/menu/coffee/espresso --insecure
79+
```
80+
81+
```text
82+
Server address: 10.16.1.29:8080
83+
Server name: coffee-676c9f8944-vj45p
84+
Date: 07/Nov/2025:11:26:05 +0000
85+
URI: /beverages/coffee/espresso
86+
Request ID: 88334a8b0eeaee2ffe4fdb4c7768641b
87+
```
88+
89+
```console
90+
curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP http://cafe.example.com:$IC_HTTP_PORT/menu/tea/green --insecure
91+
```
92+
93+
```text
94+
Server address: 10.16.0.16:8080
95+
Server name: coffee-676c9f8944-n2bmb
96+
Date: 07/Nov/2025:11:26:33 +0000
97+
URI: /beverages/tea/green
98+
Request ID: 2ba8f9055aecc059b32f797f1ce2aca5
99+
```
100+
101+
The requests to `/menu/coffee/espresso` and `/menu/tea/green` are rewritten to `/beverages/coffee/espresso` and `/beverages/tea/green` using the captured groups.
102+
103+
## Validations
104+
105+
1. Mutual Exclusivity: The `nginx.org/rewrite-target` annotation is mutually exclusive with `nginx.org/rewrites`. If both annotations are present, `nginx.org/rewrites` takes precedence and a warning will be generated.
106+
107+
2. Security Validation: The annotation includes built-in security validation to prevent:
108+
- Absolute URLs (`http://` or `https://`)
109+
- Protocol-relative URLs (`//`)
110+
- Path traversal patterns (`../` or `..\\`)
111+
- Paths not starting with `/`
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: coffee
5+
spec:
6+
replicas: 2
7+
selector:
8+
matchLabels:
9+
app: coffee
10+
template:
11+
metadata:
12+
labels:
13+
app: coffee
14+
spec:
15+
containers:
16+
- name: coffee
17+
image: nginxdemos/nginx-hello:plain-text
18+
ports:
19+
- containerPort: 8080
20+
---
21+
apiVersion: v1
22+
kind: Service
23+
metadata:
24+
name: coffee-svc
25+
spec:
26+
ports:
27+
- port: 80
28+
targetPort: 8080
29+
protocol: TCP
30+
name: http
31+
selector:
32+
app: coffee
33+
---
34+
apiVersion: apps/v1
35+
kind: Deployment
36+
metadata:
37+
name: tea
38+
spec:
39+
replicas: 3
40+
selector:
41+
matchLabels:
42+
app: tea
43+
template:
44+
metadata:
45+
labels:
46+
app: tea
47+
spec:
48+
containers:
49+
- name: tea
50+
image: nginxdemos/nginx-hello:plain-text
51+
ports:
52+
- containerPort: 8080
53+
---
54+
apiVersion: v1
55+
kind: Service
56+
metadata:
57+
name: tea-svc
58+
spec:
59+
ports:
60+
- port: 80
61+
targetPort: 8080
62+
protocol: TCP
63+
name: http
64+
selector:
65+
app: tea
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: cafe-ingress
5+
annotations:
6+
nginx.org/path-regex: "case_sensitive"
7+
nginx.org/rewrite-target: "/beverages/$1/$2"
8+
spec:
9+
ingressClassName: nginx
10+
rules:
11+
- host: cafe.example.com
12+
http:
13+
paths:
14+
- path: /menu/([^/]+)/([^/]+)
15+
pathType: ImplementationSpecific
16+
backend:
17+
service:
18+
name: coffee-svc
19+
port:
20+
number: 80
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: cafe-ingress
5+
annotations:
6+
nginx.org/rewrite-target: "/beverages/coffee"
7+
spec:
8+
ingressClassName: nginx
9+
rules:
10+
- host: cafe.example.com
11+
http:
12+
paths:
13+
- path: /coffee
14+
pathType: Prefix
15+
backend:
16+
service:
17+
name: coffee-svc
18+
port:
19+
number: 80

internal/configs/annotations.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const BasicAuthSecretAnnotation = "nginx.org/basic-auth-secret" // #nosec G101
1818
// PathRegexAnnotation is the annotation where the regex location (path) modifier is specified.
1919
const PathRegexAnnotation = "nginx.org/path-regex"
2020

21+
// RewriteTargetAnnotation is the annotation where the regex-based rewrite target is specified.
22+
const RewriteTargetAnnotation = "nginx.org/rewrite-target"
23+
2124
// SSLCiphersAnnotation is the annotation where SSL ciphers are specified.
2225
const SSLCiphersAnnotation = "nginx.org/ssl-ciphers"
2326

@@ -590,6 +593,26 @@ func getRewrites(ctx context.Context, ingEx *IngressEx) map[string]string {
590593
return nil
591594
}
592595

596+
func getRewriteTarget(ctx context.Context, ingEx *IngressEx) (string, Warnings) {
597+
l := nl.LoggerFromContext(ctx)
598+
warnings := newWarnings()
599+
600+
// Check for mutual exclusivity
601+
if _, hasRewrites := ingEx.Ingress.Annotations["nginx.org/rewrites"]; hasRewrites {
602+
if _, hasRewriteTarget := ingEx.Ingress.Annotations[RewriteTargetAnnotation]; hasRewriteTarget {
603+
warningMsg := "nginx.org/rewrites and nginx.org/rewrite-target annotations are mutually exclusive; nginx.org/rewrites will take precedence"
604+
nl.Errorf(l, "Ingress %s/%s: %s", ingEx.Ingress.Namespace, ingEx.Ingress.Name, warningMsg)
605+
warnings.AddWarning(ingEx.Ingress, warningMsg)
606+
return "", warnings
607+
}
608+
}
609+
610+
if value, exists := ingEx.Ingress.Annotations[RewriteTargetAnnotation]; exists {
611+
return value, warnings
612+
}
613+
return "", warnings
614+
}
615+
593616
func getSSLServices(ingEx *IngressEx) map[string]bool {
594617
if value, exists := ingEx.Ingress.Annotations["nginx.org/ssl-services"]; exists {
595618
return ParseServiceList(value)

0 commit comments

Comments
 (0)