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:
Kent Dong
2026-05-15 16:03:50 +08:00
committed by GitHub
parent e497d8017a
commit ba774da55e
8 changed files with 553 additions and 8 deletions

View File

@@ -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
}

View File

@@ -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 {