mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 20:57:32 +08:00
ut: add ext-auth unit tests (#1710)
This commit is contained in:
@@ -65,7 +65,7 @@ func ParseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := parseMatchRules(json, config, log); err != nil {
|
if err := parseMatchRules(json, config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ func parseAuthorizationResponseConfig(json gjson.Result, httpService *HttpServic
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMatchRules(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) error {
|
func parseMatchRules(json gjson.Result, config *ExtAuthConfig) error {
|
||||||
matchListConfig := json.Get("match_list")
|
matchListConfig := json.Get("match_list")
|
||||||
if !matchListConfig.Exists() {
|
if !matchListConfig.Exists() {
|
||||||
config.MatchRules = expr.MatchRulesDefaults()
|
config.MatchRules = expr.MatchRulesDefaults()
|
||||||
@@ -260,10 +260,15 @@ func parseMatchRules(json gjson.Result, config *ExtAuthConfig, log wrapper.Log)
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
matchListConfig.ForEach(func(key, value gjson.Result) bool {
|
matchListConfig.ForEach(func(key, value gjson.Result) bool {
|
||||||
pathMatcher, err := expr.BuildStringMatcher(
|
pathMatcher, buildErr := expr.BuildStringMatcher(
|
||||||
value.Get("match_rule_type").Str,
|
value.Get("match_rule_type").Str,
|
||||||
value.Get("match_rule_path").Str, false)
|
value.Get("match_rule_path").Str, false)
|
||||||
if err != nil {
|
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
|
return false // stop iterating
|
||||||
}
|
}
|
||||||
ruleList = append(ruleList, expr.Rule{
|
ruleList = append(ruleList, expr.Rule{
|
||||||
@@ -274,7 +279,7 @@ func parseMatchRules(json gjson.Result, config *ExtAuthConfig, log wrapper.Log)
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to build string matcher for rule %v: %w", matchListConfig, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.MatchRules = expr.MatchRules{
|
config.MatchRules = expr.MatchRules{
|
||||||
|
|||||||
@@ -1,136 +1,368 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"testing"
|
||||||
"strings"
|
|
||||||
|
|
||||||
regexp "github.com/wasilibs/go-re2"
|
"ext-auth/expr"
|
||||||
|
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func TestParseConfig(t *testing.T) {
|
||||||
MatchPatternExact string = "exact"
|
tests := []struct {
|
||||||
MatchPatternPrefix string = "prefix"
|
name string
|
||||||
MatchPatternSuffix string = "suffix"
|
json string
|
||||||
MatchPatternContains string = "contains"
|
expected ExtAuthConfig
|
||||||
MatchPatternRegex string = "regex"
|
expectedErr string
|
||||||
|
}{
|
||||||
MatchIgnoreCase string = "ignore_case"
|
{
|
||||||
)
|
name: "Valid Config with Default Values",
|
||||||
|
json: `{
|
||||||
type Matcher interface {
|
"http_service": {
|
||||||
Match(s string) bool
|
"endpoint_mode": "envoy",
|
||||||
}
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
type stringExactMatcher struct {
|
"service_port": 80,
|
||||||
target string
|
"path_prefix": "/auth"
|
||||||
ignoreCase bool
|
}
|
||||||
}
|
}
|
||||||
|
}`,
|
||||||
func (m *stringExactMatcher) Match(s string) bool {
|
expected: ExtAuthConfig{
|
||||||
if m.ignoreCase {
|
HttpService: HttpService{
|
||||||
return strings.ToLower(s) == m.target
|
EndpointMode: "envoy",
|
||||||
|
Client: wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||||
|
FQDN: "example.com",
|
||||||
|
Port: 80,
|
||||||
|
Host: "",
|
||||||
|
}),
|
||||||
|
PathPrefix: "/auth",
|
||||||
|
Timeout: 1000,
|
||||||
|
},
|
||||||
|
MatchRules: expr.MatchRulesDefaults(),
|
||||||
|
FailureModeAllow: false,
|
||||||
|
FailureModeAllowHeaderAdd: false,
|
||||||
|
StatusOnError: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Config with Custom Values",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "forward_auth",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "auth.example.com",
|
||||||
|
"service_port": 8080,
|
||||||
|
"service_host": "auth.example.com",
|
||||||
|
"request_method": "POST",
|
||||||
|
"path": "/auth"
|
||||||
|
},
|
||||||
|
"timeout": 2000,
|
||||||
|
"authorization_request": {
|
||||||
|
"headers_to_add": {
|
||||||
|
"X-Auth-Source": "wasm"
|
||||||
|
},
|
||||||
|
"with_request_body": true,
|
||||||
|
"max_request_body_bytes": 1048576
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skipped_path_prefixes": ["/health", "/metrics"],
|
||||||
|
"failure_mode_allow": true,
|
||||||
|
"failure_mode_allow_header_add": true,
|
||||||
|
"status_on_error": 500
|
||||||
|
}`,
|
||||||
|
expected: ExtAuthConfig{
|
||||||
|
HttpService: HttpService{
|
||||||
|
EndpointMode: "forward_auth",
|
||||||
|
Client: wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||||
|
FQDN: "auth.example.com",
|
||||||
|
Port: 8080,
|
||||||
|
Host: "auth.example.com",
|
||||||
|
}),
|
||||||
|
RequestMethod: "POST",
|
||||||
|
Path: "/auth",
|
||||||
|
Timeout: 2000,
|
||||||
|
AuthorizationRequest: AuthorizationRequest{
|
||||||
|
HeadersToAdd: map[string]string{
|
||||||
|
"X-Auth-Source": "wasm",
|
||||||
|
},
|
||||||
|
WithRequestBody: true,
|
||||||
|
MaxRequestBodyBytes: 1048576,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MatchRules: expr.MatchRulesDefaults(),
|
||||||
|
FailureModeAllow: true,
|
||||||
|
FailureModeAllowHeaderAdd: true,
|
||||||
|
StatusOnError: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing HttpService Configuration",
|
||||||
|
json: `{}`,
|
||||||
|
expectedErr: "missing http_service in config",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Endpoint Mode",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "invalid_mode",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectedErr: "endpoint_mode invalid_mode is not supported",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing Endpoint Configuration",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectedErr: "missing endpoint in config",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty Service Name",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "",
|
||||||
|
"service_port": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectedErr: "endpoint service name must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Request Method with Request Body",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "forward_auth",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "auth.example.com",
|
||||||
|
"service_port": 8080,
|
||||||
|
"request_method": "GET",
|
||||||
|
"path": "/auth"
|
||||||
|
},
|
||||||
|
"authorization_request": {
|
||||||
|
"with_request_body": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectedErr: "requestMethod GET does not support with_request_body set to true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing Path for Forward Auth",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "forward_auth",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "auth.example.com",
|
||||||
|
"service_port": 8080,
|
||||||
|
"service_host": "auth.example.com",
|
||||||
|
"request_method": "POST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectedErr: "when endpoint_mode is forward_auth, endpoint path must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing Path Prefix for Envoy",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectedErr: "when endpoint_mode is envoy, endpoint path_prefix must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Match Rules with Blacklist",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80,
|
||||||
|
"path_prefix": "/auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"match_type": "blacklist",
|
||||||
|
"match_list": [
|
||||||
|
{
|
||||||
|
"match_rule_domain": "*.bar.com",
|
||||||
|
"match_rule_path": "/headers",
|
||||||
|
"match_rule_type": "prefix"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
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: "blacklist",
|
||||||
|
RuleList: []expr.Rule{
|
||||||
|
{
|
||||||
|
Domain: "*.bar.com",
|
||||||
|
Path: func() expr.Matcher {
|
||||||
|
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternPrefix, "/headers", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create Matcher: %v", err)
|
||||||
|
}
|
||||||
|
return pathMatcher
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FailureModeAllow: false,
|
||||||
|
FailureModeAllowHeaderAdd: false,
|
||||||
|
StatusOnError: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Match Rules with Whitelist",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80,
|
||||||
|
"path_prefix": "/auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"match_type": "whitelist",
|
||||||
|
"match_list": [
|
||||||
|
{
|
||||||
|
"match_rule_domain": "*.foo.com",
|
||||||
|
"match_rule_path": "/api",
|
||||||
|
"match_rule_type": "exact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
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: "*.foo.com",
|
||||||
|
Path: func() expr.Matcher {
|
||||||
|
pathMatcher, err := expr.BuildStringMatcher(expr.MatchPatternExact, "/api", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create Matcher: %v", err)
|
||||||
|
}
|
||||||
|
return pathMatcher
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FailureModeAllow: false,
|
||||||
|
FailureModeAllowHeaderAdd: false,
|
||||||
|
StatusOnError: 403,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing Match Type",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80,
|
||||||
|
"path_prefix": "/auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"match_list": [
|
||||||
|
{
|
||||||
|
"match_rule_domain": "*.bar.com",
|
||||||
|
"match_rule_path": "/headers",
|
||||||
|
"match_rule_type": "prefix"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
expectedErr: "missing match_type in config",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Match Type",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80,
|
||||||
|
"path_prefix": "/auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"match_type": "invalid_type",
|
||||||
|
"match_list": [
|
||||||
|
{
|
||||||
|
"match_rule_domain": "*.bar.com",
|
||||||
|
"match_rule_path": "/headers",
|
||||||
|
"match_rule_type": "prefix"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
expectedErr: "invalid match_type in config, must be 'whitelist' or 'blacklist'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Match Rule Type",
|
||||||
|
json: `{
|
||||||
|
"http_service": {
|
||||||
|
"endpoint_mode": "envoy",
|
||||||
|
"endpoint": {
|
||||||
|
"service_name": "example.com",
|
||||||
|
"service_port": 80,
|
||||||
|
"path_prefix": "/auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"match_type": "blacklist",
|
||||||
|
"match_list": [
|
||||||
|
{
|
||||||
|
"match_rule_domain": "*.bar.com",
|
||||||
|
"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`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return s == m.target
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringPrefixMatcher struct {
|
for _, tt := range tests {
|
||||||
target string
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ignoreCase bool
|
var config ExtAuthConfig
|
||||||
}
|
result := gjson.Parse(tt.json)
|
||||||
|
err := ParseConfig(result, &config, &wrapper.DefaultLog{})
|
||||||
|
|
||||||
func (m *stringPrefixMatcher) Match(s string) bool {
|
if tt.expectedErr != "" {
|
||||||
if m.ignoreCase {
|
assert.EqualError(t, err, tt.expectedErr)
|
||||||
return strings.HasPrefix(strings.ToLower(s), m.target)
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, config)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return strings.HasPrefix(s, m.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringSuffixMatcher struct {
|
|
||||||
target string
|
|
||||||
ignoreCase bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *stringSuffixMatcher) Match(s string) bool {
|
|
||||||
if m.ignoreCase {
|
|
||||||
return strings.HasSuffix(strings.ToLower(s), m.target)
|
|
||||||
}
|
|
||||||
return strings.HasSuffix(s, m.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringContainsMatcher struct {
|
|
||||||
target string
|
|
||||||
ignoreCase bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *stringContainsMatcher) Match(s string) bool {
|
|
||||||
if m.ignoreCase {
|
|
||||||
return strings.Contains(strings.ToLower(s), m.target)
|
|
||||||
}
|
|
||||||
return strings.Contains(s, m.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringRegexMatcher struct {
|
|
||||||
regex *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *stringRegexMatcher) Match(s string) bool {
|
|
||||||
return m.regex.MatchString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MatcherConstructor func(string, bool) (Matcher, error)
|
|
||||||
|
|
||||||
var matcherConstructors = map[string]MatcherConstructor{
|
|
||||||
MatchPatternExact: newStringExactMatcher,
|
|
||||||
MatchPatternPrefix: newStringPrefixMatcher,
|
|
||||||
MatchPatternSuffix: newStringSuffixMatcher,
|
|
||||||
MatchPatternContains: newStringContainsMatcher,
|
|
||||||
MatchPatternRegex: newStringRegexMatcher,
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringExactMatcher(target string, ignoreCase bool) (Matcher, error) {
|
|
||||||
if ignoreCase {
|
|
||||||
target = strings.ToLower(target)
|
|
||||||
}
|
|
||||||
return &stringExactMatcher{target: target, ignoreCase: ignoreCase}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringPrefixMatcher(target string, ignoreCase bool) (Matcher, error) {
|
|
||||||
if ignoreCase {
|
|
||||||
target = strings.ToLower(target)
|
|
||||||
}
|
|
||||||
return &stringPrefixMatcher{target: target, ignoreCase: ignoreCase}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringSuffixMatcher(target string, ignoreCase bool) (Matcher, error) {
|
|
||||||
if ignoreCase {
|
|
||||||
target = strings.ToLower(target)
|
|
||||||
}
|
|
||||||
return &stringSuffixMatcher{target: target, ignoreCase: ignoreCase}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringContainsMatcher(target string, ignoreCase bool) (Matcher, error) {
|
|
||||||
if ignoreCase {
|
|
||||||
target = strings.ToLower(target)
|
|
||||||
}
|
|
||||||
return &stringContainsMatcher{target: target, ignoreCase: ignoreCase}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringRegexMatcher(target string, ignoreCase bool) (Matcher, error) {
|
|
||||||
if ignoreCase && !strings.HasPrefix(target, "(?i)") {
|
|
||||||
target = "(?i)" + target
|
|
||||||
}
|
|
||||||
re, err := regexp.Compile(target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &stringRegexMatcher{regex: re}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildStringMatcher(matchType, target string, ignoreCase bool) (Matcher, error) {
|
|
||||||
for constructorType, constructor := range matcherConstructors {
|
|
||||||
if constructorType == matchType {
|
|
||||||
return constructor(target, ignoreCase)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("unknown string matcher type")
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user