Skip to content

Commit

Permalink
feat: return query_parameters_to_remove with OkResponse
Browse files Browse the repository at this point in the history
Envoy External Auth v3 API supports the `query_parameters_to_remove` attribute with
`OkResponse`.

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-okhttpresponse

The change allows to return from OPA which query parameters should be removed
from the upstream query.

Signed-off-by: Anthony Regeda <regedaster@gmail.com>
  • Loading branch information
regeda committed Dec 21, 2024
1 parent a512b57 commit 171d43f
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 25 deletions.
38 changes: 23 additions & 15 deletions envoyauth/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,43 +114,51 @@ func (result *EvalResult) IsAllowed() (bool, error) {
return false, result.invalidDecisionErr()
}

// GetRequestHTTPHeadersToRemove - returns the http headers to remove from the original request before dispatching
// it to the upstream
func (result *EvalResult) GetRequestHTTPHeadersToRemove() ([]string, error) {
headersToRemove := []string{}

func (result *EvalResult) getStringSliceFromDecision(fieldName string) ([]string, error) {
switch decision := result.Decision.(type) {
case bool:
return headersToRemove, nil
return nil, nil
case map[string]interface{}:
var ok bool
var val interface{}

if val, ok = decision["request_headers_to_remove"]; !ok {
return headersToRemove, nil
if val, ok = decision[fieldName]; !ok {
return nil, nil
}

switch val := val.(type) {
case []string:
return val, nil
case []interface{}:
for _, vval := range val {
header, ok := vval.(string)
ss := make([]string, len(val))
for i, v := range val {
s, ok := v.(string)
if !ok {
return nil, fmt.Errorf("type assertion error, expected request_headers_to_remove value to be of type 'string' but got '%T'", vval)
return nil, fmt.Errorf("type assertion error, expected %s value to be of type 'string' but got '%T'", fieldName, v)
}

headersToRemove = append(headersToRemove, header)
ss[i] = s
}
return headersToRemove, nil
return ss, nil
default:
return nil, fmt.Errorf("type assertion error, expected request_headers_to_remove to be of type '[]string' but got '%T'", val)
return nil, fmt.Errorf("type assertion error, expected %s to be of type '[]string' but got '%T'", fieldName, val)
}
}

return nil, result.invalidDecisionErr()
}

// GetRequestQueryParametersToRemove - returns the query parameters to remove from the original request before dispatching
// it to the upstream
func (result *EvalResult) GetRequestQueryParametersToRemove() ([]string, error) {
return result.getStringSliceFromDecision("query_parameters_to_remove")
}

// GetRequestHTTPHeadersToRemove - returns the http headers to remove from the original request before dispatching
// it to the upstream
func (result *EvalResult) GetRequestHTTPHeadersToRemove() ([]string, error) {
return result.getStringSliceFromDecision("request_headers_to_remove")
}

// GetResponseHTTPHeaders - returns the http headers to return if they are part of the decision
func (result *EvalResult) GetResponseHTTPHeaders() (http.Header, error) {
var responseHeaders = make(http.Header)
Expand Down
78 changes: 73 additions & 5 deletions envoyauth/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,74 @@ func TestIsAllowed(t *testing.T) {
}
}

func TestGetRequestQueryParametersToRemove(t *testing.T) {
tests := map[string]struct {
decision interface{}
exp []string
wantErr bool
}{
"bool_eval_result": {
true,
nil,
false,
},
"invalid_eval_result": {
"hello",
nil,
true,
},
"empty_map_result": {
map[string]interface{}{},
nil,
false,
},
"bad_param_value": {
map[string]interface{}{"query_parameters_to_remove": "test"},
nil,
true,
},
"string_array_param_value": {
map[string]interface{}{"query_parameters_to_remove": []string{"foo", "bar"}},
[]string{"foo", "bar"},
false,
},
"interface_array_param_value": {
map[string]interface{}{"query_parameters_to_remove": []interface{}{"foo", "bar", "fuz"}},
[]string{"foo", "bar", "fuz"},
false,
},
"interface_array_bad_param_value": {
map[string]interface{}{"query_parameters_to_remove": []interface{}{1}},
nil,
true,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
er := EvalResult{
Decision: tc.decision,
}

result, err := er.GetRequestQueryParametersToRemove()

if tc.wantErr {
if err == nil {
t.Fatal("Expected error but got nil")
}
} else {
if err != nil {
t.Fatalf("Unexpected error %v", err)
}

if !reflect.DeepEqual(tc.exp, result) {
t.Fatalf("Expected result %v but got %v", tc.exp, result)
}
}
})
}
}

func TestGetRequestHTTPHeadersToRemove(t *testing.T) {
tests := map[string]struct {
decision interface{}
Expand All @@ -54,22 +122,22 @@ func TestGetRequestHTTPHeadersToRemove(t *testing.T) {
}{
"bool_eval_result": {
true,
[]string{},
nil,
false,
},
"invalid_eval_result": {
"hello",
[]string{},
nil,
true,
},
"empty_map_result": {
map[string]interface{}{},
[]string{},
nil,
false,
},
"bad_header_value": {
map[string]interface{}{"request_headers_to_remove": "test"},
[]string{},
nil,
true,
},
"string_array_header_value": {
Expand All @@ -84,7 +152,7 @@ func TestGetRequestHTTPHeadersToRemove(t *testing.T) {
},
"interface_array_bad_header_value": {
map[string]interface{}{"request_headers_to_remove": []interface{}{1}},
[]string{},
nil,
true,
},
}
Expand Down
Loading

0 comments on commit 171d43f

Please sign in to comment.