mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 12:47:28 +08:00
feat: ext-auth plugin: Blacklist and whitelist modes support HTTP request method matching (#1798)
This commit is contained in:
@@ -77,6 +77,7 @@ MatchRule 类型每一项的配置字段说明,在使用 `array of MatchRule`
|
|||||||
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
|
||||||
| ------------------- | -------- | ---- | ------ | ------------------------------------------------------------ |
|
| ------------------- | -------- | ---- | ------ | ------------------------------------------------------------ |
|
||||||
| `match_rule_domain` | string | 否 | - | 匹配规则域名,支持通配符模式,例如 `*.bar.com` |
|
| `match_rule_domain` | string | 否 | - | 匹配规则域名,支持通配符模式,例如 `*.bar.com` |
|
||||||
|
| `match_rule_method` | []string | 否 | - | 匹配请求方法 |
|
||||||
| `match_rule_path` | string | 否 | - | 匹配请求路径的规则 |
|
| `match_rule_path` | string | 否 | - | 匹配请求路径的规则 |
|
||||||
| `match_rule_type` | string | 否 | - | 匹配请求路径的规则类型,可选 `exact` , `prefix` , `suffix`, `contains`, `regex` |
|
| `match_rule_type` | string | 否 | - | 匹配请求路径的规则类型,可选 `exact` , `prefix` , `suffix`, `contains`, `regex` |
|
||||||
|
|
||||||
@@ -100,27 +101,41 @@ MatchRule 类型每一项的配置字段说明,在使用 `array of MatchRule`
|
|||||||
**白名单模式**
|
**白名单模式**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# 白名单模式配置,符合白名单规则的请求无需验证
|
||||||
match_type: 'whitelist'
|
match_type: 'whitelist'
|
||||||
match_list:
|
match_list:
|
||||||
- match_rule_domain: '*.bar.com'
|
# 所有以 api.example.com 为域名,且路径前缀为 /public 的请求无需验证
|
||||||
match_rule_path: '/foo'
|
- match_rule_domain: 'api.example.com'
|
||||||
match_rule_type: 'prefix'
|
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
|
```yaml
|
||||||
|
# 黑名单模式配置,符合黑名单规则的请求需要验证
|
||||||
match_type: 'blacklist'
|
match_type: 'blacklist'
|
||||||
match_list:
|
match_list:
|
||||||
- match_rule_domain: '*.bar.com'
|
# 所有以 admin.example.com 为域名,且路径前缀为 /sensitive 的请求需要验证
|
||||||
match_rule_path: '/headers'
|
- match_rule_domain: 'admin.example.com'
|
||||||
match_rule_type: 'prefix'
|
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`
|
下面假设 `ext-auth` 服务在 Kubernetes 中 serviceName 为 `ext-auth`,端口 `8090`,路径为 `/auth`,命名空间为 `backend`
|
||||||
@@ -185,13 +200,13 @@ content-length: 0
|
|||||||
http_service:
|
http_service:
|
||||||
authorization_request:
|
authorization_request:
|
||||||
allowed_headers:
|
allowed_headers:
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
headers_to_add:
|
headers_to_add:
|
||||||
x-envoy-header: true
|
x-envoy-header: true
|
||||||
authorization_response:
|
authorization_response:
|
||||||
allowed_upstream_headers:
|
allowed_upstream_headers:
|
||||||
- exact: x-user-id
|
- exact: x-user-id
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
endpoint_mode: envoy
|
endpoint_mode: envoy
|
||||||
endpoint:
|
endpoint:
|
||||||
service_name: ext-auth.backend.svc.cluster.local
|
service_name: ext-auth.backend.svc.cluster.local
|
||||||
@@ -287,13 +302,13 @@ content-length: 0
|
|||||||
http_service:
|
http_service:
|
||||||
authorization_request:
|
authorization_request:
|
||||||
allowed_headers:
|
allowed_headers:
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
headers_to_add:
|
headers_to_add:
|
||||||
x-envoy-header: true
|
x-envoy-header: true
|
||||||
authorization_response:
|
authorization_response:
|
||||||
allowed_upstream_headers:
|
allowed_upstream_headers:
|
||||||
- exact: x-user-id
|
- exact: x-user-id
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
endpoint_mode: forward_auth
|
endpoint_mode: forward_auth
|
||||||
endpoint:
|
endpoint:
|
||||||
service_name: ext-auth.backend.svc.cluster.local
|
service_name: ext-auth.backend.svc.cluster.local
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ Configuration fields for each item of `MatchRule` type. When using `array of Mat
|
|||||||
| Name | Data Type | Required | Default Value | Description |
|
| 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_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_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` |
|
| `match_rule_type` | string | No | - | The type of the rule for matching the request path, can be `exact`, `prefix`, `suffix`, `contains`, `regex` |
|
||||||
|
|
||||||
@@ -100,27 +101,41 @@ Supports blacklist and whitelist mode configuration. The default is the whitelis
|
|||||||
**Whitelist Mode**
|
**Whitelist Mode**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# Configuration for the whitelist mode. Requests that match the whitelist rules do not need verification.
|
||||||
match_type: 'whitelist'
|
match_type: 'whitelist'
|
||||||
match_list:
|
match_list:
|
||||||
- match_rule_domain: '*.bar.com'
|
# Requests with the domain name api.example.com and a path prefixed with /public do not need verification.
|
||||||
match_rule_path: '/foo'
|
- match_rule_domain: 'api.example.com'
|
||||||
match_rule_type: 'prefix'
|
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**
|
**Blacklist Mode**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# Configuration for the blacklist mode. Requests that match the blacklist rules need verification.
|
||||||
match_type: 'blacklist'
|
match_type: 'blacklist'
|
||||||
match_list:
|
match_list:
|
||||||
- match_rule_domain: '*.bar.com'
|
# Requests with the domain name admin.example.com and a path prefixed with /sensitive need verification.
|
||||||
match_rule_path: '/headers'
|
- match_rule_domain: 'admin.example.com'
|
||||||
match_rule_type: 'prefix'
|
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
|
## Configuration Examples
|
||||||
|
|
||||||
@@ -186,13 +201,13 @@ Configuration of the `ext-auth` plugin:
|
|||||||
http_service:
|
http_service:
|
||||||
authorization_request:
|
authorization_request:
|
||||||
allowed_headers:
|
allowed_headers:
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
headers_to_add:
|
headers_to_add:
|
||||||
x-envoy-header: true
|
x-envoy-header: true
|
||||||
authorization_response:
|
authorization_response:
|
||||||
allowed_upstream_headers:
|
allowed_upstream_headers:
|
||||||
- exact: x-user-id
|
- exact: x-user-id
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
endpoint_mode: envoy
|
endpoint_mode: envoy
|
||||||
endpoint:
|
endpoint:
|
||||||
service_name: ext-auth.backend.svc.cluster.local
|
service_name: ext-auth.backend.svc.cluster.local
|
||||||
@@ -286,13 +301,13 @@ Configuration of the `ext-auth` plugin:
|
|||||||
http_service:
|
http_service:
|
||||||
authorization_request:
|
authorization_request:
|
||||||
allowed_headers:
|
allowed_headers:
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
headers_to_add:
|
headers_to_add:
|
||||||
x-envoy-header: true
|
x-envoy-header: true
|
||||||
authorization_response:
|
authorization_response:
|
||||||
allowed_upstream_headers:
|
allowed_upstream_headers:
|
||||||
- exact: x-user-id
|
- exact: x-user-id
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
endpoint_mode: forward_auth
|
endpoint_mode: forward_auth
|
||||||
endpoint:
|
endpoint:
|
||||||
service_name: ext-auth.backend.svc.cluster.local
|
service_name: ext-auth.backend.svc.cluster.local
|
||||||
|
|||||||
@@ -260,19 +260,28 @@ func parseMatchRules(json gjson.Result, config *ExtAuthConfig) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
matchListConfig.ForEach(func(key, value gjson.Result) bool {
|
matchListConfig.ForEach(func(key, value gjson.Result) bool {
|
||||||
pathMatcher, buildErr := expr.BuildStringMatcher(
|
domain := value.Get("match_rule_domain").Str
|
||||||
value.Get("match_rule_type").Str,
|
methodArray := value.Get("match_rule_method").Array()
|
||||||
value.Get("match_rule_path").Str, false)
|
matchRuleType := value.Get("match_rule_type").Str
|
||||||
if buildErr != nil {
|
matchRulePath := value.Get("match_rule_path").Str
|
||||||
err = fmt.Errorf("failed to build string matcher for rule with domain %q, path %q, type %q: %w",
|
|
||||||
value.Get("match_rule_domain").Str,
|
var pathMatcher expr.Matcher
|
||||||
value.Get("match_rule_path").Str,
|
var buildErr error
|
||||||
value.Get("match_rule_type").Str,
|
|
||||||
buildErr)
|
if matchRuleType == "" && matchRulePath == "" {
|
||||||
return false // stop iterating
|
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{
|
ruleList = append(ruleList, expr.Rule{
|
||||||
Domain: value.Get("match_rule_domain").Str,
|
Domain: domain,
|
||||||
|
Method: convertToStringList(methodArray),
|
||||||
Path: pathMatcher,
|
Path: pathMatcher,
|
||||||
})
|
})
|
||||||
return true // keep iterating
|
return true // keep iterating
|
||||||
@@ -297,3 +306,11 @@ func convertToStringMap(result gjson.Result) map[string]string {
|
|||||||
})
|
})
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertToStringList(results []gjson.Result) []string {
|
||||||
|
interfaces := make([]string, len(results))
|
||||||
|
for i, result := range results {
|
||||||
|
interfaces[i] = result.String()
|
||||||
|
}
|
||||||
|
return interfaces
|
||||||
|
}
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ func TestParseConfig(t *testing.T) {
|
|||||||
RuleList: []expr.Rule{
|
RuleList: []expr.Rule{
|
||||||
{
|
{
|
||||||
Domain: "*.bar.com",
|
Domain: "*.bar.com",
|
||||||
|
Method: []string{},
|
||||||
Path: func() expr.Matcher {
|
Path: func() expr.Matcher {
|
||||||
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternPrefix, "/headers", false)
|
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternPrefix, "/headers", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -248,6 +249,7 @@ func TestParseConfig(t *testing.T) {
|
|||||||
"match_list": [
|
"match_list": [
|
||||||
{
|
{
|
||||||
"match_rule_domain": "*.foo.com",
|
"match_rule_domain": "*.foo.com",
|
||||||
|
"match_rule_method": ["GET"],
|
||||||
"match_rule_path": "/api",
|
"match_rule_path": "/api",
|
||||||
"match_rule_type": "exact"
|
"match_rule_type": "exact"
|
||||||
}
|
}
|
||||||
@@ -269,6 +271,7 @@ func TestParseConfig(t *testing.T) {
|
|||||||
RuleList: []expr.Rule{
|
RuleList: []expr.Rule{
|
||||||
{
|
{
|
||||||
Domain: "*.foo.com",
|
Domain: "*.foo.com",
|
||||||
|
Method: []string{"GET"},
|
||||||
Path: func() expr.Matcher {
|
Path: func() expr.Matcher {
|
||||||
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternExact, "/api", false)
|
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternExact, "/api", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -284,6 +287,50 @@ func TestParseConfig(t *testing.T) {
|
|||||||
StatusOnError: 403,
|
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",
|
name: "Missing Match Type",
|
||||||
json: `{
|
json: `{
|
||||||
@@ -342,12 +389,13 @@ func TestParseConfig(t *testing.T) {
|
|||||||
"match_list": [
|
"match_list": [
|
||||||
{
|
{
|
||||||
"match_rule_domain": "*.bar.com",
|
"match_rule_domain": "*.bar.com",
|
||||||
|
"match_rule_method": ["POST","PUT","DELETE"],
|
||||||
"match_rule_path": "/headers",
|
"match_rule_path": "/headers",
|
||||||
"match_rule_type": "invalid_type"
|
"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`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package expr
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"ext-auth/util"
|
||||||
regexp "github.com/wasilibs/go-re2"
|
regexp "github.com/wasilibs/go-re2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ type MatchRules struct {
|
|||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Domain string
|
Domain string
|
||||||
|
Method []string
|
||||||
Path Matcher
|
Path Matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,19 +30,19 @@ func MatchRulesDefaults() MatchRules {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowedByMode checks if the given domain and path are allowed based on the configuration mode.
|
// IsAllowedByMode checks if the given domain, method and path are allowed based on the configuration mode.
|
||||||
func (config *MatchRules) IsAllowedByMode(domain, path string) bool {
|
func (config *MatchRules) IsAllowedByMode(domain, method, path string) bool {
|
||||||
switch config.Mode {
|
switch config.Mode {
|
||||||
case ModeWhitelist:
|
case ModeWhitelist:
|
||||||
for _, rule := range config.RuleList {
|
for _, rule := range config.RuleList {
|
||||||
if rule.matchDomainAndPath(domain, path) {
|
if rule.matchesAllConditions(domain, method, path) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
case ModeBlacklist:
|
case ModeBlacklist:
|
||||||
for _, rule := range config.RuleList {
|
for _, rule := range config.RuleList {
|
||||||
if rule.matchDomainAndPath(domain, path) {
|
if rule.matchesAllConditions(domain, method, path) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,17 +52,21 @@ func (config *MatchRules) IsAllowedByMode(domain, path string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchDomainAndPath checks if the given domain and path match the rule.
|
// matchesAllConditions checks if the given domain, method and path match all conditions of the rule.
|
||||||
// If rule.Domain is empty, it only checks rule.Path.
|
func (rule *Rule) matchesAllConditions(domain, method, path string) bool {
|
||||||
// If rule.Path is empty, it only checks rule.Domain.
|
// If all conditions are empty, return false
|
||||||
// If both are empty, it returns false.
|
if rule.Domain == "" && rule.Path == nil && len(rule.Method) == 0 {
|
||||||
func (rule *Rule) matchDomainAndPath(domain, path string) bool {
|
|
||||||
if rule.Domain == "" && rule.Path == nil {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check domain and path matching
|
||||||
domainMatch := rule.Domain == "" || matchDomain(domain, rule.Domain)
|
domainMatch := rule.Domain == "" || matchDomain(domain, rule.Domain)
|
||||||
pathMatch := rule.Path == nil || rule.Path.Match(path)
|
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.
|
// matchDomain checks if the given domain matches the pattern.
|
||||||
|
|||||||
@@ -6,11 +6,20 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func createMatcher(pattern string, caseSensitive bool) Matcher {
|
||||||
|
pathMatcher, err := newStringExactMatcher(pattern, caseSensitive)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pathMatcher
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsAllowedByMode(t *testing.T) {
|
func TestIsAllowedByMode(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
config MatchRules
|
config MatchRules
|
||||||
domain string
|
domain string
|
||||||
|
method string
|
||||||
path string
|
path string
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
@@ -21,17 +30,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
@@ -42,18 +47,14 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
path: "/bar",
|
method: "POST",
|
||||||
|
path: "/foo",
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,17 +64,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
@@ -84,18 +81,14 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
path: "/bar",
|
method: "POST",
|
||||||
|
path: "/foo",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -107,6 +100,7 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
@@ -117,29 +111,25 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "",
|
Domain: "",
|
||||||
Path: func() Matcher {
|
Path: createMatcher("/foo", true),
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Both Domain and Path are empty",
|
name: "All fields (Domain, Method, Path) are empty",
|
||||||
config: MatchRules{
|
config: MatchRules{
|
||||||
Mode: ModeWhitelist,
|
Mode: ModeWhitelist,
|
||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{Domain: "", Path: nil},
|
{Domain: "", Method: []string{}, Path: nil},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
@@ -150,17 +140,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
@@ -171,17 +157,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "*.example.com",
|
Domain: "*.example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "sub.example.com",
|
domain: "sub.example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
@@ -192,20 +174,48 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "*.example.com",
|
Domain: "*.example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Whitelist mode, only method matches",
|
||||||
|
config: MatchRules{
|
||||||
|
Mode: ModeWhitelist,
|
||||||
|
RuleList: []Rule{
|
||||||
|
{
|
||||||
|
Method: []string{"GET"},
|
||||||
|
Path: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
|
path: "/foo",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Whitelist mode, only domain matches",
|
||||||
|
config: MatchRules{
|
||||||
|
Mode: ModeWhitelist,
|
||||||
|
RuleList: []Rule{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Path: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
|
path: "/foo",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Blacklist mode, generic domain matches",
|
name: "Blacklist mode, generic domain matches",
|
||||||
config: MatchRules{
|
config: MatchRules{
|
||||||
@@ -213,17 +223,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "*.example.com",
|
Domain: "*.example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "sub.example.com",
|
domain: "sub.example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
@@ -234,25 +240,89 @@ func TestIsAllowedByMode(t *testing.T) {
|
|||||||
RuleList: []Rule{
|
RuleList: []Rule{
|
||||||
{
|
{
|
||||||
Domain: "*.example.com",
|
Domain: "*.example.com",
|
||||||
Path: func() Matcher {
|
Method: []string{"GET"},
|
||||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
Path: createMatcher("/foo", true),
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Matcher: %v", err)
|
|
||||||
}
|
|
||||||
return pathMatcher
|
|
||||||
}(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
path: "/foo",
|
path: "/foo",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Domain with special characters",
|
||||||
|
config: MatchRules{
|
||||||
|
Mode: ModeWhitelist,
|
||||||
|
RuleList: []Rule{
|
||||||
|
{
|
||||||
|
Domain: "example-*.com",
|
||||||
|
Method: []string{"GET"},
|
||||||
|
Path: createMatcher("/foo", true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: "example-test.com",
|
||||||
|
method: "GET",
|
||||||
|
path: "/foo",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path with special characters",
|
||||||
|
config: MatchRules{
|
||||||
|
Mode: ModeWhitelist,
|
||||||
|
RuleList: []Rule{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Method: []string{"GET"},
|
||||||
|
Path: createMatcher("/foo-bar", true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: "example.com",
|
||||||
|
method: "GET",
|
||||||
|
path: "/foo-bar",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple methods, one matches",
|
||||||
|
config: MatchRules{
|
||||||
|
Mode: ModeWhitelist,
|
||||||
|
RuleList: []Rule{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Method: []string{"GET", "POST"},
|
||||||
|
Path: createMatcher("/foo", true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: "example.com",
|
||||||
|
method: "POST",
|
||||||
|
path: "/foo",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple methods, none match",
|
||||||
|
config: MatchRules{
|
||||||
|
Mode: ModeWhitelist,
|
||||||
|
RuleList: []Rule{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Method: []string{"GET", "POST"},
|
||||||
|
Path: createMatcher("/foo", true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: "example.com",
|
||||||
|
method: "PUT",
|
||||||
|
path: "/foo",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := tt.config.IsAllowedByMode(tt.domain, tt.path)
|
result := tt.config.IsAllowedByMode(tt.domain, tt.method, tt.path)
|
||||||
assert.Equal(t, tt.expected, result)
|
assert.Equal(t, tt.expected, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config config.ExtAuthConfig, log wrapper.Log) types.Action {
|
func onHttpRequestHeaders(ctx wrapper.HttpContext, config config.ExtAuthConfig, log wrapper.Log) types.Action {
|
||||||
path := wrapper.GetRequestPathWithoutQuery()
|
|
||||||
// If the request's domain and path match the MatchRules, skip authentication
|
// If the request's domain and path match the MatchRules, skip authentication
|
||||||
if config.MatchRules.IsAllowedByMode(ctx.Host(), path) {
|
if config.MatchRules.IsAllowedByMode(ctx.Host(), ctx.Method(), wrapper.GetRequestPathWithoutQuery()) {
|
||||||
ctx.DontReadRequestBody()
|
ctx.DontReadRequestBody()
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,3 +37,12 @@ func ExtractFromHeader(headers [][2]string, headerKey string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ContainsString(slice []string, s string) bool {
|
||||||
|
for _, item := range slice {
|
||||||
|
if strings.EqualFold(item, s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user