Files
higress/plugins/wasm-go/extensions/nginx-rewrite-compatible/README_EN.md
2026-05-14 11:24:58 +08:00

256 lines
6.7 KiB
Markdown

---
title: Nginx Rewrite Compatibility Migration
keywords: [higress, nginx, rewrite, set, migration]
description: Secure migration plugin for nginx rewrite + set
---
## Features
The `nginx-rewrite-compatible` plugin provides the common behavior of `nginx rewrite + set`, including path rewrites, query append or replacement, capture-group variable storage, and optional variable propagation to upstream services through request headers.
It is designed as a secure migration alternative when moving from Nginx to Higress, so users do not need to keep relying on the rewrite path affected by `CVE-2026-42945`.
## Security Background
`CVE-2026-42945` is a long-standing heap overflow issue related to the interaction between Nginx `rewrite` and `set`. The vulnerable pattern is:
1. A `rewrite` rule uses a replacement containing `?`, so URI and query string are updated during one rewrite pass.
2. A later `set` still references capture groups such as `$1` or `$2`.
3. The state kept across rewrite passes becomes inconsistent, so `set` reads capture-group metadata from a mismatched state and eventually triggers out-of-bounds access and heap corruption.
The Higress WASM approach does not have this problem because:
1. Each request is handled in an isolated WASM request context.
2. This plugin performs match, rewrite, variable extraction, and upstream propagation in one request callback instead of relying on Nginx's multi-pass rewrite state machine.
3. Capture-group data lives only inside the current request and request properties, so there is no cross-pass state leakage.
## Runtime Properties
Plugin execution phase: `UNSPECIFIED`
Plugin execution priority: `100`
## Configuration Fields
| Field Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `rules` | array of object | Yes | - | Ordered rewrite rules |
### `rules`
| Field Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `regex` | string | Yes | - | Regular expression that matches the request path without the query string |
| `replacement` | string | Yes | - | New path template. Supports capture references such as `$1` and `$2` |
| `query_append` | string | No | - | Query fragment appended to the existing query string. Supports `$1`, `$2` |
| `query_template` | string | No | - | Query template that replaces the existing query string. Supports `$1`, `$2` |
| `set_vars` | array of object | No | - | Stores capture groups as request-scoped variables, or rewrites query/header/cookie based on variable prefixes |
| `pass_to_upstream` | bool | No | `false` | Whether variables from the current rule should also be written into upstream request headers |
| `mode` | string | No | `last` | Rule flow mode. Supported values: `break`, `last` |
Notes:
1. `query_append` and `query_template` are mutually exclusive.
2. `mode: break` stops evaluation after the current matching rule.
3. `mode: last` continues evaluating the following rules with the rewritten path.
4. In `set_vars`, the `arg_` prefix rewrites a request query parameter, `http_` rewrites a request header, `cookie_` rewrites a request cookie, and any other name is stored with `proxywasm.SetProperty([]string{"nginx_rewrite_compatible","vars",name})`.
5. For `http_`, the header name is derived by removing the prefix and converting underscores to hyphens.
6. When `pass_to_upstream: true`, variables are also written to `x-higress-rewrite-var-<name>`.
### `set_vars`
| Field Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Variable name. `arg_`/`http_`/`cookie_` mean query parameter, header, and cookie updates respectively; other names become custom properties |
| `capture_group` | int | Yes | Capture-group index. `0` means the whole match and `1` means the first group |
## Nginx Mapping Table
### 1. Simple Path Rewrite
Nginx:
```nginx
rewrite ^/old/(.*)$ /new/$1;
```
Plugin:
```yaml
rules:
- regex: ^/old/(.*)$
replacement: /new/$1
```
### 2. Capture-Group Replacement
Nginx:
```nginx
rewrite ^/product/([0-9]+)$ /detail/$1;
```
Plugin:
```yaml
rules:
- regex: ^/product/([0-9]+)$
replacement: /detail/$1
```
### 3. Query String Operations
Append query:
```nginx
rewrite ^/api/(.*)$ /internal?migrated=true;
```
```yaml
rules:
- regex: ^/api/(.*)$
replacement: /internal
query_append: migrated=true
```
Replace query:
```nginx
rewrite ^/x/(.*)/(.*)$ /y?a=$1&b=$2;
```
```yaml
rules:
- regex: ^/x/(.*)/(.*)$
replacement: /y
query_template: a=$1&b=$2
```
### 4. Special Variable Prefixes
```yaml
rules:
- regex: "^/api/(.*)/(.*)$"
replacement: "/internal"
set_vars:
- name: original_path
capture_group: 1
- name: http_x_original
capture_group: 1
- name: arg_source
capture_group: 2
- name: cookie_track_id
capture_group: 1
```
Semantics:
1. `original_path` is stored as a request property and can be read later with `GetProperty(["nginx_rewrite_compatible","vars","original_path"])`.
2. `http_x_original` sets the request header `x-original`.
3. `arg_source` sets the query parameter `source`.
4. `cookie_track_id` sets the cookie `track_id`.
### 5. Variable Preservation and Propagation
Nginx:
```nginx
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1;
```
Plugin:
```yaml
rules:
- regex: ^/api/(.*)$
replacement: /internal
query_append: migrated=true
set_vars:
- name: original_endpoint
capture_group: 1
pass_to_upstream: true
```
### 6. Multiple Rules
Nginx:
```nginx
rewrite ^/stage/(.*)$ /mid/$1;
rewrite ^/mid/(.*)$ /final/$1;
```
Plugin:
```yaml
rules:
- regex: ^/stage/(.*)$
replacement: /mid/$1
mode: last
- regex: ^/mid/(.*)$
replacement: /final/$1
```
### 7. `break` / `last`
Nginx `break`:
```nginx
rewrite ^/stage/(.*)$ /mid/$1 break;
```
```yaml
rules:
- regex: ^/stage/(.*)$
replacement: /mid/$1
mode: break
```
Nginx `last`:
```nginx
rewrite ^/stage/(.*)$ /mid/$1 last;
rewrite ^/mid/(.*)$ /final/$1;
```
```yaml
rules:
- regex: ^/stage/(.*)$
replacement: /mid/$1
mode: last
- regex: ^/mid/(.*)$
replacement: /final/$1
```
## Example
```yaml
rules:
- regex: ^/api/(.*)$
replacement: /internal
query_append: migrated=true
set_vars:
- name: original_endpoint
capture_group: 1
- name: http_x_original_endpoint
capture_group: 1
- name: arg_source
capture_group: 1
- name: cookie_track_id
capture_group: 1
pass_to_upstream: true
mode: break
- regex: ^/old/(.*)$
replacement: /new/$1
- regex: ^/x/(.*)/(.*)$
replacement: /y
query_template: a=$1&b=$2
set_vars:
- name: first
capture_group: 1
- name: second
capture_group: 2
```