mirror of
https://github.com/alibaba/higress.git
synced 2026-05-25 21:28:17 +08:00
feat(ext-auth): add support for allowed properties forwarding in external authorization requests (#3694)
Signed-off-by: CH3CHO <ch3cho@qq.com>
This commit is contained in:
@@ -51,6 +51,12 @@ type AuthorizationRequest struct {
|
||||
HeadersToAdd map[string]string
|
||||
WithRequestBody bool
|
||||
MaxRequestBodyBytes uint32
|
||||
AllowedProperties []AllowedProperty
|
||||
}
|
||||
|
||||
type AllowedProperty struct {
|
||||
Path []string
|
||||
Header string
|
||||
}
|
||||
|
||||
type AuthorizationResponse struct {
|
||||
@@ -210,6 +216,13 @@ func parseAuthorizationRequestConfig(json gjson.Result, httpService *HttpService
|
||||
}
|
||||
authorizationRequest.MaxRequestBodyBytes = maxRequestBodyBytes
|
||||
|
||||
allowedProperties := authorizationRequestConfig.Get("allowed_properties").Array()
|
||||
var err error
|
||||
authorizationRequest.AllowedProperties, err = parseAllowedProperties(allowedProperties)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpService.AuthorizationRequest = authorizationRequest
|
||||
}
|
||||
return nil
|
||||
@@ -316,3 +329,33 @@ func convertToStringList(results []gjson.Result) []string {
|
||||
}
|
||||
return interfaces
|
||||
}
|
||||
|
||||
func parseAllowedProperties(results []gjson.Result) ([]AllowedProperty, error) {
|
||||
props := make([]AllowedProperty, 0, len(results))
|
||||
for i, result := range results {
|
||||
pathVal := result.Get("path")
|
||||
headerVal := result.Get("header")
|
||||
if !pathVal.Exists() {
|
||||
return nil, fmt.Errorf("allowed_properties[%d]: missing required field 'path'", i)
|
||||
}
|
||||
if !headerVal.Exists() {
|
||||
return nil, fmt.Errorf("allowed_properties[%d]: missing required field 'header'", i)
|
||||
}
|
||||
// path can be array format: [route_name] or [metadata, test]
|
||||
// or single value format: route_name
|
||||
var path []string
|
||||
if pathVal.IsArray() {
|
||||
pathVal.ForEach(func(key, value gjson.Result) bool {
|
||||
path = append(path, value.String())
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
path = []string{pathVal.String()}
|
||||
}
|
||||
props = append(props, AllowedProperty{
|
||||
Path: path,
|
||||
Header: headerVal.String(),
|
||||
})
|
||||
}
|
||||
return props, nil
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ func TestParseConfig(t *testing.T) {
|
||||
},
|
||||
WithRequestBody: true,
|
||||
MaxRequestBodyBytes: 1048576,
|
||||
AllowedProperties: []AllowedProperty{},
|
||||
},
|
||||
},
|
||||
MatchRules: expr.MatchRulesDefaults(),
|
||||
@@ -398,6 +399,165 @@ func TestParseConfig(t *testing.T) {
|
||||
}`,
|
||||
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`,
|
||||
},
|
||||
{
|
||||
name: "Valid AllowedProperties with Array Path",
|
||||
json: `{
|
||||
"http_service": {
|
||||
"endpoint_mode": "envoy",
|
||||
"endpoint": {
|
||||
"service_name": "example.com",
|
||||
"service_port": 80,
|
||||
"path_prefix": "/auth"
|
||||
},
|
||||
"authorization_request": {
|
||||
"allowed_properties": [
|
||||
{"path": ["route_name"], "header": "x-route-name"},
|
||||
{"path": ["metadata", "test"], "header": "x-metadata"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expected: ExtAuthConfig{
|
||||
HttpService: HttpService{
|
||||
EndpointMode: "envoy",
|
||||
Client: wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||
FQDN: "example.com",
|
||||
Port: 80,
|
||||
Host: "",
|
||||
}),
|
||||
PathPrefix: "/auth",
|
||||
Timeout: 1000,
|
||||
AuthorizationRequest: AuthorizationRequest{
|
||||
HeadersToAdd: map[string]string{},
|
||||
MaxRequestBodyBytes: 10 * 1024 * 1024,
|
||||
AllowedProperties: []AllowedProperty{
|
||||
{Path: []string{"route_name"}, Header: "x-route-name"},
|
||||
{Path: []string{"metadata", "test"}, Header: "x-metadata"},
|
||||
},
|
||||
},
|
||||
},
|
||||
MatchRules: expr.MatchRulesDefaults(),
|
||||
FailureModeAllow: false,
|
||||
FailureModeAllowHeaderAdd: false,
|
||||
StatusOnError: 403,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid AllowedProperties with Single Value Path",
|
||||
json: `{
|
||||
"http_service": {
|
||||
"endpoint_mode": "envoy",
|
||||
"endpoint": {
|
||||
"service_name": "example.com",
|
||||
"service_port": 80,
|
||||
"path_prefix": "/auth"
|
||||
},
|
||||
"authorization_request": {
|
||||
"allowed_properties": [
|
||||
{"path": "route_name", "header": "x-route-name"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expected: ExtAuthConfig{
|
||||
HttpService: HttpService{
|
||||
EndpointMode: "envoy",
|
||||
Client: wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||
FQDN: "example.com",
|
||||
Port: 80,
|
||||
Host: "",
|
||||
}),
|
||||
PathPrefix: "/auth",
|
||||
Timeout: 1000,
|
||||
AuthorizationRequest: AuthorizationRequest{
|
||||
HeadersToAdd: map[string]string{},
|
||||
MaxRequestBodyBytes: 10 * 1024 * 1024,
|
||||
AllowedProperties: []AllowedProperty{
|
||||
{Path: []string{"route_name"}, Header: "x-route-name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
MatchRules: expr.MatchRulesDefaults(),
|
||||
FailureModeAllow: false,
|
||||
FailureModeAllowHeaderAdd: false,
|
||||
StatusOnError: 403,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid AllowedProperties Empty Array",
|
||||
json: `{
|
||||
"http_service": {
|
||||
"endpoint_mode": "envoy",
|
||||
"endpoint": {
|
||||
"service_name": "example.com",
|
||||
"service_port": 80,
|
||||
"path_prefix": "/auth"
|
||||
},
|
||||
"authorization_request": {
|
||||
"allowed_properties": []
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expected: ExtAuthConfig{
|
||||
HttpService: HttpService{
|
||||
EndpointMode: "envoy",
|
||||
Client: wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||
FQDN: "example.com",
|
||||
Port: 80,
|
||||
Host: "",
|
||||
}),
|
||||
PathPrefix: "/auth",
|
||||
Timeout: 1000,
|
||||
AuthorizationRequest: AuthorizationRequest{
|
||||
HeadersToAdd: map[string]string{},
|
||||
MaxRequestBodyBytes: 10 * 1024 * 1024,
|
||||
AllowedProperties: []AllowedProperty{},
|
||||
},
|
||||
},
|
||||
MatchRules: expr.MatchRulesDefaults(),
|
||||
FailureModeAllow: false,
|
||||
FailureModeAllowHeaderAdd: false,
|
||||
StatusOnError: 403,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AllowedProperties Missing Path Field",
|
||||
json: `{
|
||||
"http_service": {
|
||||
"endpoint_mode": "envoy",
|
||||
"endpoint": {
|
||||
"service_name": "example.com",
|
||||
"service_port": 80,
|
||||
"path_prefix": "/auth"
|
||||
},
|
||||
"authorization_request": {
|
||||
"allowed_properties": [
|
||||
{"header": "x-route-name"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expectedErr: "allowed_properties[0]: missing required field 'path'",
|
||||
},
|
||||
{
|
||||
name: "AllowedProperties Missing Header Field",
|
||||
json: `{
|
||||
"http_service": {
|
||||
"endpoint_mode": "envoy",
|
||||
"endpoint": {
|
||||
"service_name": "example.com",
|
||||
"service_port": 80,
|
||||
"path_prefix": "/auth"
|
||||
},
|
||||
"authorization_request": {
|
||||
"allowed_properties": [
|
||||
{"path": ["route_name"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expectedErr: "allowed_properties[0]: missing required field 'header'",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user