Skip to content

Commit

Permalink
feat: ext-auth plugin: Blacklist and whitelist modes support HTTP req…
Browse files Browse the repository at this point in the history
…uest method matching (#1798)
  • Loading branch information
hanxiantao authored Feb 26, 2025
1 parent 2d8a8f2 commit 90ca903
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 132 deletions.
47 changes: 31 additions & 16 deletions plugins/wasm-go/extensions/ext-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ MatchRule 类型每一项的配置字段说明,在使用 `array of MatchRule`
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
| ------------------- | -------- | ---- | ------ | ------------------------------------------------------------ |
| `match_rule_domain` | string || - | 匹配规则域名,支持通配符模式,例如 `*.bar.com` |
| `match_rule_method` | []string || - | 匹配请求方法 |
| `match_rule_path` | string || - | 匹配请求路径的规则 |
| `match_rule_type` | string || - | 匹配请求路径的规则类型,可选 `exact` , `prefix` , `suffix`, `contains`, `regex` |

Expand All @@ -100,27 +101,41 @@ MatchRule 类型每一项的配置字段说明,在使用 `array of MatchRule`
**白名单模式**

```yaml
# 白名单模式配置,符合白名单规则的请求无需验证
match_type: 'whitelist'
match_list:
- match_rule_domain: '*.bar.com'
match_rule_path: '/foo'
match_rule_type: 'prefix'
# 所有以 api.example.com 为域名,且路径前缀为 /public 的请求无需验证
- match_rule_domain: 'api.example.com'
match_rule_path: '/public'
match_rule_type: 'prefix'
# 针对图片资源服务器 images.example.com,所有 GET 请求无需验证
- match_rule_domain: 'images.example.com'
match_rule_method: ["GET"]
# 所有域名下,路径精确匹配 /health-check 的 HEAD 请求无需验证
- match_rule_method: ["HEAD"]
match_rule_path: '/health-check'
match_rule_type: 'exact'
```
泛域名 `*.bar.com` 下前缀匹配 `/foo` 的请求无需验证

**黑名单模式**
```yaml
# 黑名单模式配置,符合黑名单规则的请求需要验证
match_type: 'blacklist'
match_list:
- match_rule_domain: '*.bar.com'
match_rule_path: '/headers'
match_rule_type: 'prefix'
# 所有以 admin.example.com 为域名,且路径前缀为 /sensitive 的请求需要验证
- match_rule_domain: 'admin.example.com'
match_rule_path: '/sensitive'
match_rule_type: 'prefix'
# 所有域名下,路径精确匹配 /user 的 DELETE 请求需要验证
- match_rule_method: ["DELETE"]
match_rule_path: '/user'
match_rule_type: 'exact'
# 所有以 legacy.example.com 为域名的 POST 请求需要验证
- match_rule_domain: 'legacy.example.com'
match_rule_method: ["POST"]
```
只有泛域名 `*.bar.com` 下前缀匹配 `/header` 的请求需要验证

## 配置示例
下面假设 `ext-auth` 服务在 Kubernetes 中 serviceName 为 `ext-auth`,端口 `8090`,路径为 `/auth`,命名空间为 `backend`
Expand Down Expand Up @@ -185,13 +200,13 @@ content-length: 0
http_service:
authorization_request:
allowed_headers:
- exact: x-auth-version
- exact: x-auth-version
headers_to_add:
x-envoy-header: true
authorization_response:
allowed_upstream_headers:
- exact: x-user-id
- exact: x-auth-version
- exact: x-user-id
- exact: x-auth-version
endpoint_mode: envoy
endpoint:
service_name: ext-auth.backend.svc.cluster.local
Expand Down Expand Up @@ -287,13 +302,13 @@ content-length: 0
http_service:
authorization_request:
allowed_headers:
- exact: x-auth-version
- exact: x-auth-version
headers_to_add:
x-envoy-header: true
authorization_response:
allowed_upstream_headers:
- exact: x-user-id
- exact: x-auth-version
- exact: x-user-id
- exact: x-auth-version
endpoint_mode: forward_auth
endpoint:
service_name: ext-auth.backend.svc.cluster.local
Expand Down
47 changes: 31 additions & 16 deletions plugins/wasm-go/extensions/ext-auth/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Configuration fields for each item of `MatchRule` type. When using `array of Mat
| Name | Data Type | Required | Default Value | Description |
| --- | --- | --- | --- | --- |
| `match_rule_domain` | string | No | - | The domain of the matching rule, supports wildcard patterns, e.g., `*.bar.com` |
| `match_rule_method` | []string | No | - | Matching rule for the request method |
| `match_rule_path` | string | No | - | The rule for matching the request path |
| `match_rule_type` | string | No | - | The type of the rule for matching the request path, can be `exact`, `prefix`, `suffix`, `contains`, `regex` |

Expand All @@ -100,27 +101,41 @@ Supports blacklist and whitelist mode configuration. The default is the whitelis
**Whitelist Mode**

```yaml
# Configuration for the whitelist mode. Requests that match the whitelist rules do not need verification.
match_type: 'whitelist'
match_list:
- match_rule_domain: '*.bar.com'
match_rule_path: '/foo'
match_rule_type: 'prefix'
# Requests with the domain name api.example.com and a path prefixed with /public do not need verification.
- match_rule_domain: 'api.example.com'
match_rule_path: '/public'
match_rule_type: 'prefix'
# For the image resource server images.example.com, all GET requests do not need verification.
- match_rule_domain: 'images.example.com'
match_rule_method: ["GET"]
# For all domains, HEAD requests with an exact path match of /health-check do not need verification.
- match_rule_method: ["HEAD"]
match_rule_path: '/health-check'
match_rule_type: 'exact'
```
Requests with a prefix match of `/foo` under the wildcard domain `*.bar.com` do not need to be verified.

**Blacklist Mode**
```yaml
# Configuration for the blacklist mode. Requests that match the blacklist rules need verification.
match_type: 'blacklist'
match_list:
- match_rule_domain: '*.bar.com'
match_rule_path: '/headers'
match_rule_type: 'prefix'
# Requests with the domain name admin.example.com and a path prefixed with /sensitive need verification.
- match_rule_domain: 'admin.example.com'
match_rule_path: '/sensitive'
match_rule_type: 'prefix'
# For all domains, DELETE requests with an exact path match of /user need verification.
- match_rule_method: ["DELETE"]
match_rule_path: '/user'
match_rule_type: 'exact'
# For the domain legacy.example.com, all POST requests need verification.
- match_rule_domain: 'legacy.example.com'
match_rule_method: ["POST"]
```
Only requests with a prefix match of `/header` under the wildcard domain `*.bar.com` need to be verified.

## Configuration Examples
Expand Down Expand Up @@ -186,13 +201,13 @@ Configuration of the `ext-auth` plugin:
http_service:
authorization_request:
allowed_headers:
- exact: x-auth-version
- exact: x-auth-version
headers_to_add:
x-envoy-header: true
authorization_response:
allowed_upstream_headers:
- exact: x-user-id
- exact: x-auth-version
- exact: x-user-id
- exact: x-auth-version
endpoint_mode: envoy
endpoint:
service_name: ext-auth.backend.svc.cluster.local
Expand Down Expand Up @@ -286,13 +301,13 @@ Configuration of the `ext-auth` plugin:
http_service:
authorization_request:
allowed_headers:
- exact: x-auth-version
- exact: x-auth-version
headers_to_add:
x-envoy-header: true
authorization_response:
allowed_upstream_headers:
- exact: x-user-id
- exact: x-auth-version
- exact: x-user-id
- exact: x-auth-version
endpoint_mode: forward_auth
endpoint:
service_name: ext-auth.backend.svc.cluster.local
Expand Down
39 changes: 28 additions & 11 deletions plugins/wasm-go/extensions/ext-auth/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,19 +260,28 @@ func parseMatchRules(json gjson.Result, config *ExtAuthConfig) error {
var err error

matchListConfig.ForEach(func(key, value gjson.Result) bool {
pathMatcher, buildErr := expr.BuildStringMatcher(
value.Get("match_rule_type").Str,
value.Get("match_rule_path").Str, false)
if buildErr != nil {
err = fmt.Errorf("failed to build string matcher for rule with domain %q, path %q, type %q: %w",
value.Get("match_rule_domain").Str,
value.Get("match_rule_path").Str,
value.Get("match_rule_type").Str,
buildErr)
return false // stop iterating
domain := value.Get("match_rule_domain").Str
methodArray := value.Get("match_rule_method").Array()
matchRuleType := value.Get("match_rule_type").Str
matchRulePath := value.Get("match_rule_path").Str

var pathMatcher expr.Matcher
var buildErr error

if matchRuleType == "" && matchRulePath == "" {
pathMatcher = nil
} else {
pathMatcher, buildErr = expr.BuildStringMatcher(matchRuleType, matchRulePath, false)
if buildErr != nil {
err = fmt.Errorf("failed to build string matcher for rule with domain %q, method %v, path %q, type %q: %w",
domain, methodArray, matchRulePath, matchRuleType, buildErr)
return false // stop iterating
}
}

ruleList = append(ruleList, expr.Rule{
Domain: value.Get("match_rule_domain").Str,
Domain: domain,
Method: convertToStringList(methodArray),
Path: pathMatcher,
})
return true // keep iterating
Expand All @@ -297,3 +306,11 @@ func convertToStringMap(result gjson.Result) map[string]string {
})
return m
}

func convertToStringList(results []gjson.Result) []string {
interfaces := make([]string, len(results))
for i, result := range results {
interfaces[i] = result.String()
}
return interfaces
}
50 changes: 49 additions & 1 deletion plugins/wasm-go/extensions/ext-auth/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ func TestParseConfig(t *testing.T) {
RuleList: []expr.Rule{
{
Domain: "*.bar.com",
Method: []string{},
Path: func() expr.Matcher {
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternPrefix, "/headers", false)
if err != nil {
Expand Down Expand Up @@ -248,6 +249,7 @@ func TestParseConfig(t *testing.T) {
"match_list": [
{
"match_rule_domain": "*.foo.com",
"match_rule_method": ["GET"],
"match_rule_path": "/api",
"match_rule_type": "exact"
}
Expand All @@ -269,6 +271,7 @@ func TestParseConfig(t *testing.T) {
RuleList: []expr.Rule{
{
Domain: "*.foo.com",
Method: []string{"GET"},
Path: func() expr.Matcher {
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternExact, "/api", false)
if err != nil {
Expand All @@ -284,6 +287,50 @@ func TestParseConfig(t *testing.T) {
StatusOnError: 403,
},
},
{
name: "Valid Match Rules with Whitelist - Only Method",
json: `{
"http_service": {
"endpoint_mode": "envoy",
"endpoint": {
"service_name": "example.com",
"service_port": 80,
"path_prefix": "/auth"
}
},
"match_type": "whitelist",
"match_list": [
{
"match_rule_method": ["GET"]
}
]
}`,
expected: ExtAuthConfig{
HttpService: HttpService{
EndpointMode: "envoy",
Client: wrapper.NewClusterClient(wrapper.FQDNCluster{
FQDN: "example.com",
Port: 80,
Host: "",
}),
PathPrefix: "/auth",
Timeout: 1000,
},
MatchRules: expr.MatchRules{
Mode: "whitelist",
RuleList: []expr.Rule{
{
Domain: "",
Method: []string{"GET"},
Path: nil,
},
},
},
FailureModeAllow: false,
FailureModeAllowHeaderAdd: false,
StatusOnError: 403,
},
},
{
name: "Missing Match Type",
json: `{
Expand Down Expand Up @@ -342,12 +389,13 @@ func TestParseConfig(t *testing.T) {
"match_list": [
{
"match_rule_domain": "*.bar.com",
"match_rule_method": ["POST","PUT","DELETE"],
"match_rule_path": "/headers",
"match_rule_type": "invalid_type"
}
]
}`,
expectedErr: `failed to build string matcher for rule with domain "*.bar.com", path "/headers", type "invalid_type": unknown string matcher type`,
expectedErr: `failed to build string matcher for rule with domain "*.bar.com", method [POST PUT DELETE], path "/headers", type "invalid_type": unknown string matcher type`,
},
}

Expand Down
28 changes: 17 additions & 11 deletions plugins/wasm-go/extensions/ext-auth/expr/match_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package expr
import (
"strings"

"ext-auth/util"
regexp "github.com/wasilibs/go-re2"
)

Expand All @@ -18,6 +19,7 @@ type MatchRules struct {

type Rule struct {
Domain string
Method []string
Path Matcher
}

Expand All @@ -28,19 +30,19 @@ func MatchRulesDefaults() MatchRules {
}
}

// IsAllowedByMode checks if the given domain and path are allowed based on the configuration mode.
func (config *MatchRules) IsAllowedByMode(domain, path string) bool {
// IsAllowedByMode checks if the given domain, method and path are allowed based on the configuration mode.
func (config *MatchRules) IsAllowedByMode(domain, method, path string) bool {
switch config.Mode {
case ModeWhitelist:
for _, rule := range config.RuleList {
if rule.matchDomainAndPath(domain, path) {
if rule.matchesAllConditions(domain, method, path) {
return true
}
}
return false
case ModeBlacklist:
for _, rule := range config.RuleList {
if rule.matchDomainAndPath(domain, path) {
if rule.matchesAllConditions(domain, method, path) {
return false
}
}
Expand All @@ -50,17 +52,21 @@ func (config *MatchRules) IsAllowedByMode(domain, path string) bool {
}
}

// matchDomainAndPath checks if the given domain and path match the rule.
// If rule.Domain is empty, it only checks rule.Path.
// If rule.Path is empty, it only checks rule.Domain.
// If both are empty, it returns false.
func (rule *Rule) matchDomainAndPath(domain, path string) bool {
if rule.Domain == "" && rule.Path == nil {
// matchesAllConditions checks if the given domain, method and path match all conditions of the rule.
func (rule *Rule) matchesAllConditions(domain, method, path string) bool {
// If all conditions are empty, return false
if rule.Domain == "" && rule.Path == nil && len(rule.Method) == 0 {
return false
}

// Check domain and path matching
domainMatch := rule.Domain == "" || matchDomain(domain, rule.Domain)
pathMatch := rule.Path == nil || rule.Path.Match(path)
return domainMatch && pathMatch

// Check HTTP method matching: if no methods are specified, any method is allowed
methodMatch := len(rule.Method) == 0 || util.ContainsString(rule.Method, method)

return domainMatch && pathMatch && methodMatch
}

// matchDomain checks if the given domain matches the pattern.
Expand Down
Loading

0 comments on commit 90ca903

Please sign in to comment.