mirror of
https://github.com/alibaba/higress.git
synced 2026-06-26 02:35:02 +08:00
feat: add rust demo plugin request block (#1091)
Co-authored-by: Yi <lynskylate@gmail.com>
This commit is contained in:
2
.github/workflows/build-and-test-plugin.yaml
vendored
2
.github/workflows/build-and-test-plugin.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# TODO(Xunzhuo): Enable C WASM Filters in CI
|
# TODO(Xunzhuo): Enable C WASM Filters in CI
|
||||||
wasmPluginType: [ GO ]
|
wasmPluginType: [ GO, RUST ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|||||||
10
plugins/wasm-rust/Cargo.lock
generated
10
plugins/wasm-rust/Cargo.lock
generated
@@ -43,6 +43,7 @@ dependencies = [
|
|||||||
name = "higress-wasm-rust"
|
name = "higress-wasm-rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"multimap",
|
||||||
"proxy-wasm",
|
"proxy-wasm",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -70,6 +71,15 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multimap"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.1"
|
version = "1.17.1"
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ proxy-wasm = "0.2.1"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
uuid = { version = "1.3.3", features = ["v4"] }
|
uuid = { version = "1.3.3", features = ["v4"] }
|
||||||
|
multimap = "0"
|
||||||
|
|||||||
243
plugins/wasm-rust/extensions/request-block/Cargo.lock
generated
Normal file
243
plugins/wasm-rust/extensions/request-block/Cargo.lock
generated
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "higress-wasm-rust"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"multimap",
|
||||||
|
"proxy-wasm",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.144"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multimap"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.17.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proxy-wasm"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "823b744520cd4a54ba7ebacbffe4562e839d6dcd8f89209f96a1ace4f5229cd4"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "request-block"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"higress-wasm-rust",
|
||||||
|
"multimap",
|
||||||
|
"proxy-wasm",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.163"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.163"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.96"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
17
plugins/wasm-rust/extensions/request-block/Cargo.toml
Normal file
17
plugins/wasm-rust/extensions/request-block/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "request-block"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
higress-wasm-rust = { path = "../../", version = "0.1.0" }
|
||||||
|
proxy-wasm = "0.2.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
regex = "1"
|
||||||
|
multimap = "0"
|
||||||
88
plugins/wasm-rust/extensions/request-block/README.md
Normal file
88
plugins/wasm-rust/extensions/request-block/README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# 功能说明
|
||||||
|
`request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露
|
||||||
|
|
||||||
|
# 配置字段
|
||||||
|
|
||||||
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|
| -------- | -------- | -------- | -------- | -------- |
|
||||||
|
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||||
|
| block_exact_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要精确屏蔽 URL 的字符串 |
|
||||||
|
| block_regexp_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的正则表达式 |
|
||||||
|
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||||
|
| block_bodies | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||||
|
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
|
||||||
|
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
|
||||||
|
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
|
||||||
|
|
||||||
|
# 配置示例
|
||||||
|
|
||||||
|
## 屏蔽请求 url 路径
|
||||||
|
```yaml
|
||||||
|
block_urls:
|
||||||
|
- swagger.html
|
||||||
|
- foo=bar
|
||||||
|
case_sensitive: false
|
||||||
|
```
|
||||||
|
|
||||||
|
根据该配置,下列请求将被禁止访问:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://example.com?foo=Bar
|
||||||
|
curl http://exmaple.com/Swagger.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## 屏蔽请求 header
|
||||||
|
```yaml
|
||||||
|
block_headers:
|
||||||
|
- example-key
|
||||||
|
- example-value
|
||||||
|
```
|
||||||
|
|
||||||
|
根据该配置,下列请求将被禁止访问:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://example.com -H 'example-key: 123'
|
||||||
|
curl http://exmaple.com -H 'my-header: example-value'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 屏蔽请求 body
|
||||||
|
```yaml
|
||||||
|
block_bodies:
|
||||||
|
- "hello world"
|
||||||
|
case_sensitive: false
|
||||||
|
```
|
||||||
|
|
||||||
|
根据该配置,下列请求将被禁止访问:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://example.com -d 'Hello World'
|
||||||
|
curl http://exmaple.com -d 'hello world'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 对特定路由或域名开启
|
||||||
|
```yaml
|
||||||
|
# 使用 _rules_ 字段进行细粒度规则配置
|
||||||
|
_rules_:
|
||||||
|
# 规则一:按路由名称匹配生效
|
||||||
|
- _match_route_:
|
||||||
|
- route-a
|
||||||
|
- route-b
|
||||||
|
block_bodies:
|
||||||
|
- "hello world"
|
||||||
|
# 规则二:按域名匹配生效
|
||||||
|
- _match_domain_:
|
||||||
|
- "*.example.com"
|
||||||
|
- test.com
|
||||||
|
block_urls:
|
||||||
|
- "swagger.html"
|
||||||
|
block_bodies:
|
||||||
|
- "hello world"
|
||||||
|
```
|
||||||
|
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||||
|
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||||
|
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||||
|
|
||||||
|
# 请求 Body 大小限制
|
||||||
|
|
||||||
|
当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||||
|
当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||||
248
plugins/wasm-rust/extensions/request-block/src/lib.rs
Normal file
248
plugins/wasm-rust/extensions/request-block/src/lib.rs
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
// Copyright (c) 2023 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use higress_wasm_rust::log::Log;
|
||||||
|
use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper};
|
||||||
|
use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher};
|
||||||
|
use multimap::MultiMap;
|
||||||
|
use proxy_wasm::traits::{Context, HttpContext, RootContext};
|
||||||
|
use proxy_wasm::types::{Action, Bytes, ContextType, LogLevel};
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Deserializer;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
proxy_wasm::main! {{
|
||||||
|
proxy_wasm::set_log_level(LogLevel::Trace);
|
||||||
|
proxy_wasm::set_root_context(|_|Box::new(RquestBlockRoot::new()));
|
||||||
|
}}
|
||||||
|
|
||||||
|
const PLUGIN_NAME: &str = "request-block";
|
||||||
|
|
||||||
|
struct RquestBlockRoot {
|
||||||
|
log: Log,
|
||||||
|
rule_matcher: SharedRuleMatcher<RquestBlockConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RquestBlock {
|
||||||
|
log: Log,
|
||||||
|
config: Option<RquestBlockConfig>,
|
||||||
|
cache_request: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_block_regexp_urls<'de, D>(deserializer: D) -> Result<Vec<Regex>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
let value: Value = Deserialize::deserialize(deserializer)?;
|
||||||
|
let block_regexp_urls = value
|
||||||
|
.as_array()
|
||||||
|
.ok_or(Error::custom("block_regexp_urls error not list"))?;
|
||||||
|
|
||||||
|
for block_regexp_url in block_regexp_urls {
|
||||||
|
let reg_exp = block_regexp_url
|
||||||
|
.as_str()
|
||||||
|
.ok_or(Error::custom("block_regexp_urls error not str"))?;
|
||||||
|
if let Ok(reg) = Regex::new(reg_exp) {
|
||||||
|
ret.push(reg);
|
||||||
|
} else {
|
||||||
|
return Err(Error::custom(format!(
|
||||||
|
"block_regexp_urls error field {}",
|
||||||
|
reg_exp
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
fn blocked_code_default() -> u32 {
|
||||||
|
403
|
||||||
|
}
|
||||||
|
fn case_sensitive_default() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
#[derive(Default, Debug, Deserialize, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct RquestBlockConfig {
|
||||||
|
#[serde(default = "blocked_code_default")]
|
||||||
|
blocked_code: u32,
|
||||||
|
blocked_message: String,
|
||||||
|
#[serde(default = "case_sensitive_default")]
|
||||||
|
case_sensitive: bool,
|
||||||
|
block_urls: Vec<String>,
|
||||||
|
block_exact_urls: Vec<String>,
|
||||||
|
block_headers: Vec<String>,
|
||||||
|
block_bodies: Vec<String>,
|
||||||
|
#[serde(deserialize_with = "deserialize_block_regexp_urls")]
|
||||||
|
block_regexp_urls: Vec<Regex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RquestBlockRoot {
|
||||||
|
fn new() -> Self {
|
||||||
|
RquestBlockRoot {
|
||||||
|
log: Log::new(PLUGIN_NAME.to_string()),
|
||||||
|
rule_matcher: Rc::new(RefCell::new(RuleMatcher::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context for RquestBlockRoot {}
|
||||||
|
|
||||||
|
impl RootContext for RquestBlockRoot {
|
||||||
|
fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool {
|
||||||
|
let ret = on_configure(
|
||||||
|
self,
|
||||||
|
_plugin_configuration_size,
|
||||||
|
self.rule_matcher.borrow_mut().deref_mut(),
|
||||||
|
&self.log,
|
||||||
|
);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
fn create_http_context(&self, _context_id: u32) -> Option<Box<dyn HttpContext>> {
|
||||||
|
self.create_http_context_use_wrapper(_context_id)
|
||||||
|
}
|
||||||
|
fn get_type(&self) -> Option<ContextType> {
|
||||||
|
Some(ContextType::HttpContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RootContextWrapper<RquestBlockConfig> for RquestBlockRoot {
|
||||||
|
fn rule_matcher(&self) -> &SharedRuleMatcher<RquestBlockConfig> {
|
||||||
|
&self.rule_matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_http_context_wrapper(
|
||||||
|
&self,
|
||||||
|
_context_id: u32,
|
||||||
|
) -> Option<Box<dyn HttpContextWrapper<RquestBlockConfig>>> {
|
||||||
|
Some(Box::new(RquestBlock {
|
||||||
|
cache_request: false,
|
||||||
|
config: None,
|
||||||
|
log: Log::new(PLUGIN_NAME.to_string()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context for RquestBlock {}
|
||||||
|
impl HttpContext for RquestBlock {}
|
||||||
|
impl HttpContextWrapper<RquestBlockConfig> for RquestBlock {
|
||||||
|
fn on_config(&mut self, _config: &RquestBlockConfig) {
|
||||||
|
self.config = Some(_config.clone());
|
||||||
|
self.cache_request = !_config.block_bodies.is_empty();
|
||||||
|
}
|
||||||
|
fn cache_request_body(&self) -> bool {
|
||||||
|
self.cache_request
|
||||||
|
}
|
||||||
|
fn on_http_request_headers_ok(&mut self, headers: &MultiMap<String, String>) -> Action {
|
||||||
|
if self.config.is_none() {
|
||||||
|
return Action::Continue;
|
||||||
|
}
|
||||||
|
let config = self.config.as_ref().unwrap();
|
||||||
|
if !config.block_urls.is_empty()
|
||||||
|
|| !config.block_exact_urls.is_empty()
|
||||||
|
|| !config.block_regexp_urls.is_empty()
|
||||||
|
{
|
||||||
|
let value = headers.get(":path");
|
||||||
|
|
||||||
|
if value.is_none() {
|
||||||
|
self.log.warn("get path failed");
|
||||||
|
return Action::Continue;
|
||||||
|
}
|
||||||
|
let mut request_url = value.unwrap().clone();
|
||||||
|
|
||||||
|
if config.case_sensitive {
|
||||||
|
request_url = request_url.to_lowercase();
|
||||||
|
}
|
||||||
|
for block_exact_url in &config.block_exact_urls {
|
||||||
|
if *block_exact_url == request_url {
|
||||||
|
self.send_http_response(
|
||||||
|
config.blocked_code,
|
||||||
|
Vec::new(),
|
||||||
|
Some(config.blocked_message.as_bytes()),
|
||||||
|
);
|
||||||
|
return Action::Pause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for block_url in &config.block_urls {
|
||||||
|
if request_url.contains(block_url) {
|
||||||
|
self.send_http_response(
|
||||||
|
config.blocked_code,
|
||||||
|
Vec::new(),
|
||||||
|
Some(config.blocked_message.as_bytes()),
|
||||||
|
);
|
||||||
|
return Action::Pause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for block_reg_exp in &config.block_regexp_urls {
|
||||||
|
if block_reg_exp.is_match(&request_url) {
|
||||||
|
self.send_http_response(
|
||||||
|
config.blocked_code,
|
||||||
|
Vec::new(),
|
||||||
|
Some(config.blocked_message.as_bytes()),
|
||||||
|
);
|
||||||
|
return Action::Pause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !config.block_headers.is_empty() {
|
||||||
|
let mut header_strs: Vec<String> = Vec::new();
|
||||||
|
for (k, v) in headers {
|
||||||
|
header_strs.push(k.clone());
|
||||||
|
header_strs.push(v.join("\n"));
|
||||||
|
}
|
||||||
|
let header_str = header_strs.join("\n");
|
||||||
|
for block_header in &config.block_headers {
|
||||||
|
if header_str.contains(block_header) {
|
||||||
|
self.send_http_response(
|
||||||
|
config.blocked_code,
|
||||||
|
Vec::new(),
|
||||||
|
Some(config.blocked_message.as_bytes()),
|
||||||
|
);
|
||||||
|
return Action::Pause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::Continue
|
||||||
|
}
|
||||||
|
fn on_http_request_body_ok(&mut self, req_body: &Bytes) -> Action {
|
||||||
|
if self.config.is_none() {
|
||||||
|
return Action::Continue;
|
||||||
|
}
|
||||||
|
let config = self.config.as_ref().unwrap();
|
||||||
|
if config.block_bodies.is_empty() {
|
||||||
|
return Action::Continue;
|
||||||
|
}
|
||||||
|
let mut body = req_body.clone();
|
||||||
|
if config.case_sensitive {
|
||||||
|
body = body.to_ascii_lowercase();
|
||||||
|
}
|
||||||
|
for block_body in &config.block_bodies {
|
||||||
|
let s = block_body.as_bytes();
|
||||||
|
if body.windows(s.len()).any(|window| window == s) {
|
||||||
|
self.send_http_response(
|
||||||
|
config.blocked_code,
|
||||||
|
Vec::new(),
|
||||||
|
Some(config.blocked_message.as_bytes()),
|
||||||
|
);
|
||||||
|
return Action::Pause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,4 +15,5 @@
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
mod internal;
|
mod internal;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
|
pub mod plugin_wrapper;
|
||||||
pub mod rule_matcher;
|
pub mod rule_matcher;
|
||||||
|
|||||||
164
plugins/wasm-rust/src/plugin_wrapper.rs
Normal file
164
plugins/wasm-rust/src/plugin_wrapper.rs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// Copyright (c) 2023 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::rule_matcher::SharedRuleMatcher;
|
||||||
|
use multimap::MultiMap;
|
||||||
|
use proxy_wasm::traits::{Context, HttpContext, RootContext};
|
||||||
|
use proxy_wasm::types::{Action, Bytes};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
pub trait RootContextWrapper<PluginConfig>: RootContext
|
||||||
|
where
|
||||||
|
PluginConfig: Default + DeserializeOwned + 'static + Clone,
|
||||||
|
{
|
||||||
|
// fn create_http_context(&self, _context_id: u32) -> Option<Box<dyn HttpContext>> {
|
||||||
|
fn create_http_context_use_wrapper(&self, _context_id: u32) -> Option<Box<dyn HttpContext>> {
|
||||||
|
// trait 继承没法重写 RootContext 的 create_http_context,先写个函数让上层调下吧
|
||||||
|
match self.create_http_context_wrapper(_context_id) {
|
||||||
|
Some(http_context) => Some(Box::new(PluginHttpWrapper::new(
|
||||||
|
self.rule_matcher(),
|
||||||
|
http_context,
|
||||||
|
))),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn rule_matcher(&self) -> &SharedRuleMatcher<PluginConfig>;
|
||||||
|
fn create_http_context_wrapper(
|
||||||
|
&self,
|
||||||
|
_context_id: u32,
|
||||||
|
) -> Option<Box<dyn HttpContextWrapper<PluginConfig>>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait HttpContextWrapper<PluginConfig>: HttpContext {
|
||||||
|
fn on_config(&mut self, _config: &PluginConfig) {}
|
||||||
|
fn on_http_request_headers_ok(&mut self, _headers: &MultiMap<String, String>) -> Action {
|
||||||
|
Action::Continue
|
||||||
|
}
|
||||||
|
fn cache_request_body(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn cache_response_body(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn on_http_request_body_ok(&mut self, _req_body: &Bytes) -> Action {
|
||||||
|
Action::Continue
|
||||||
|
}
|
||||||
|
fn on_http_response_body_ok(&mut self, _res_body: &Bytes) -> Action {
|
||||||
|
Action::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct PluginHttpWrapper<PluginConfig> {
|
||||||
|
req_headers: MultiMap<String, String>,
|
||||||
|
req_body: Bytes,
|
||||||
|
res_body: Bytes,
|
||||||
|
config: Option<PluginConfig>,
|
||||||
|
rule_matcher: SharedRuleMatcher<PluginConfig>,
|
||||||
|
http_content: Box<dyn HttpContextWrapper<PluginConfig>>,
|
||||||
|
}
|
||||||
|
impl<PluginConfig> PluginHttpWrapper<PluginConfig> {
|
||||||
|
pub fn new(
|
||||||
|
rule_matcher: &SharedRuleMatcher<PluginConfig>,
|
||||||
|
http_content: Box<dyn HttpContextWrapper<PluginConfig>>,
|
||||||
|
) -> Self {
|
||||||
|
PluginHttpWrapper {
|
||||||
|
req_headers: MultiMap::new(),
|
||||||
|
req_body: Bytes::new(),
|
||||||
|
res_body: Bytes::new(),
|
||||||
|
config: None,
|
||||||
|
rule_matcher: rule_matcher.clone(),
|
||||||
|
http_content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<PluginConfig> Context for PluginHttpWrapper<PluginConfig> {}
|
||||||
|
impl<PluginConfig> HttpContext for PluginHttpWrapper<PluginConfig>
|
||||||
|
where
|
||||||
|
PluginConfig: Default + DeserializeOwned + Clone,
|
||||||
|
{
|
||||||
|
fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
|
||||||
|
let binding = self.rule_matcher.borrow();
|
||||||
|
self.config = match binding.get_match_config() {
|
||||||
|
None => None,
|
||||||
|
Some(config) => Some(config.1.clone()),
|
||||||
|
};
|
||||||
|
for (k, v) in self.get_http_request_headers() {
|
||||||
|
self.req_headers.insert(k, v);
|
||||||
|
}
|
||||||
|
if let Some(config) = &self.config {
|
||||||
|
self.http_content.on_config(config);
|
||||||
|
}
|
||||||
|
let ret = self
|
||||||
|
.http_content
|
||||||
|
.on_http_request_headers(_num_headers, _end_of_stream);
|
||||||
|
if ret != Action::Continue {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
self.http_content
|
||||||
|
.on_http_request_headers_ok(&self.req_headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_http_request_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action {
|
||||||
|
let mut ret = self
|
||||||
|
.http_content
|
||||||
|
.on_http_request_body(_body_size, _end_of_stream);
|
||||||
|
if !self.http_content.cache_request_body() {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if _body_size > 0 {
|
||||||
|
if let Some(body) = self.get_http_request_body(0, _body_size) {
|
||||||
|
self.req_body.extend(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _end_of_stream && ret == Action::Continue {
|
||||||
|
ret = self.http_content.on_http_request_body_ok(&self.req_body);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_http_request_trailers(&mut self, _num_trailers: usize) -> Action {
|
||||||
|
self.http_content.on_http_request_trailers(_num_trailers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_http_response_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
|
||||||
|
self.http_content
|
||||||
|
.on_http_response_headers(_num_headers, _end_of_stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_http_response_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action {
|
||||||
|
let mut ret = self
|
||||||
|
.http_content
|
||||||
|
.on_http_response_body(_body_size, _end_of_stream);
|
||||||
|
if !self.http_content.cache_response_body() {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if _body_size > 0 {
|
||||||
|
if let Some(body) = self.get_http_response_body(0, _body_size) {
|
||||||
|
self.res_body.extend(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _end_of_stream && ret == Action::Continue {
|
||||||
|
ret = self.http_content.on_http_response_body_ok(&self.res_body);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_http_response_trailers(&mut self, _num_trailers: usize) -> Action {
|
||||||
|
self.http_content.on_http_response_trailers(_num_trailers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_log(&mut self) {
|
||||||
|
self.http_content.on_log()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ Higress 提供了运行 Ingress API 一致性测试和 wasmplugin 测试的 make
|
|||||||
+ 为测试构建所有 GO WasmPlugins: `make higress-wasmplugin-test`
|
+ 为测试构建所有 GO WasmPlugins: `make higress-wasmplugin-test`
|
||||||
+ 仅为一个 GO WasmPlugin 构建测试: `PLUGIN_NAME=request-block make higress-wasmplugin-test`
|
+ 仅为一个 GO WasmPlugin 构建测试: `PLUGIN_NAME=request-block make higress-wasmplugin-test`
|
||||||
+ 仅为一个 CPP WasmPlugin 构建测试: `PLUGIN_TYPE=CPP PLUGIN_NAME=key_auth make higress-wasmplugin-test`
|
+ 仅为一个 CPP WasmPlugin 构建测试: `PLUGIN_TYPE=CPP PLUGIN_NAME=key_auth make higress-wasmplugin-test`
|
||||||
|
+ 为测试构建所有 Rust WasmPlugins: `PLUGIN_TYPE=RUST make higress-wasmplugin-test`
|
||||||
|
+ 仅为一个 Rust WasmPlugin 构建测试: `PLUGIN_TYPE=RUST PLUGIN_NAME=request-block make higress-wasmplugin-test`
|
||||||
+ 仅运行指定测试,用逗号分隔 `TEST_SHORTNAME=WasmPluginsIPRestrictionAllow,WasmPluginsIPRestrictionDeny make higress-wasmplugin-test`
|
+ 仅运行指定测试,用逗号分隔 `TEST_SHORTNAME=WasmPluginsIPRestrictionAllow,WasmPluginsIPRestrictionDeny make higress-wasmplugin-test`
|
||||||
|
|
||||||
可以分为以下步骤:
|
可以分为以下步骤:
|
||||||
|
|||||||
166
test/e2e/conformance/tests/rust-wasm-request-block.go
Normal file
166
test/e2e/conformance/tests/rust-wasm-request-block.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
|
||||||
|
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(RustWasmPluginsRequestBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
var RustWasmPluginsRequestBlock = suite.ConformanceTest{
|
||||||
|
ShortName: "RustWasmPluginsRequestBlock",
|
||||||
|
Description: "The Ingress in the higress-conformance-infra namespace test the rust request-block wasmplugins.",
|
||||||
|
Manifests: []string{"tests/rust-wasm-request-block.yaml"},
|
||||||
|
Features: []suite.SupportedFeature{suite.WASMRustConformanceFeature},
|
||||||
|
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||||
|
testcases := []http.Assertion{
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/swagger.html",
|
||||||
|
UnfollowRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/env/info",
|
||||||
|
UnfollowRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/web/info",
|
||||||
|
UnfollowRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// post blocked body
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Method: "POST",
|
||||||
|
ContentType: http.ContentTypeTextPlain,
|
||||||
|
Body: []byte(`hello world`),
|
||||||
|
UnfollowRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// check body echoed back in expected request(same as ActualRequest if not set)
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
CompareTarget: http.CompareTargetRequest,
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Method: "POST",
|
||||||
|
ContentType: http.ContentTypeTextPlain,
|
||||||
|
Body: []byte(`hello higress`),
|
||||||
|
UnfollowRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// check body echoed back in expected response
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TargetBackend: "infra-backend-echo-body-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
CompareTarget: http.CompareTargetResponse,
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo2.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Method: "POST",
|
||||||
|
ContentType: http.ContentTypeTextPlain,
|
||||||
|
Body: []byte(`hello higress`),
|
||||||
|
UnfollowRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
ContentType: http.ContentTypeTextPlain,
|
||||||
|
Body: []byte(`hello higress`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Run("WasmPlugins request-block", func(t *testing.T) {
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
72
test/e2e/conformance/tests/rust-wasm-request-block.yaml
Normal file
72
test/e2e/conformance/tests/rust-wasm-request-block.yaml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/app-root: "/foo"
|
||||||
|
name: httproute-app-root
|
||||||
|
namespace: higress-conformance-infra
|
||||||
|
spec:
|
||||||
|
ingressClassName: higress
|
||||||
|
rules:
|
||||||
|
- host: "foo.com"
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- pathType: Prefix
|
||||||
|
path: "/"
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: infra-backend-v1
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/app-root: "/foo"
|
||||||
|
name: httproute-app-root2
|
||||||
|
namespace: higress-conformance-infra
|
||||||
|
spec:
|
||||||
|
ingressClassName: higress
|
||||||
|
rules:
|
||||||
|
- host: "foo2.com"
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- pathType: Prefix
|
||||||
|
path: "/"
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: infra-backend-echo-body-v1
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
---
|
||||||
|
apiVersion: extensions.higress.io/v1alpha1
|
||||||
|
kind: WasmPlugin
|
||||||
|
metadata:
|
||||||
|
name: request-block
|
||||||
|
namespace: higress-system
|
||||||
|
spec:
|
||||||
|
defaultConfig:
|
||||||
|
block_urls:
|
||||||
|
- "swagger.html"
|
||||||
|
block_regexp_urls:
|
||||||
|
- "/env.*"
|
||||||
|
block_exact_urls:
|
||||||
|
- "/web/info"
|
||||||
|
block_bodies:
|
||||||
|
- "hello world"
|
||||||
|
url: file:///opt/plugins/wasm-rust/extensions/request-block/plugin.wasm
|
||||||
@@ -25,6 +25,7 @@ const (
|
|||||||
// extended: extensibility
|
// extended: extensibility
|
||||||
WASMGoConformanceFeature SupportedFeature = "wasm-go"
|
WASMGoConformanceFeature SupportedFeature = "wasm-go"
|
||||||
WASMCPPConformanceFeature SupportedFeature = "wasm-cpp"
|
WASMCPPConformanceFeature SupportedFeature = "wasm-cpp"
|
||||||
|
WASMRustConformanceFeature SupportedFeature = "wasm-rust"
|
||||||
|
|
||||||
// extended: service discovery
|
// extended: service discovery
|
||||||
DubboConformanceFeature SupportedFeature = "dubbo"
|
DubboConformanceFeature SupportedFeature = "dubbo"
|
||||||
@@ -36,6 +37,13 @@ const (
|
|||||||
EnvoyConfigConformanceFeature SupportedFeature = "envoy-config"
|
EnvoyConfigConformanceFeature SupportedFeature = "envoy-config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var WasmPluginTypeMap = map[string]SupportedFeature{
|
||||||
|
"": WASMGoConformanceFeature, // default
|
||||||
|
"GO": WASMGoConformanceFeature,
|
||||||
|
"CPP": WASMCPPConformanceFeature,
|
||||||
|
"RUST": WASMRustConformanceFeature,
|
||||||
|
}
|
||||||
|
|
||||||
var AllFeatures = sets.Set{}.
|
var AllFeatures = sets.Set{}.
|
||||||
Insert(string(HTTPConformanceFeature)).
|
Insert(string(HTTPConformanceFeature)).
|
||||||
Insert(string(DubboConformanceFeature)).
|
Insert(string(DubboConformanceFeature)).
|
||||||
@@ -46,4 +54,5 @@ var AllFeatures = sets.Set{}.
|
|||||||
|
|
||||||
var ExperimentFeatures = sets.Set{}.
|
var ExperimentFeatures = sets.Set{}.
|
||||||
Insert(string(WASMGoConformanceFeature)).
|
Insert(string(WASMGoConformanceFeature)).
|
||||||
Insert(string(WASMCPPConformanceFeature))
|
Insert(string(WASMCPPConformanceFeature)).
|
||||||
|
Insert(string(WASMRustConformanceFeature))
|
||||||
|
|||||||
@@ -95,10 +95,11 @@ func New(s Options) *ConformanceTestSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.IsWasmPluginTest {
|
if s.IsWasmPluginTest {
|
||||||
if s.WasmPluginType == "CPP" {
|
feature, ok := WasmPluginTypeMap[s.WasmPluginType]
|
||||||
s.SupportedFeatures.Insert(string(WASMCPPConformanceFeature))
|
if ok {
|
||||||
|
s.SupportedFeatures.Insert(string(feature))
|
||||||
} else {
|
} else {
|
||||||
s.SupportedFeatures.Insert(string(WASMGoConformanceFeature))
|
panic("WasmPluginType [" + s.WasmPluginType + "] not support")
|
||||||
}
|
}
|
||||||
} else if s.IsEnvoyConfigTest {
|
} else if s.IsEnvoyConfigTest {
|
||||||
s.SupportedFeatures.Insert(string(EnvoyConfigConformanceFeature))
|
s.SupportedFeatures.Insert(string(EnvoyConfigConformanceFeature))
|
||||||
|
|||||||
@@ -29,6 +29,24 @@ then
|
|||||||
echo "🚀 Build CPP WasmPlugin: $INNER_PLUGIN_NAME"
|
echo "🚀 Build CPP WasmPlugin: $INNER_PLUGIN_NAME"
|
||||||
PLUGIN_NAME=${INNER_PLUGIN_NAME} make build
|
PLUGIN_NAME=${INNER_PLUGIN_NAME} make build
|
||||||
fi
|
fi
|
||||||
|
elif [ "$TYPE" == "RUST" ]
|
||||||
|
then
|
||||||
|
cd ./plugins/wasm-rust/
|
||||||
|
if [ ! -n "$INNER_PLUGIN_NAME" ]; then
|
||||||
|
EXTENSIONS_DIR=$(pwd)"/extensions/"
|
||||||
|
echo "🚀 Build all Rust WasmPlugins under folder of $EXTENSIONS_DIR"
|
||||||
|
for file in `ls $EXTENSIONS_DIR`
|
||||||
|
do
|
||||||
|
if [ -d $EXTENSIONS_DIR$file ]; then
|
||||||
|
name=${file##*/}
|
||||||
|
echo "🚀 Build Rust WasmPlugin: $name"
|
||||||
|
PLUGIN_NAME=${name} BUILDER_REGISTRY="docker.io/alihigress/plugins-rust-" make build
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "🚀 Build Rust WasmPlugin: $INNER_PLUGIN_NAME"
|
||||||
|
PLUGIN_NAME=${INNER_PLUGIN_NAME} make build
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "Not specify plugin language, so just compile wasm-go as default"
|
echo "Not specify plugin language, so just compile wasm-go as default"
|
||||||
cd ./plugins/wasm-go/
|
cd ./plugins/wasm-go/
|
||||||
|
|||||||
Reference in New Issue
Block a user