diff --git a/api/envoy/v11/http/header_sanitizer/BUILD b/api/envoy/v11/http/header_sanitizer/BUILD new file mode 100644 index 000000000..c1c9e49d5 --- /dev/null +++ b/api/envoy/v11/http/header_sanitizer/BUILD @@ -0,0 +1,18 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_cc_py_proto_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +package(default_visibility = ["//visibility:public"]) + +api_cc_py_proto_library( + name = "config_proto", + srcs = [ + "config.proto", + ], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "config_go_proto", + importpath = "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/header_sanitizer", + proto = ":config_proto", +) diff --git a/api/envoy/v11/http/header_sanitizer/config.proto b/api/envoy/v11/http/header_sanitizer/config.proto new file mode 100644 index 000000000..3a18c0fb8 --- /dev/null +++ b/api/envoy/v11/http/header_sanitizer/config.proto @@ -0,0 +1,22 @@ +// 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. + +syntax = "proto3"; + +package espv2.api.envoy.v11.http.header_sanitizer; + +// header_sanitizer filter doesn't need any config. +// But a config protobuf type is required by FilterFactory. +// Hence defines an empty FilterConfig here. +message FilterConfig {} diff --git a/api/scripts/go_proto_gen.sh b/api/scripts/go_proto_gen.sh index 115331b78..8057ac36e 100755 --- a/api/scripts/go_proto_gen.sh +++ b/api/scripts/go_proto_gen.sh @@ -42,3 +42,7 @@ cp -f bazel-bin/api/envoy/v11/http/backend_auth/config_go_proto_/github.com/Goog bazelisk build //api/envoy/v11/http/grpc_metadata_scrubber:config_go_proto mkdir -p src/go/proto/api/envoy/v11/http/grpc_metadata_scrubber cp -f bazel-bin/api/envoy/v11/http/grpc_metadata_scrubber/config_go_proto_/github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/grpc_metadata_scrubber/* src/go/proto/api/envoy/v11/http/grpc_metadata_scrubber +# HTTP filter header_sanitizer +bazelisk build //api/envoy/v11/http/header_sanitizer:config_go_proto +mkdir -p src/go/proto/api/envoy/v11/http/header_sanitizer +cp -f bazel-bin/api/envoy/v11/http/header_sanitizer/config_go_proto_/github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/header_sanitizer/* src/go/proto/api/envoy/v11/http/header_sanitizer diff --git a/examples/auth/envoy_config.json b/examples/auth/envoy_config.json index 14f0c2766..48da3613a 100644 --- a/examples/auth/envoy_config.json +++ b/examples/auth/envoy_config.json @@ -163,6 +163,12 @@ "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.jwt_authn", "typedConfig": { diff --git a/examples/dynamic_routing/envoy_config.json b/examples/dynamic_routing/envoy_config.json index a96e91438..44f3b7ab0 100644 --- a/examples/dynamic_routing/envoy_config.json +++ b/examples/dynamic_routing/envoy_config.json @@ -218,6 +218,12 @@ "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": { diff --git a/examples/grpc_dynamic_routing/envoy_config.json b/examples/grpc_dynamic_routing/envoy_config.json index 614658384..700bbf580 100644 --- a/examples/grpc_dynamic_routing/envoy_config.json +++ b/examples/grpc_dynamic_routing/envoy_config.json @@ -231,6 +231,12 @@ "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.compressor", "typedConfig": { diff --git a/examples/service_control/envoy_config.json b/examples/service_control/envoy_config.json index ccdadc3f7..83d919050 100644 --- a/examples/service_control/envoy_config.json +++ b/examples/service_control/envoy_config.json @@ -124,6 +124,12 @@ "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.service_control", "typedConfig": { diff --git a/examples/testdata/route_match/envoy_config.json b/examples/testdata/route_match/envoy_config.json index d70a92079..8a4749233 100644 --- a/examples/testdata/route_match/envoy_config.json +++ b/examples/testdata/route_match/envoy_config.json @@ -202,6 +202,12 @@ "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": { diff --git a/examples/testdata/sidecar_backend/envoy_config.json b/examples/testdata/sidecar_backend/envoy_config.json index 69e35df82..d0746198c 100644 --- a/examples/testdata/sidecar_backend/envoy_config.json +++ b/examples/testdata/sidecar_backend/envoy_config.json @@ -124,6 +124,12 @@ "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.grpc_metadata_scrubber", "typedConfig": { diff --git a/src/envoy/BUILD b/src/envoy/BUILD index 49db0667c..5ddf75e54 100644 --- a/src/envoy/BUILD +++ b/src/envoy/BUILD @@ -17,6 +17,11 @@ alias( actual = "//src/envoy/http/grpc_metadata_scrubber:filter_factory", ) +alias( + name = "header_sanitizer", + actual = "//src/envoy/http/header_sanitizer:filter_factory", +) + alias( name = "path_rewrite", actual = "//src/envoy/http/path_rewrite:filter_factory", @@ -38,6 +43,7 @@ envoy_cc_binary( deps = [ ":backend_auth", ":grpc_metadata_scrubber", + ":header_sanitizer", ":main", ":path_rewrite", ":service_control", diff --git a/src/envoy/http/header_sanitizer/BUILD b/src/envoy/http/header_sanitizer/BUILD new file mode 100644 index 000000000..d971ed4ac --- /dev/null +++ b/src/envoy/http/header_sanitizer/BUILD @@ -0,0 +1,39 @@ +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", +) + +package( + default_visibility = [ + "//src/envoy:__subpackages__", + ], +) + +envoy_cc_library( + name = "filter_lib", + srcs = [ + "filter.cc", + ], + hdrs = [ + "filter.h", + ], + repository = "@envoy", + deps = [ + "//src/envoy/utils:http_header_utils_lib", + "//src/envoy/utils:rc_detail_utils_lib", + "@envoy//envoy/stats:stats_interface", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_cc_library( + name = "filter_factory", + srcs = ["filter_factory.cc"], + repository = "@envoy", + deps = [ + ":filter_lib", + "//api/envoy/v11/http/header_sanitizer:config_proto_cc_proto", + "@envoy//source/exe:envoy_common_lib", + ], +) diff --git a/src/envoy/http/header_sanitizer/filter.cc b/src/envoy/http/header_sanitizer/filter.cc new file mode 100644 index 000000000..63eac4b09 --- /dev/null +++ b/src/envoy/http/header_sanitizer/filter.cc @@ -0,0 +1,49 @@ +// 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. + +#include "src/envoy/http/header_sanitizer/filter.h" + +#include + +#include "envoy/http/header_map.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" +#include "src/envoy/utils/http_header_utils.h" +#include "src/envoy/utils/rc_detail_utils.h" + +namespace espv2 { +namespace envoy { +namespace http_filters { +namespace header_sanitizer { + +using Envoy::Http::FilterDataStatus; +using Envoy::Http::FilterHeadersStatus; +using Envoy::Http::FilterTrailersStatus; +using Envoy::Http::RequestHeaderMap; + +FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool) { + if (utils::handleHttpMethodOverride(headers)) { + // Update later filters that the HTTP method has changed by clearing the + // route cache. + ENVOY_LOG(debug, "HTTP method override occurred, recalculating route"); + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); + } + + return FilterHeadersStatus::Continue; +} + +} // namespace header_sanitizer +} // namespace http_filters +} // namespace envoy +} // namespace espv2 diff --git a/src/envoy/http/header_sanitizer/filter.h b/src/envoy/http/header_sanitizer/filter.h new file mode 100644 index 000000000..c4a7f5841 --- /dev/null +++ b/src/envoy/http/header_sanitizer/filter.h @@ -0,0 +1,40 @@ +// 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. + +#pragma once + +#include "envoy/http/filter.h" +#include "envoy/http/header_map.h" +#include "source/common/common/logger.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +namespace espv2 { +namespace envoy { +namespace http_filters { +namespace header_sanitizer { + +class Filter : public Envoy::Http::PassThroughDecoderFilter, + public Envoy::Logger::Loggable { + public: + Filter() = default; + + // Envoy::Http::StreamDecoderFilter + Envoy::Http::FilterHeadersStatus decodeHeaders(Envoy::Http::RequestHeaderMap&, + bool) override; +}; + +} // namespace header_sanitizer +} // namespace http_filters +} // namespace envoy +} // namespace espv2 diff --git a/src/envoy/http/header_sanitizer/filter_factory.cc b/src/envoy/http/header_sanitizer/filter_factory.cc new file mode 100644 index 000000000..7fb39bf94 --- /dev/null +++ b/src/envoy/http/header_sanitizer/filter_factory.cc @@ -0,0 +1,59 @@ +// 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. + +#include "api/envoy/v11/http/header_sanitizer/config.pb.h" +#include "api/envoy/v11/http/header_sanitizer/config.pb.validate.h" +#include "envoy/registry/registry.h" +#include "source/extensions/filters/http/common/factory_base.h" +#include "src/envoy/http/header_sanitizer/filter.h" + +namespace espv2 { +namespace envoy { +namespace http_filters { +namespace header_sanitizer { + +constexpr const char kFilterName[] = + "com.google.espv2.filters.http.header_sanitizer"; + +/** + * Config registration for ESPv2 header sanitizer filter. + */ +class FilterFactory + : public Envoy::Extensions::HttpFilters::Common::FactoryBase< + ::espv2::api::envoy::v11::http::header_sanitizer::FilterConfig> { + public: + FilterFactory() : FactoryBase(kFilterName) {} + + private: + Envoy::Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const ::espv2::api::envoy::v11::http::header_sanitizer::FilterConfig&, + const std::string&, + Envoy::Server::Configuration::FactoryContext&) override { + return [](Envoy::Http::FilterChainFactoryCallbacks& callbacks) -> void { + auto filter = std::make_shared(); + callbacks.addStreamDecoderFilter(filter); + }; + } +}; +/** + * Static registration for the filter. @see RegisterFactory. + */ +static Envoy::Registry::RegisterFactory< + FilterFactory, Envoy::Server::Configuration::NamedHttpFilterConfigFactory> + register_; + +} // namespace header_sanitizer +} // namespace http_filters +} // namespace envoy +} // namespace espv2 diff --git a/src/envoy/http/service_control/filter.cc b/src/envoy/http/service_control/filter.cc index cde4d7154..a2e5d9d27 100644 --- a/src/envoy/http/service_control/filter.cc +++ b/src/envoy/http/service_control/filter.cc @@ -53,13 +53,6 @@ Envoy::Http::FilterHeadersStatus ServiceControlFilter::decodeHeaders( return Envoy::Http::FilterHeadersStatus::StopIteration; } - if (utils::handleHttpMethodOverride(headers)) { - // Update later filters that the HTTP method has changed by clearing the - // route cache. - ENVOY_LOG(debug, "HTTP method override occurred, recalculating route"); - decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); - } - // Make sure route is calculated auto route = decoder_callbacks_->route(); diff --git a/src/go/configgenerator/filter_generator.go b/src/go/configgenerator/filter_generator.go index 3957fcf61..602aaedec 100644 --- a/src/go/configgenerator/filter_generator.go +++ b/src/go/configgenerator/filter_generator.go @@ -49,6 +49,7 @@ type FilterGenerator interface { // MakeFilterGenerators provide of a slice of FilterGenerator in sequence. func MakeFilterGenerators(serviceInfo *ci.ServiceInfo) ([]FilterGenerator, error) { return []FilterGenerator{ + &filtergen.HeaderSanitizerGenerator{}, filtergen.NewCORSGenerator(serviceInfo), // Health check filter is behind Path Matcher filter, since Service Control diff --git a/src/go/configgenerator/filtergen/header_sanitizer.go b/src/go/configgenerator/filtergen/header_sanitizer.go new file mode 100644 index 000000000..d862bfa09 --- /dev/null +++ b/src/go/configgenerator/filtergen/header_sanitizer.go @@ -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 +} diff --git a/src/go/configgenerator/listener_generator_test.go b/src/go/configgenerator/listener_generator_test.go index bc382ff2b..d8e4fed8c 100644 --- a/src/go/configgenerator/listener_generator_test.go +++ b/src/go/configgenerator/listener_generator_test.go @@ -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" @@ -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" diff --git a/src/go/configmanager/testdata/test_fetch_listeners.go b/src/go/configmanager/testdata/test_fetch_listeners.go index 65aaa12f0..8598e1fd3 100644 --- a/src/go/configmanager/testdata/test_fetch_listeners.go +++ b/src/go/configmanager/testdata/test_fetch_listeners.go @@ -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" @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", diff --git a/src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go b/src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go index 22421b532..36756795d 100644 --- a/src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go +++ b/src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go @@ -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", diff --git a/src/go/util/marshal.go b/src/go/util/marshal.go index 668d36314..d383ec926 100644 --- a/src/go/util/marshal.go +++ b/src/go/util/marshal.go @@ -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" @@ -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": diff --git a/src/go/util/xds_name.go b/src/go/util/xds_name.go index dcd1d23f6..880c9e366 100644 --- a/src/go/util/xds_name.go +++ b/src/go/util/xds_name.go @@ -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" diff --git a/tests/env/platform/ports.go b/tests/env/platform/ports.go index 2480b2865..4b6050ef2 100644 --- a/tests/env/platform/ports.go +++ b/tests/env/platform/ports.go @@ -34,6 +34,7 @@ const ( TestAuthAllowMissing TestAuthJwksAsyncFetch TestAuthJwksCache + TestAuthWithMethodOverride TestBackendAddressOverride TestBackendAuthDisableAuth TestBackendAuthPerPlatform diff --git a/tests/integration_test/http1_integration_test/http1_integration_test.go b/tests/integration_test/http1_integration_test/http1_integration_test.go index d7f4cae9f..2906780dd 100644 --- a/tests/integration_test/http1_integration_test/http1_integration_test.go +++ b/tests/integration_test/http1_integration_test/http1_integration_test.go @@ -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)) + } } - } + }) } } diff --git a/tests/integration_test/jwt_auth_integration_test/jwt_auth_integration_test.go b/tests/integration_test/jwt_auth_integration_test/jwt_auth_integration_test.go index a90e2b513..886a5bc9e 100644 --- a/tests/integration_test/jwt_auth_integration_test/jwt_auth_integration_test.go +++ b/tests/integration_test/jwt_auth_integration_test/jwt_auth_integration_test.go @@ -17,6 +17,7 @@ package jwt_auth_integration_test import ( "encoding/json" "fmt" + "net/http" "strings" "testing" "time" @@ -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) + } + } + }) + } +}