diff --git a/plugins/wasm-cpp/extensions/model_mapper/README.md b/plugins/wasm-cpp/extensions/model_mapper/README.md index 1e9fc747f..256b68ac2 100644 --- a/plugins/wasm-cpp/extensions/model_mapper/README.md +++ b/plugins/wasm-cpp/extensions/model_mapper/README.md @@ -1,17 +1,15 @@ -## 功能说明 +# 功能说明 `model-mapper`插件实现了基于LLM协议中的model参数路由的功能 -## 配置字段 +# 配置字段 | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | | ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- | | `modelKey` | string | 选填 | model | 请求body中model参数的位置 | | `modelMapping` | map of string | 选填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。
1. 支持前缀匹配。例如用 "gpt-3-*" 匹配所有名称以“gpt-3-”开头的模型;
2. 支持使用 "*" 为键来配置通用兜底映射关系;
3. 如果映射的目标名称为空字符串 "",则表示保留原模型名称。 | -| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 ## 运行属性 +| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 | + -插件执行阶段:认证阶段 -插件执行优先级:800 - | ## 效果说明 如下配置 diff --git a/plugins/wasm-cpp/extensions/model_router/README.md b/plugins/wasm-cpp/extensions/model_router/README.md index e78988e0e..7d7d30805 100644 --- a/plugins/wasm-cpp/extensions/model_router/README.md +++ b/plugins/wasm-cpp/extensions/model_router/README.md @@ -1,7 +1,7 @@ -## 功能说明 +# 功能说明 `model-router`插件实现了基于LLM协议中的model参数路由的功能 -## 配置字段 +# 配置字段 | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | | ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- | diff --git a/plugins/wasm-cpp/extensions/request_block/BUILD b/plugins/wasm-cpp/extensions/request_block/BUILD index f1eaa40d0..b8857774e 100644 --- a/plugins/wasm-cpp/extensions/request_block/BUILD +++ b/plugins/wasm-cpp/extensions/request_block/BUILD @@ -26,6 +26,7 @@ proxy_wasm_cc_binary( "@com_google_absl//absl/time", "//common:json_util", "//common:http_util", + "//common:regex_util", "//common:rule_util", ], ) @@ -44,6 +45,7 @@ cc_library( "//common:json_util", "@proxy_wasm_cpp_host//:lib", "//common:http_util_nullvm", + "//common:regex_util", "//common:rule_util_nullvm", ], ) diff --git a/plugins/wasm-cpp/extensions/request_block/README.md b/plugins/wasm-cpp/extensions/request_block/README.md index 95c3cdecb..7cd762a3c 100644 --- a/plugins/wasm-cpp/extensions/request_block/README.md +++ b/plugins/wasm-cpp/extensions/request_block/README.md @@ -1,31 +1,22 @@ ---- -title: 请求屏蔽 -keywords: [higress,request block] -description: 请求屏蔽插件配置参考 ---- - -## 功能说明 +# 功能说明 `request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露 -## 运行属性 +# 配置字段 -插件执行阶段:`鉴权阶段` -插件执行优先级:`320` +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +| -------- | -------- | -------- | -------- | -------- | +| block_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 | +| block_exact_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要精确屏蔽 URL 的字符串 | +| block_regexp_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的正则表达式 | +| block_headers | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 | +| block_bodies | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 | +| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 | +| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body | +| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 | -## 配置字段 +# 配置示例 -| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | -| -------- | -------- | -------- | -------- | -------- | -| block_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 路径 +## 屏蔽请求 url 路径 ```yaml block_urls: - swagger.html @@ -40,7 +31,36 @@ curl http://example.com?foo=Bar curl http://exmaple.com/Swagger.html ``` -### 屏蔽请求 header +## 屏蔽精确匹配的请求 url 路径 + +```yaml +block_exact_urls: +- /swagger.html?foo=bar +case_sensitive: false +``` + +根据该配置,下列请求将被禁止访问: + +```bash +curl http://exmaple.com/Swagger.html?foo=Bar +``` + +## 屏蔽正则匹配的请求 url 路径 + +```yaml +block_exact_urls: +- .*swagger.* +case_sensitive: false +``` + +根据该配置,下列请求将被禁止访问: + +```bash +curl http://exmaple.com/Swagger.html?foo=Bar +``` + + +## 屏蔽请求 header ```yaml block_headers: - example-key @@ -54,9 +74,9 @@ curl http://example.com -H 'example-key: 123' curl http://exmaple.com -H 'my-header: example-value' ``` -### 屏蔽请求 body +## 屏蔽请求 body ```yaml -block_bodies: +block_bodys: - "hello world" case_sensitive: false ``` @@ -68,8 +88,30 @@ curl http://example.com -d 'Hello World' curl http://exmaple.com -d 'hello world' ``` +## 对特定路由或域名开启 +```yaml +# 使用 _rules_ 字段进行细粒度规则配置 +_rules_: +# 规则一:按路由名称匹配生效 +- _match_route_: + - route-a + - route-b + block_bodys: + - "hello world" +# 规则二:按域名匹配生效 +- _match_domain_: + - "*.example.com" + - test.com + block_urls: + - "swagger.html" + block_bodys: + - "hello world" +``` +此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置; +此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置; +配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。 -## 请求 Body 大小限制 +# 请求 Body 大小限制 -当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作 -当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large` +当配置了 `block_bodys` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作 +当配置了 `block_bodys` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`, 请在参数配置页调高此项。注意调高此参数配置后,网关内存使用将有显著增加。 diff --git a/plugins/wasm-cpp/extensions/request_block/plugin.cc b/plugins/wasm-cpp/extensions/request_block/plugin.cc index 90c6c92f7..ce6492379 100644 --- a/plugins/wasm-cpp/extensions/request_block/plugin.cc +++ b/plugins/wasm-cpp/extensions/request_block/plugin.cc @@ -15,6 +15,7 @@ #include "extensions/request_block/plugin.h" #include +#include #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" @@ -89,6 +90,48 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, LOG_WARN("failed to parse configuration for block_urls."); return false; } + if (!JsonArrayIterate( + configuration, "block_exact_urls", [&](const json& item) -> bool { + auto url = JsonValueAs(item); + if (url.second != Wasm::Common::JsonParserResultDetail::OK) { + LOG_WARN("cannot parse block_exact_urls"); + return false; + } + if (rule.case_sensitive) { + rule.block_exact_urls.push_back(std::move(url.first.value())); + } else { + rule.block_exact_urls.push_back( + absl::AsciiStrToLower(url.first.value())); + } + return true; + })) { + LOG_WARN("failed to parse configuration for block_exact_urls."); + return false; + } + if (!JsonArrayIterate( + configuration, "block_regexp_urls", [&](const json& item) -> bool { + auto url = JsonValueAs(item); + if (url.second != Wasm::Common::JsonParserResultDetail::OK) { + LOG_WARN("cannot parse block_regexp_urls"); + return false; + } + std::string regex; + if (rule.case_sensitive) { + regex = url.first.value(); + } else { + regex = absl::AsciiStrToLower(url.first.value()); + } + auto re = std::make_unique(regex); + if (!re->error().empty()) { + LOG_WARN(re->error()); + return false; + } + rule.block_regexp_urls.push_back(std::move(re)); + return true; + })) { + LOG_WARN("failed to parse configuration for block_regexp_urls."); + return false; + } if (!JsonArrayIterate( configuration, "block_headers", [&](const json& item) -> bool { auto header = JsonValueAs(item); @@ -125,8 +168,28 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, LOG_WARN("failed to parse configuration for block_bodys."); return false; } + // compatiable + if (!JsonArrayIterate( + configuration, "block_bodies", [&](const json& item) -> bool { + auto body = JsonValueAs(item); + if (body.second != Wasm::Common::JsonParserResultDetail::OK) { + LOG_WARN("cannot parse block_bodies"); + return false; + } + if (rule.case_sensitive) { + rule.block_bodys.push_back(std::move(body.first.value())); + } else { + rule.block_bodys.push_back( + absl::AsciiStrToLower(body.first.value())); + } + return true; + })) { + LOG_WARN("failed to parse configuration for block_bodies."); + return false; + } if (rule.block_bodys.empty() && rule.block_headers.empty() && - rule.block_urls.empty()) { + rule.block_urls.empty() && rule.block_exact_urls.empty() && + rule.block_regexp_urls.empty()) { LOG_WARN("there is no block rules"); return false; } @@ -172,6 +235,18 @@ bool PluginRootContext::checkHeader(const RequestBlockConfigRule& rule, urlstr = absl::AsciiStrToLower(request_url); url = urlstr; } + for (const auto& block_url : rule.block_exact_urls) { + if (url == block_url) { + sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {}); + return false; + } + } + for (const auto& block_url : rule.block_regexp_urls) { + if (block_url->match(url)) { + sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {}); + return false; + } + } for (const auto& block_url : rule.block_urls) { if (absl::StrContains(url, block_url)) { sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {}); diff --git a/plugins/wasm-cpp/extensions/request_block/plugin.h b/plugins/wasm-cpp/extensions/request_block/plugin.h index 4c9288f2a..e9c6ce393 100644 --- a/plugins/wasm-cpp/extensions/request_block/plugin.h +++ b/plugins/wasm-cpp/extensions/request_block/plugin.h @@ -22,6 +22,7 @@ #include #include "common/http_util.h" +#include "common/regex.h" #include "common/route_rule_matcher.h" #define ASSERT(_X) assert(_X) @@ -39,11 +40,16 @@ namespace request_block { #endif +using ReMatcher = Wasm::Common::Regex::CompiledGoogleReMatcher; +using ReMatcherPtr = std::unique_ptr; + struct RequestBlockConfigRule { int blocked_code = 403; std::string blocked_message; bool case_sensitive = true; std::vector block_urls; + std::vector block_exact_urls; + std::vector block_regexp_urls; std::vector block_headers; std::vector block_bodys; }; diff --git a/plugins/wasm-cpp/extensions/request_block/plugin_test.cc b/plugins/wasm-cpp/extensions/request_block/plugin_test.cc index 40cc32711..15b7d9475 100644 --- a/plugins/wasm-cpp/extensions/request_block/plugin_test.cc +++ b/plugins/wasm-cpp/extensions/request_block/plugin_test.cc @@ -127,6 +127,8 @@ TEST_F(RequestBlockTest, CaseSensitive) { std::string configuration = R"( { "block_urls": ["?foo=bar", "swagger.html"], + "block_exact_urls": ["/hello.html?abc=123"], + "block_regexp_urls": [".*monkey.*"], "block_headers": ["headerKey", "headerValue"], "block_bodys": ["Hello World"] })"; @@ -150,6 +152,22 @@ TEST_F(RequestBlockTest, CaseSensitive) { EXPECT_EQ(context_->onRequestHeaders(0, false), FilterHeadersStatus::StopIteration); + path_ = "/hello.html?abc=123"; + EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_, + testing::_, testing::_)); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + + path_ = "/black/Monkey"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::Continue); + + path_ = "/black/monkey"; + EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_, + testing::_, testing::_)); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + path_ = ""; headers_ = {{"headerKey", "123"}}; EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_, @@ -188,6 +206,8 @@ TEST_F(RequestBlockTest, CaseInsensitive) { "blocked_code": 404, "block_urls": ["?foo=bar", "swagger.html"], "block_headers": ["headerKey", "headerValue"], + "block_exact_urls": ["/hello.html?abc=123"], + "block_regexp_urls": [".*monkey.*"], "block_bodys": ["Hello World"] })"; @@ -206,6 +226,24 @@ TEST_F(RequestBlockTest, CaseInsensitive) { EXPECT_EQ(context_->onRequestHeaders(0, false), FilterHeadersStatus::StopIteration); + path_ = "/Hello.html?abc=123"; + EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_, + testing::_, testing::_)); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + + path_ = "/black/Monkey"; + EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_, + testing::_, testing::_)); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + + path_ = "/black/monkey"; + EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_, + testing::_, testing::_)); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + path_ = ""; headers_ = {{"headerkey", "123"}}; EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_, @@ -232,6 +270,26 @@ TEST_F(RequestBlockTest, CaseInsensitive) { FilterDataStatus::StopIterationNoBuffer); } +TEST_F(RequestBlockTest, Bodies) { + std::string configuration = R"( +{ + "case_sensitive": false, + "blocked_code": 404, + "block_bodies": ["Hello World"] +})"; + + config_.set({configuration.data(), configuration.size()}); + EXPECT_TRUE(root_context_->configure(configuration.size())); + + body_.set("hello world"); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::Continue); + EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_, + testing::_, testing::_)); + EXPECT_EQ(context_->onRequestBody(11, true), + FilterDataStatus::StopIterationNoBuffer); +} + } // namespace request_block } // namespace null_plugin } // namespace proxy_wasm