50 changes: 50 additions & 0 deletions src/go/configgenerator/filtergen/header_sanitizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package filtergen

import (
ci "github.com/GoogleCloudPlatform/esp-v2/src/go/configinfo"
hspb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/header_sanitizer"
"github.com/GoogleCloudPlatform/esp-v2/src/go/util"
"github.com/GoogleCloudPlatform/esp-v2/src/go/util/httppattern"
hcmpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/golang/protobuf/ptypes"
anypb "github.com/golang/protobuf/ptypes/any"
)

type HeaderSanitizerGenerator struct{}

func (g *HeaderSanitizerGenerator) FilterName() string {
return util.HeaderSanitizerScrubber
}

func (g *HeaderSanitizerGenerator) IsEnabled() bool {
return true
}

func (g *HeaderSanitizerGenerator) GenFilterConfig(serviceInfo *ci.ServiceInfo) (*hcmpb.HttpFilter, error) {
a, err := ptypes.MarshalAny(&hspb.FilterConfig{})
if err != nil {
return nil, err
}
return &hcmpb.HttpFilter{
Name: g.FilterName(),
ConfigType: &hcmpb.HttpFilter_TypedConfig{TypedConfig: a},
}, nil
}

func (g *HeaderSanitizerGenerator) GenPerRouteConfig(method *ci.MethodInfo, httpRule *httppattern.Pattern) (*anypb.Any, error) {
return nil, nil
}
16 changes: 14 additions & 2 deletions src/go/configgenerator/listener_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ func TestMakeListeners(t *testing.T) {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"commonHttpProtocolOptions": {},
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "com.google.espv2.filters.http.grpc_metadata_scrubber",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.grpc_metadata_scrubber.FilterConfig"
Expand Down Expand Up @@ -234,7 +240,13 @@ func TestMakeListeners(t *testing.T) {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"commonHttpProtocolOptions": {},
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "com.google.espv2.filters.http.grpc_metadata_scrubber",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.grpc_metadata_scrubber.FilterConfig"
Expand Down
56 changes: 49 additions & 7 deletions src/go/configmanager/testdata/test_fetch_listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ var (
"headersWithUnderscoresAction": "REJECT_REQUEST"
},
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "envoy.filters.http.grpc_web",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb"
Expand Down Expand Up @@ -316,7 +322,13 @@ var (
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "envoy.filters.http.jwt_authn",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
Expand Down Expand Up @@ -604,7 +616,13 @@ var (
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "envoy.filters.http.jwt_authn",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
Expand Down Expand Up @@ -1116,7 +1134,13 @@ var (
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "envoy.filters.http.jwt_authn",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
Expand Down Expand Up @@ -1587,7 +1611,13 @@ var (
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "com.google.espv2.filters.http.service_control",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.service_control.FilterConfig",
Expand Down Expand Up @@ -2122,7 +2152,13 @@ var (
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "envoy.filters.http.jwt_authn",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
Expand Down Expand Up @@ -2454,7 +2490,13 @@ var (
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "com.google.espv2.filters.http.service_control",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.service_control.FilterConfig",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,13 @@ var (
"headersWithUnderscoresAction": "REJECT_REQUEST"
},
"httpFilters": [
{
{
"name": "com.google.espv2.filters.http.header_sanitizer",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
}
},
{
"name": "com.google.espv2.filters.http.backend_auth",
"typedConfig": {
"@type": "type.googleapis.com/espv2.api.envoy.v11.http.backend_auth.FilterConfig",
Expand Down
3 changes: 3 additions & 0 deletions src/go/util/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

bapb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/backend_auth"
gmspb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/grpc_metadata_scrubber"
hspb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/header_sanitizer"
prpb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/path_rewrite"
scpb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/service_control"

Expand Down Expand Up @@ -117,6 +118,8 @@ var Resolver = FuncResolver(func(url string) (proto.Message, error) {
return new(bapb.FilterConfig), nil
case "type.googleapis.com/espv2.api.envoy.v11.http.grpc_metadata_scrubber.FilterConfig":
return new(gmspb.FilterConfig), nil
case "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig":
return new(hspb.FilterConfig), nil
case "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router":
return new(routerpb.Router), nil
case "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext":
Expand Down
2 changes: 2 additions & 0 deletions src/go/util/xds_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const (
BackendAuth = "com.google.espv2.filters.http.backend_auth"
// gRPC Metadata Scrubber filter.
GrpcMetadataScrubber = "com.google.espv2.filters.http.grpc_metadata_scrubber"
// HeaderSanitizerScrubber is the filter name (matches c++ factory).
HeaderSanitizerScrubber = "com.google.espv2.filters.http.header_sanitizer"

// The metadata server cluster name.
MetadataServerClusterName = "metadata-cluster"
Expand Down
1 change: 1 addition & 0 deletions tests/env/platform/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
TestAuthAllowMissing
TestAuthJwksAsyncFetch
TestAuthJwksCache
TestAuthWithMethodOverride
TestBackendAddressOverride
TestBackendAuthDisableAuth
TestBackendAuthPerPlatform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,18 @@ func TestHttp1JWT(t *testing.T) {
},
}
for _, tc := range testData {
host := fmt.Sprintf("http://%v:%v", platform.GetLoopbackAddress(), s.Ports().ListenerPort)
resp, err := client.DoJWT(host, tc.httpMethod, tc.httpPath, "", "", tc.token)
t.Run(tc.desc, func(t *testing.T) {
host := fmt.Sprintf("http://%v:%v", platform.GetLoopbackAddress(), s.Ports().ListenerPort)
resp, err := client.DoJWT(host, tc.httpMethod, tc.httpPath, "", "", tc.token)

if tc.wantedError == "" && err != nil || tc.wantedError != "" && err == nil || err != nil && !strings.Contains(err.Error(), tc.wantedError) {
t.Errorf("Test (%s): failed, expected err: %s, got: %s", tc.desc, tc.wantedError, err)
} else {
if !strings.Contains(string(resp), tc.wantResp) {
t.Errorf("Test (%s): failed, expected: %s, got: %s", tc.desc, tc.wantResp, string(resp))
if tc.wantedError == "" && err != nil || tc.wantedError != "" && err == nil || err != nil && !strings.Contains(err.Error(), tc.wantedError) {
t.Errorf("Failed, expected err: %s, got: %s", tc.wantedError, err)
} else {
if !strings.Contains(string(resp), tc.wantResp) {
t.Errorf("Failed, expected: %s, got: %s", tc.wantResp, string(resp))
}
}
}
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package jwt_auth_integration_test
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -490,3 +491,66 @@ func TestFrontendAndBackendAuthHeaders(t *testing.T) {
})
}
}

func TestAuthWithMethodOverride(t *testing.T) {
t.Parallel()

configID := "test-config-id"
args := []string{"--service_config_id=" + configID, "--rollout_strategy=fixed"}

s := env.NewTestEnv(platform.TestAuthWithMethodOverride, platform.GrpcBookstoreSidecar)
defer s.TearDown(t)
if err := s.Setup(args); err != nil {
t.Fatalf("fail to setup test env, %v", err)
}

time.Sleep(5 * time.Second)
tests := []struct {
desc string
httpMethod string
path string
header http.Header
wantResp string
wantError string
}{
{
desc: "Succeeded, no JWT needed to get a book",
httpMethod: "GET",
path: "/v1/shelves/100/books/1001?key=api-key",
wantResp: `{"id":"1001","title":"Alphabet"}`,
},
{
desc: "Failed, need JWT to delete a book.",
httpMethod: "DELETE",
path: "/v1/shelves/100/books/1001?key=api-key",
wantError: `401 Unauthorized, {"code":401,"message":"Jwt is missing"}`,
},
{
// Regression test for b/273531500 & b/270767471.
desc: "Failed, need JWT to delete a book even when HTTP method override occurs",
httpMethod: "GET",
path: "/v1/shelves/100/books/1001?key=api-key",
header: map[string][]string{
"x-http-method-override": {"DELETE"},
},
wantError: `{"code":401,"message":"Jwt is missing"}`,
},
}

for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
addr := fmt.Sprintf("%v:%v", platform.GetLoopbackAddress(), s.Ports().ListenerPort)
resp, err := client.MakeCall("http", addr, tc.httpMethod, tc.path, "", tc.header)

if tc.wantError != "" && (err == nil || !strings.Contains(err.Error(), tc.wantError)) {
t.Errorf("Test (%s): failed, expected err: %v, got: %v", tc.desc, tc.wantError, err)
} else if tc.wantError == "" && err != nil {
t.Errorf("Test (%s): failed, expected no error, got error: %s", tc.desc, err)
} else {
if !strings.Contains(resp, tc.wantResp) {
t.Errorf("Test (%s): failed, expected: %s, got: %s", tc.desc, tc.wantResp, resp)
}
}
})
}
}