diff --git a/plugins/wasm-go/extensions/ext-auth/README.md b/plugins/wasm-go/extensions/ext-auth/README.md index cca0f655c..de7e2feb8 100644 --- a/plugins/wasm-go/extensions/ext-auth/README.md +++ b/plugins/wasm-go/extensions/ext-auth/README.md @@ -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` | @@ -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` @@ -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 @@ -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 diff --git a/plugins/wasm-go/extensions/ext-auth/README_EN.md b/plugins/wasm-go/extensions/ext-auth/README_EN.md index a095690cf..8a0121600 100644 --- a/plugins/wasm-go/extensions/ext-auth/README_EN.md +++ b/plugins/wasm-go/extensions/ext-auth/README_EN.md @@ -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` | @@ -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 @@ -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 @@ -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 diff --git a/plugins/wasm-go/extensions/ext-auth/config/config.go b/plugins/wasm-go/extensions/ext-auth/config/config.go index 5709bbf9b..def0955ce 100644 --- a/plugins/wasm-go/extensions/ext-auth/config/config.go +++ b/plugins/wasm-go/extensions/ext-auth/config/config.go @@ -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 @@ -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 +} diff --git a/plugins/wasm-go/extensions/ext-auth/config/config_test.go b/plugins/wasm-go/extensions/ext-auth/config/config_test.go index 02750356e..299035f45 100644 --- a/plugins/wasm-go/extensions/ext-auth/config/config_test.go +++ b/plugins/wasm-go/extensions/ext-auth/config/config_test.go @@ -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 { @@ -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" } @@ -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 { @@ -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: `{ @@ -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`, }, } diff --git a/plugins/wasm-go/extensions/ext-auth/expr/match_rules.go b/plugins/wasm-go/extensions/ext-auth/expr/match_rules.go index c4c89fe38..bc74cd9bf 100644 --- a/plugins/wasm-go/extensions/ext-auth/expr/match_rules.go +++ b/plugins/wasm-go/extensions/ext-auth/expr/match_rules.go @@ -3,6 +3,7 @@ package expr import ( "strings" + "ext-auth/util" regexp "github.com/wasilibs/go-re2" ) @@ -18,6 +19,7 @@ type MatchRules struct { type Rule struct { Domain string + Method []string Path Matcher } @@ -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 } } @@ -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. diff --git a/plugins/wasm-go/extensions/ext-auth/expr/match_rules_test.go b/plugins/wasm-go/extensions/ext-auth/expr/match_rules_test.go index 5d041262a..f6ab9a542 100644 --- a/plugins/wasm-go/extensions/ext-auth/expr/match_rules_test.go +++ b/plugins/wasm-go/extensions/ext-auth/expr/match_rules_test.go @@ -6,11 +6,20 @@ import ( "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) { tests := []struct { name string config MatchRules domain string + method string path string expected bool }{ @@ -21,17 +30,13 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", + method: "GET", path: "/foo", expected: true, }, @@ -42,18 +47,14 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", - path: "/bar", + method: "POST", + path: "/foo", expected: false, }, { @@ -63,17 +64,13 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", + method: "GET", path: "/foo", expected: false, }, @@ -84,18 +81,14 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", - path: "/bar", + method: "POST", + path: "/foo", expected: true, }, { @@ -107,6 +100,7 @@ func TestIsAllowedByMode(t *testing.T) { }, }, domain: "example.com", + method: "GET", path: "/foo", expected: true, }, @@ -117,29 +111,25 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", + method: "GET", path: "/foo", expected: true, }, { - name: "Both Domain and Path are empty", + name: "All fields (Domain, Method, Path) are empty", config: MatchRules{ Mode: ModeWhitelist, RuleList: []Rule{ - {Domain: "", Path: nil}, + {Domain: "", Method: []string{}, Path: nil}, }, }, domain: "example.com", + method: "GET", path: "/foo", expected: false, }, @@ -150,17 +140,13 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", + method: "GET", path: "/foo", expected: false, }, @@ -171,17 +157,13 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "*.example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "sub.example.com", + method: "GET", path: "/foo", expected: true, }, @@ -192,20 +174,48 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "*.example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", + method: "GET", path: "/foo", 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", config: MatchRules{ @@ -213,17 +223,13 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "*.example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "sub.example.com", + method: "GET", path: "/foo", expected: false, }, @@ -234,25 +240,89 @@ func TestIsAllowedByMode(t *testing.T) { RuleList: []Rule{ { Domain: "*.example.com", - Path: func() Matcher { - pathMatcher, err := newStringExactMatcher("/foo", true) - if err != nil { - t.Fatalf("Failed to create Matcher: %v", err) - } - return pathMatcher - }(), + Method: []string{"GET"}, + Path: createMatcher("/foo", true), }, }, }, domain: "example.com", + method: "GET", path: "/foo", 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 { 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) }) } diff --git a/plugins/wasm-go/extensions/ext-auth/main.go b/plugins/wasm-go/extensions/ext-auth/main.go index 8cc8c0595..7d3ce54b4 100644 --- a/plugins/wasm-go/extensions/ext-auth/main.go +++ b/plugins/wasm-go/extensions/ext-auth/main.go @@ -51,9 +51,8 @@ const ( ) 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 config.MatchRules.IsAllowedByMode(ctx.Host(), path) { + if config.MatchRules.IsAllowedByMode(ctx.Host(), ctx.Method(), wrapper.GetRequestPathWithoutQuery()) { ctx.DontReadRequestBody() return types.ActionContinue } diff --git a/plugins/wasm-go/extensions/ext-auth/util/utils.go b/plugins/wasm-go/extensions/ext-auth/util/utils.go index eef185287..2f6d8586a 100644 --- a/plugins/wasm-go/extensions/ext-auth/util/utils.go +++ b/plugins/wasm-go/extensions/ext-auth/util/utils.go @@ -37,3 +37,12 @@ func ExtractFromHeader(headers [][2]string, headerKey string) string { } return "" } + +func ContainsString(slice []string, s string) bool { + for _, item := range slice { + if strings.EqualFold(item, s) { + return true + } + } + return false +}