Skip to content

Commit

Permalink
Add support for multi-valued headers.
Browse files Browse the repository at this point in the history
Remove the assumption on header value to be a string, by checking the type of value and parsing accordingly, when multiple values are present.
Fixes #5925

Signed-off-by: Pushpalanka Jayawardhana <pushpalankajaya@gmail.com>
  • Loading branch information
Pushpalanka authored and ashutosh-narkar committed May 30, 2023
1 parent 4efcf24 commit 42fa70f
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 6 deletions.
23 changes: 17 additions & 6 deletions envoyauth/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,24 @@ func transformToHTTPHeaderFormat(input interface{}, result *http.Header) error {

takeResponseHeaders := func(headers map[string]interface{}, targetHeaders *http.Header) error {
for key, value := range headers {
var headerVal string
var ok bool
if headerVal, ok = value.(string); !ok {
return fmt.Errorf("type assertion error")
switch values := value.(type) {
case string:
targetHeaders.Add(key, values)
case []string:
for _, v := range values {
targetHeaders.Add(key, v)
}
case []interface{}:
for _, value := range values {
if headerVal, ok := value.(string); ok {
targetHeaders.Add(key, headerVal)
} else {
return fmt.Errorf("invalid value type for header '%s'", key)
}
}
default:
return fmt.Errorf("type assertion error for header '%s'", key)
}

targetHeaders.Add(key, headerVal)
}
return nil
}
Expand Down
28 changes: 28 additions & 0 deletions envoyauth/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ func TestGetResponseHTTPHeadersToAdd(t *testing.T) {
if len(result) != 2 {
t.Fatalf("Expected two headers but got %v", len(result))
}

testAddHeaders := make(map[string]interface{})
testAddHeaders["foo"] = []string{"bar", "baz"}
input["response_headers_to_add"] = testAddHeaders

result, err = er.GetResponseHTTPHeadersToAdd()

if err != nil {
t.Fatalf("Expected no error but got %v", err)
}

if len(result) != 2 {
t.Fatalf("Expected two header but got %v", len(result))
}
}

func TestGetResponseHeaders(t *testing.T) {
Expand Down Expand Up @@ -236,6 +250,20 @@ func TestGetResponseHeaders(t *testing.T) {
if seen["bar"] != 1 || seen["baz"] != 1 {
t.Errorf("expected 'bar' and 'baz', got %v", seen)
}

testAddHeaders := make(map[string]interface{})
testAddHeaders["foo"] = []string{"bar", "baz"}
input["headers"] = testAddHeaders

result, err = er.GetResponseEnvoyHeaderValueOptions()

if err != nil {
t.Fatalf("Expected no error but got %v", err)
}

if len(result) != 2 {
t.Fatalf("Expected two header but got %v", len(result))
}
}

func TestGetResponseBody(t *testing.T) {
Expand Down
78 changes: 78 additions & 0 deletions internal/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,59 @@ func TestCheckAllowObjectDecisionResponseHeadersToAdd(t *testing.T) {
}
}

func TestCheckAllowObjectDecisionMultiValuedHeaders(t *testing.T) {
var req ext_authz.CheckRequest
if err := util.Unmarshal([]byte(exampleAllowedRequestParsedPath), &req); err != nil {
panic(err)
}

module := `
package envoy.authz
default allow = false
allow {
input.parsed_path = ["my", "test", "path"]
}
response_headers_to_add["x"] = ["hello", "world"]
result["allowed"] = allow
result["response_headers_to_add"] = response_headers_to_add`

server := testAuthzServerWithModule(module, "envoy/authz/result", &testPlugin{}, false, false)

ctx := context.Background()
output, err := server.Check(ctx, &req)
if err != nil {
t.Fatal(err)
}

if output.Status.Code != int32(code.Code_OK) {
t.Fatalf("Expected request to be allowed but got: %v", output)
}

response := output.GetOkResponse()
if response == nil {
t.Fatal("Expected OkHttpResponse struct but got nil")
}

headersToAdd := response.GetResponseHeadersToAdd()
if len(headersToAdd) != 2 {
t.Fatalf("Expected two headers to add but got %v", headersToAdd)
}

headers := response.GetHeaders()
if len(headers) != 0 {
t.Fatalf("Expected no headers but got %v", len(headers))
}

expectedHeaders := http.Header{}
expectedHeaders.Set("x", "hello")
expectedHeaders.Add("x", "world")
assertHeaderValues(t, expectedHeaders, headersToAdd)
}

func TestCheckAllowObjectDecision(t *testing.T) {

// Example Envoy Check Request for input:
Expand Down Expand Up @@ -1748,6 +1801,31 @@ func assertHeaders(t *testing.T, actualHeaders []*ext_core.HeaderValueOption, ex

}

func assertHeaderValues(t *testing.T, expectedHeaders http.Header, headersToAdd []*ext_core.HeaderValueOption) {
t.Helper()
for _, header := range headersToAdd {
key := header.GetHeader().GetKey()
value := header.GetHeader().GetValue()

expectedValues := expectedHeaders[key]
if expectedValues == nil {
t.Fatalf("unexpected header '%s'", key)
}

found := false
for _, expectedValue := range expectedValues {
if expectedValue == value {
found = true
break
}
}

if !found {
t.Fatalf("unexpected value '%s' for header '%s'", value, key)
}
}
}

type testPlugin struct {
events []logs.EventV1
}
Expand Down

0 comments on commit 42fa70f

Please sign in to comment.