CVE-2023-30845: Improper Authentication (CWE-287)
value: ESPv2 is a service proxy that provides API management capabilities using Google Service Infrastructure. ESPv2 2.20.0 through 2.42.0 contains an authentication bypass vulnerability. API clients can craft a malicious `X-HTTP-Method-Override` header value to bypass JWT authentication in specific cases.

ESPv2 allows malicious requests to bypass authentication if both the conditions are true: The requested HTTP method is **not** in the API service definition (OpenAPI spec or gRPC `google.api.http` proto annotations, and the specified `X-HTTP-Method-Override` is a valid HTTP method in the API service definition. ESPv2 will forward the request to your backend without checking the JWT. Attackers can craft requests with a malicious `X-HTTP-Method-Override` value that allows them to bypass specifying JWTs. Restricting API access with API keys works as intended and is not affected by this vulnerability.

Upgrade deployments to release v2.43.0 or higher to receive a patch. This release ensures that JWT authentication occurs, even when the caller specifies `x-http-method-override`. `x-http-method-override` is still supported by v2.43.0+. API clients can continue sending this header to ESPv2.
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 9.8
Published: 2023-04-26T21:15Z
Repository: https://github.com/GoogleCloudPlatform/esp-v2
Language: Go
Files changed: api/envoy/v11/http/header_sanitizer/BUILD, api/envoy/v11/http/header_sanitizer/config.proto, api/scripts/go_proto_gen.sh, examples/auth/envoy_config.json, examples/dynamic_routing/envoy_config.json, examples/grpc_dynamic_routing/envoy_config.json, examples/service_control/envoy_config.json, examples/testdata/route_match/envoy_config.json, examples/testdata/sidecar_backend/envoy_config.json, src/envoy/BUILD, src/envoy/http/header_sanitizer/BUILD, src/envoy/http/header_sanitizer/filter.cc, src/envoy/http/header_sanitizer/filter.h, src/envoy/http/header_sanitizer/filter_factory.cc, src/envoy/http/service_control/filter.cc, src/go/configgenerator/filter_generator.go, src/go/configgenerator/filtergen/header_sanitizer.go, src/go/configgenerator/listener_generator_test.go, src/go/configmanager/testdata/test_fetch_listeners.go, src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go, src/go/util/marshal.go, src/go/util/xds_name.go, tests/env/platform/ports.go, tests/integration_test/http1_integration_test/http1_integration_test.go, tests/integration_test/jwt_auth_integration_test/jwt_auth_integration_test.go
Commit date: 2023-03-27 23:24:44 -0500
Commit message: move x-http-method-override to beginning of filter chain (#801)

Create a new filter to handle method override. Tested via integration test.
Diff: diff --git a/api/envoy/v11/http/header_sanitizer/BUILD b/api/envoy/v11/http/header_sanitizer/BUILD
new file mode 100644
index 00000000..c1c9e49d
--- /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 00000000..3a18c0fb
--- /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 115331b7..8057ac36 100755
--- a/api/scripts/go_proto_gen.sh
+++ b/api/scripts/go_proto_gen.sh
@@ -40,5 +40,9 @@ mkdir -p src/go/proto/api/envoy/v11/http/backend_auth
 cp -f bazel-bin/api/envoy/v11/http/backend_auth/config_go_proto_/github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/backend_auth/* src/go/proto/api/envoy/v11/http/backend_auth
 # HTTP filter grpc_metadata_scrubber
 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 14f0c276..48da3613 100644
--- a/examples/auth/envoy_config.json
+++ b/examples/auth/envoy_config.json
@@ -161,10 +161,16 @@
                   "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                   "commonHttpProtocolOptions": {
                     "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": {
                         "@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
                         "providers": {
diff --git a/examples/dynamic_routing/envoy_config.json b/examples/dynamic_routing/envoy_config.json
index a96e9143..44f3b7ab 100644
--- a/examples/dynamic_routing/envoy_config.json
+++ b/examples/dynamic_routing/envoy_config.json
@@ -216,10 +216,16 @@
                   "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                   "commonHttpProtocolOptions": {
                     "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",
                         "depErrorBehavior": "BLOCK_INIT_ON_ANY_ERROR",
diff --git a/examples/grpc_dynamic_routing/envoy_config.json b/examples/grpc_dynamic_routing/envoy_config.json
index 61465838..700bbf58 100644
--- a/examples/grpc_dynamic_routing/envoy_config.json
+++ b/examples/grpc_dynamic_routing/envoy_config.json
@@ -229,10 +229,16 @@
                   "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                   "commonHttpProtocolOptions": {
                     "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": {
                         "@type": "type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor",
                         "compressorLibrary": {
diff --git a/examples/service_control/envoy_config.json b/examples/service_control/envoy_config.json
index ccdadc3f..83d91905 100644
--- a/examples/service_control/envoy_config.json
+++ b/examples/service_control/envoy_config.json
@@ -122,10 +122,16 @@
                   "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                   "commonHttpProtocolOptions": {
                     "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": {
                         "@type": "type.googleapis.com/espv2.api.envoy.v11.http.service_control.FilterConfig",
                         "depErrorBehavior": "BLOCK_INIT_ON_ANY_ERROR",
diff --git a/examples/testdata/route_match/envoy_config.json b/examples/testdata/route_match/envoy_config.json
index d70a9207..8a474923 100644
--- a/examples/testdata/route_match/envoy_config.json
+++ b/examples/testdata/route_match/envoy_config.json
@@ -200,10 +200,16 @@
                   "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                   "commonHttpProtocolOptions": {
                     "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",
                         "depErrorBehavior": "BLOCK_INIT_ON_ANY_ERROR",
diff --git a/examples/testdata/sidecar_backend/envoy_config.json b/examples/testdata/sidecar_backend/envoy_config.json
index 69e35df8..d0746198 100644
--- a/examples/testdata/sidecar_backend/envoy_config.json
+++ b/examples/testdata/sidecar_backend/envoy_config.json
@@ -122,10 +122,16 @@
                   "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                   "commonHttpProtocolOptions": {
                     "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": {
                         "@type": "type.googleapis.com/espv2.api.envoy.v11.http.grpc_metadata_scrubber.FilterConfig"
                       }
diff --git a/src/envoy/BUILD b/src/envoy/BUILD
index 49db0667..5ddf75e5 100644
--- a/src/envoy/BUILD
+++ b/src/envoy/BUILD
@@ -15,10 +15,15 @@ alias(
 alias(
     name = "grpc_metadata_scrubber",
     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",
 )
 
@@ -36,10 +41,11 @@ envoy_cc_binary(
     name = "envoy",
     repository = "@envoy",
     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 00000000..d971ed4a
--- /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 00000000..63eac4b0
--- /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 <string>
+
+#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 00000000..c4a7f584
--- /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<Envoy::Logger::Id::filter> {
+ 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 00000000..7fb39bf9
--- /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<Filter>();
+      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 cde4d715..a2e5d9d2 100644
--- a/src/envoy/http/service_control/filter.cc
+++ b/src/envoy/http/service_control/filter.cc
@@ -51,17 +51,10 @@ Envoy::Http::FilterHeadersStatus ServiceControlFilter::decodeHeaders(
                                            utils::kRcDetailErrorTypeBadRequest,
                                            utils::kRcDetailErrorMissingPath));
     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();
 
   // This shouldn't happen as the catch-all route match should catch all
   // the undefined requests.
diff --git a/src/go/configgenerator/filter_generator.go b/src/go/configgenerator/filter_generator.go
index 3957fcf6..602aaede 100644
--- a/src/go/configgenerator/filter_generator.go
+++ b/src/go/configgenerator/filter_generator.go
@@ -47,10 +47,11 @@ 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
 		// filter needs to get the corresponding rule for health check in order to skip Report
 		filtergen.NewHealthCheckGenerator(serviceInfo),
diff --git a/src/go/configgenerator/filtergen/header_sanitizer.go b/src/go/configgenerator/filtergen/header_sanitizer.go
new file mode 100644
index 00000000..d862bfa0
--- /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 bc382ff2..d8e4fed8 100644
--- a/src/go/configgenerator/listener_generator_test.go
+++ b/src/go/configgenerator/listener_generator_test.go
@@ -77,11 +77,17 @@ func TestMakeListeners(t *testing.T) {
           "name": "envoy.filters.network.http_connection_manager",
           "typedConfig": {
             "@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"
                 }
               },
@@ -232,11 +238,17 @@ func TestMakeListeners(t *testing.T) {
           "name": "envoy.filters.network.http_connection_manager",
           "typedConfig": {
             "@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 65aaa12f..8598e1fd 100644
--- a/src/go/configmanager/testdata/test_fetch_listeners.go
+++ b/src/go/configmanager/testdata/test_fetch_listeners.go
@@ -90,11 +90,17 @@ var (
             "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
             "commonHttpProtocolOptions": {
               "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"
                 }
               },
@@ -314,11 +320,17 @@ var (
         {
           "name": "envoy.filters.network.http_connection_manager",
           "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",
                   "requirementMap": {
                     "endpoints.examples.bookstore.Bookstore.CreateShelf": {
@@ -602,11 +614,17 @@ var (
         {
           "name": "envoy.filters.network.http_connection_manager",
           "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",
                   "requirementMap": {
                     "endpoints.examples.bookstore.Bookstore.CreateShelf": {
@@ -1114,11 +1132,17 @@ var (
         {
           "name": "envoy.filters.network.http_connection_manager",
           "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",
                   "requirementMap": {
                     "endpoints.examples.bookstore.Bookstore.GetBook": {
@@ -1585,11 +1609,17 @@ var (
         {
           "name": "envoy.filters.network.http_connection_manager",
           "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",
                   "imdsToken": {
                     "cluster": "metadata-cluster",
@@ -2120,11 +2150,17 @@ var (
         {
           "name": "envoy.filters.network.http_connection_manager",
           "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",
                   "requirementMap": {
                     "1.echo_api_endpoints_cloudesf_testing_cloud_goog.Echo_Auth_Jwt": {
@@ -2452,11 +2488,17 @@ var (
         {
           "name": "envoy.filters.network.http_connection_manager",
           "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",
                   "imdsToken": {
                     "cluster": "metadata-cluster",
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 22421b53..36756795 100644
--- a/src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go
+++ b/src/go/configmanager/testdata/test_fixed_mode_dynamic_routing.go
@@ -288,11 +288,17 @@ var (
             "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
             "commonHttpProtocolOptions": {
               "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",
                   "depErrorBehavior": "BLOCK_INIT_ON_ANY_ERROR",
                   "imdsToken": {
diff --git a/src/go/util/marshal.go b/src/go/util/marshal.go
index 668d3631..d383ec92 100644
--- a/src/go/util/marshal.go
+++ b/src/go/util/marshal.go
@@ -21,10 +21,11 @@ import (
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/golang/protobuf/proto"
 
 	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"
 
 	listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	statspb "github.com/envoyproxy/go-control-plane/envoy/config/metrics/v3"
@@ -115,10 +116,12 @@ var Resolver = FuncResolver(func(url string) (proto.Message, error) {
 		return new(bapb.PerRouteFilterConfig), nil
 	case "type.googleapis.com/espv2.api.envoy.v11.http.backend_auth.FilterConfig":
 		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":
 		return new(tlspb.UpstreamTlsContext), nil
 	case "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog":
diff --git a/src/go/util/xds_name.go b/src/go/util/xds_name.go
index dcd1d23f..880c9e36 100644
--- a/src/go/util/xds_name.go
+++ b/src/go/util/xds_name.go
@@ -56,10 +56,12 @@ const (
 	PathRewrite = "com.google.espv2.filters.http.path_rewrite"
 	// BackendAuth filter.
 	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"
 
 	// The token agent server cluster name.
diff --git a/tests/env/platform/ports.go b/tests/env/platform/ports.go
index 2480b286..4b6050ef 100644
--- a/tests/env/platform/ports.go
+++ b/tests/env/platform/ports.go
@@ -32,10 +32,11 @@ const (
 	TestAddHeaders
 	TestAsymmetricKeys
 	TestAuthAllowMissing
 	TestAuthJwksAsyncFetch
 	TestAuthJwksCache
+	TestAuthWithMethodOverride
 	TestBackendAddressOverride
 	TestBackendAuthDisableAuth
 	TestBackendAuthPerPlatform
 	TestBackendAuthUsingIamIdTokenWithDelegates
 	TestBackendAuthWithIamIdToken
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 d7f4cae9..2906780d 100644
--- a/tests/integration_test/http1_integration_test/http1_integration_test.go
+++ b/tests/integration_test/http1_integration_test/http1_integration_test.go
@@ -166,20 +166,22 @@ func TestHttp1JWT(t *testing.T) {
 			token:       testdata.FakeCloudTokenSingleAudience1,
 			wantedError: "403 Forbidden",
 		},
 	}
 	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))
+				}
 			}
-		}
+		})
 	}
 }
 
 func TestDisableJwtServiceNameCheckFlag(t *testing.T) {
 	t.Parallel()
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 a90e2b51..886a5bc9 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
@@ -15,10 +15,11 @@
 package jwt_auth_integration_test
 
 import (
 	"encoding/json"
 	"fmt"
+	"net/http"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/GoogleCloudPlatform/esp-v2/src/go/util"
@@ -488,5 +489,68 @@ 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)
+				}
+			}
+		})
+	}
+}

Vulnerable code:   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();
  }

              {
              {
              {
              {
              {
              {
              {
              {
              {
              {
		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))
		}
Fixed code: 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",
)
// 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 {}
# 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
                    {
                      "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.header_sanitizer",
                      "typedConfig": {
                        "@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
                      }
                    },
                    {
                      "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.header_sanitizer",
                      "typedConfig": {
                        "@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
                      }
                    },
                    {
                      "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.header_sanitizer",
                      "typedConfig": {
                        "@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
                      }
                    },
alias(
    name = "header_sanitizer",
    actual = "//src/envoy/http/header_sanitizer:filter_factory",
)

        ":header_sanitizer",
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",
    ],
)
// 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 <string>

#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
// 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<Envoy::Logger::Id::filter> {
 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
// 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<Filter>();
      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
		&filtergen.HeaderSanitizerGenerator{},
// 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
}
							{
								"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.header_sanitizer",
								"typedConfig": {
									"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
								}
							},
							{
							{
								"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.header_sanitizer",
								"typedConfig": {
									"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
								}
							},
							{
							{
								"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.header_sanitizer",
								"typedConfig": {
									"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
								}
							},
							{
							{
								"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.header_sanitizer",
								"typedConfig": {
									"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
								}
							},
							{
							{
								"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.header_sanitizer",
								"typedConfig": {
									"@type": "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig"
								}
							},
							{
	hspb "github.com/GoogleCloudPlatform/esp-v2/src/go/proto/api/envoy/v11/http/header_sanitizer"
	case "type.googleapis.com/espv2.api.envoy.v11.http.header_sanitizer.FilterConfig":
		return new(hspb.FilterConfig), nil
	// HeaderSanitizerScrubber is the filter name (matches c++ factory).
	HeaderSanitizerScrubber = "com.google.espv2.filters.http.header_sanitizer"
	TestAuthWithMethodOverride
		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("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))
				}
		})
	"net/http"

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