WEBKT

Envoy 实战:用 RE2:Set 打造高性能 WAF 过滤器

2 0 0 0

为什么选择 Envoy 和 RE2:Set?

Envoy:云原生时代的流量管理大师

RE2:Set:高效的正则表达式引擎

核心思路:Envoy Filter 链 + RE2:Set

实战步骤:手把手教你搭建 WAF

1. 准备工作

2. 编写 WAF 规则

3.2 注册 Filter

3.3 编译 Filter

4. 配置 Envoy

5. 测试

总结

各位老铁,大家好!我是你们的赛博朋克老司机,极客君。

今天咱们来聊点硬核的,聊聊怎么用 Envoy 打造一个性能炸裂的 WAF(Web Application Firewall)。相信不少做过网站或者搞过服务器的兄弟都对 WAF 不陌生,这玩意儿能帮你挡住各种奇奇怪怪的攻击,保护你的应用安全。

但传统的 WAF,要么规则配置麻烦,要么性能堪忧。今天,我就来给大家分享一个基于 Envoy 和 RE2:Set 的高性能 WAF 解决方案,让你鱼和熊掌兼得!

为什么选择 Envoy 和 RE2:Set?

在深入讲解之前,咱们先来聊聊为什么选择 Envoy 和 RE2:Set 这对组合。

Envoy:云原生时代的流量管理大师

Envoy,这名字听起来就透着一股子“特使”范儿。没错,它就是云原生时代的流量管理特使,由 Lyft 开源,现在是 CNCF(云原生计算基金会)的毕业项目。Envoy 的优点简直不要太多:

  • 高性能: 基于 C++11/14 开发,性能杠杠的。
  • 可扩展性: 模块化设计,你可以像搭积木一样,通过过滤器(Filter)链机制扩展 Envoy 的功能。
  • 动态配置: 支持 xDS API,可以动态更新配置,无需重启 Envoy。
  • 可观测性: 提供了丰富的 metrics、tracing 和 logging,方便你监控和调试。

总之,Envoy 就是一个为云原生而生的流量管理利器,用来做 WAF 再合适不过了。

RE2:Set:高效的正则表达式引擎

RE2 是 Google 出品的一个正则表达式引擎,它的特点是:

  • 安全: 不会因为恶意的正则表达式而导致 ReDoS(正则表达式拒绝服务)攻击。
  • 高效: 使用有限状态自动机(DFA)实现,匹配速度快,且不受正则表达式复杂度的影响。
  • RE2:Set 支持: 允许你将多个正则表达式编译成一个集合,一次匹配多个规则,进一步提高效率。

对于 WAF 来说,需要处理大量的规则,RE2:Set 的高效性和安全性就显得尤为重要。

核心思路:Envoy Filter 链 + RE2:Set

我们的核心思路很简单,就是利用 Envoy 的 Filter 链机制,将 RE2:Set 编译的 WAF 规则集成到 Envoy 的请求处理流程中。具体来说,我们会创建一个自定义的 Envoy Filter,这个 Filter 会:

  1. 加载 WAF 规则: 从配置文件或者 xDS API 中加载 WAF 规则(这些规则是基于 RE2:Set 的)。
  2. 编译规则: 将加载的规则编译成 RE2:Set 对象。
  3. 匹配请求: 对每个进入 Envoy 的请求,提取关键信息(如 URL、Header、Body 等),使用 RE2:Set 进行匹配。
  4. 执行动作: 如果匹配到规则,则执行相应的动作(如拒绝请求、记录日志、重定向等)。

整个流程如下图所示:

[Client Request] --> [Envoy Listener] --> [HTTP Filter Chain] --> [Custom WAF Filter (RE2:Set)] --> [Router Filter] --> [Upstream Server]
| ^
| |
| (Match Rules & Take Actions) |
| |
V |
[Reject/Log/Redirect...] <------------------

实战步骤:手把手教你搭建 WAF

理论说了这么多,接下来咱们就真刀真枪地干起来!

1. 准备工作

  • 安装 Envoy: 可以参考 Envoy 官方文档进行安装。
  • 安装 RE2: 可以通过包管理器(如 apt、yum、brew 等)安装,也可以从源码编译安装。
  • 安装 Bazel: 如果你要从源码编译 Envoy Filter,需要安装 Bazel 构建工具。

2. 编写 WAF 规则

WAF 规则通常是一些正则表达式,用于匹配恶意请求的特征。例如,下面是一些常见的 WAF 规则:

# 防止 SQL 注入
.*(?:;|\/\*|\*\/|\-\-).*\b(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|UNION|EXEC|DECLARE)\b.*
# 防止 XSS 攻击
.*<script.*?>.*</script>.*
.*<.*on(?:load|click|mouseover|...).*?>.*
# 防止路径遍历
.*\.\.\/.*
# 防止命令注入
.*;\s*(?:cat|ls|whoami|id|pwd).*\n```
你可以根据自己的需求编写 WAF 规则,并将它们保存到一个文件中(例如 `waf_rules.txt`)。
### 3. 创建自定义 Envoy Filter
接下来,我们需要创建一个自定义的 Envoy Filter,用于加载和匹配 WAF 规则。这里我们使用 C++ 来编写 Filter。
#### 3.1 创建 Filter 代码
```c++
// waf_filter.h
#pragma once
#include "envoy/http/filter.h"
#include "envoy/registry/registry.h"
#include "re2/re2.h"
#include "re2/set.h"
namespace Envoy {
namespace Http {
class WafFilter : public Filter {
public:
WafFilter(const std::string& rules_file);
~WafFilter() override;
FilterHeadersStatus decodeHeaders(RequestHeaderMap& headers, bool end_stream) override;
FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override;
FilterTrailersStatus decodeTrailers(RequestTrailerMap& trailers) override;
private:
re2::RE2::Set rule_set_;
};
} // namespace Http
} // namespace Envoy
// waf_filter.cc
#include "waf_filter.h"
#include <fstream>
#include <iostream>
namespace Envoy {
namespace Http {
WafFilter::WafFilter(const std::string& rules_file) : rule_set_(re2::RE2::Options(), re2::RE2::Anchor::UNANCHORED) {
std::ifstream file(rules_file);
std::string line;
if (file.is_open()) {
while (std::getline(file, line)) {
if (!line.empty() && line[0] != '#') { // 忽略注释
rule_set_.Add(line, nullptr);
}
}
file.close();
if (int error_code = rule_set_.Compile()) {
//处理错误
std::cerr <<"rule_set_.Compile() failed with: " << error_code << "\n";
throw std::runtime_error("Failed to compile WAF rules");
}
} else {
//处理错误
throw std::runtime_error("Failed to open WAF rules file");
}
}
WafFilter::~WafFilter() {}
FilterHeadersStatus WafFilter::decodeHeaders(RequestHeaderMap& headers, bool end_stream) {
// 匹配请求头
for (const auto& header : headers) {
if (rule_set_.Match(header.key()->value().getStringView(), nullptr)) {
// 执行拦截
headers.setStatus(403);
return FilterHeadersStatus::StopIteration;
}
if (rule_set_.Match(header.value()->value().getStringView(), nullptr)) {
// 执行拦截
headers.setStatus(403);
return FilterHeadersStatus::StopIteration;
}
}
return FilterHeadersStatus::Continue;
}
FilterDataStatus WafFilter::decodeData(Buffer::Instance& data, bool end_stream) {
// 匹配请求体(可选)
if (rule_set_.Match(data.toString(), nullptr)){
//这里应该返回 FilterDataStatus::StopIterationAndBuffer 或者 FilterDataStatus::StopIterationNoBuffer
// 并且在 decodeHeaders 里面设置 403 状态
}
return FilterDataStatus::Continue;
}
FilterTrailersStatus WafFilter::decodeTrailers(RequestTrailerMap& trailers) {
// 匹配请求尾部(可选)
return FilterTrailersStatus::Continue;
}
} // namespace Http
} // namespace Envoy

3.2 注册 Filter

// waf_filter_config.cc
#include "envoy/config/filter/http/http_filter_config.h"
#include "envoy/registry/registry.h"
#include "waf_filter.h"
namespace Envoy {
namespace Server {
namespace Configuration {
class WafFilterConfig : public NamedHttpFilterConfigFactory {
public:
HttpFilterFactoryCb createFilterFactoryFromProto(
const Protobuf::Message& config, const std::string&, FactoryContext& context) override {
// 从配置中获取 WAF 规则文件路径 (示例)
auto& cfg = MessageUtil::downcastAndValidate<const mywaf::WafConfig&>(config, context.messageValidationVisitor());
const std::string rules_file = cfg.rules_file();
return [rules_file](HttpFilterManager& filter_manager) -> void {
filter_manager.addFilter(std::make_shared<Http::WafFilter>(rules_file));
};
}
ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return ProtobufTypes::MessagePtr{new mywaf::WafConfig()};
}
std::string name() const override { return "my.http.waf"; }
};
static Registry::RegisterFactory<WafFilterConfig, NamedHttpFilterConfigFactory> register_;
} // namespace Configuration
} // namespace Server
} // namespace Envoy
// waf.proto

syntax = "proto3";

package mywaf;

message WafConfig {
  string rules_file = 1;
}

3.3 编译 Filter

使用 Bazel 编译 Filter:

bazel build //path/to/your/filter:waf_filter.so

4. 配置 Envoy

最后,我们需要配置 Envoy,加载我们的自定义 Filter,并指定 WAF 规则文件。

static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: some_upstream_cluster
http_filters:
- name: my.http.waf # 我们的自定义 Filter
typed_config:
"@type": type.googleapis.com/mywaf.WafConfig
rules_file: /path/to/your/waf_rules.txt # WAF 规则文件路径
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: some_upstream_cluster
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: some_upstream_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8081

waf_filter.so 文件放到Envoy可以加载的路径.

5. 测试

启动 Envoy,然后发送一些恶意请求进行测试。如果一切正常,Envoy 应该会拦截这些请求,并返回 403 状态码。

总结

通过 Envoy 的 Filter 链机制和 RE2:Set,我们可以轻松打造一个高性能、可扩展的 WAF。这只是一个简单的示例,你可以根据自己的需求进行更复杂的定制,例如:

  • 支持更多的匹配条件: 除了 URL、Header、Body,还可以匹配 Cookie、IP 地址等。
  • 更灵活的动作: 除了拒绝请求,还可以重定向、添加 Header、修改 Body 等。
  • 集成 xDS API: 从 xDS API 动态获取 WAF 规则,实现动态更新。
  • 更完善的日志记录: 记录详细的 WAF 日志,方便分析和排查问题。

希望这篇文章能帮助你更好地理解 Envoy 和 WAF,如果你有任何问题或者建议,欢迎留言讨论!

好了,今天的分享就到这里。我是极客君,咱们下期再见!

极客君 EnvoyWAFRE2

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/8204