CVE-2023-30845 (published 2023-04-26T21:15Z)

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.\n\nESPv2 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.\n\nUpgrade 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.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

Fix information:
repo_url: https://github.com/GoogleCloudPlatform/esp-v2
commit_details: repo_url: https://github.com/GoogleCloudPlatform/esp-v2
author: Teju Nareddy
author_date: 2023-03-27 23:24:44-05:00
msg: move x-http-method-override to beginning of filter chain (#801)

Create a new filter to handle method override. Tested via integration test.
repository: repo_url: https://github.com/GoogleCloudPlatform/esp-v2
repo_name: GoogleCloudPlatform/esp-v2
description: A service proxy that provides API management capabilities using Google Service Infrastructure.
repo_language: Go
owner: GoogleCloudPlatform
file_changes: filename: BUILD
change_type: ModificationType.ADD
diff: @@ -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",
+)

programming_language: Ruby
filename: config.proto
change_type: ModificationType.ADD
diff: @@ -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 {}

programming_language: Go
filename: go_proto_gen.sh
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Shell
filename: envoy_config.json
change_type: ModificationType.MODIFY
diff: @@ -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": {

programming_language: TypeScript
filename: envoy_config.json
change_type: ModificationType.MODIFY
diff: @@ -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": {

programming_language: TypeScript
filename: envoy_config.json
change_type: ModificationType.MODIFY
diff: @@ -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": {

programming_language: TypeScript
filename: envoy_config.json
change_type: ModificationType.MODIFY
diff: @@ -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": {

programming_language: TypeScript
filename: envoy_config.json
change_type: ModificationType.MODIFY
diff: @@ -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": {

programming_language: TypeScript
filename: envoy_config.json
change_type: ModificationType.MODIFY
diff: @@ -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": {

programming_language: Swift
filename: BUILD
change_type: ModificationType.MODIFY
diff: @@ -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",

programming_language: Lua
filename: BUILD
change_type: ModificationType.ADD
diff: @@ -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",
+    ],
+)

programming_language: Ruby
filename: filter.cc
change_type: ModificationType.ADD
diff: @@ -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

programming_language: C++
filename: filter.h
change_type: ModificationType.ADD
diff: @@ -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

programming_language: C++
filename: filter_factory.cc
change_type: ModificationType.ADD
diff: @@ -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

programming_language: C++
filename: filter.cc
change_type: ModificationType.MODIFY
diff: @@ -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();
 

programming_language: C++
filename: filter_generator.go
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Go
filename: header_sanitizer.go
change_type: ModificationType.ADD
diff: @@ -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
+}

programming_language: Go
filename: listener_generator_test.go
change_type: ModificationType.MODIFY
diff: @@ -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"

programming_language: Go
filename: test_fetch_listeners.go
change_type: ModificationType.MODIFY
diff: @@ -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",

programming_language: Go
filename: test_fixed_mode_dynamic_routing.go
change_type: ModificationType.MODIFY
diff: @@ -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",

programming_language: Go
filename: marshal.go
change_type: ModificationType.MODIFY
diff: @@ -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":

programming_language: Go
filename: xds_name.go
change_type: ModificationType.MODIFY
diff: @@ -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"

programming_language: Go
filename: ports.go
change_type: ModificationType.MODIFY
diff: @@ -34,6 +34,7 @@ const (
 	TestAuthAllowMissing
 	TestAuthJwksAsyncFetch
 	TestAuthJwksCache
+	TestAuthWithMethodOverride
 	TestBackendAddressOverride
 	TestBackendAuthDisableAuth
 	TestBackendAuthPerPlatform

programming_language: Go
filename: http1_integration_test.go
change_type: ModificationType.MODIFY
diff: @@ -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))
+				}
 			}
-		}
+		})
 	}
 }
 

programming_language: Go
filename: jwt_auth_integration_test.go
change_type: ModificationType.MODIFY
diff: @@ -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)
+				}
+			}
+		})
+	}
+}

programming_language: Go
repo_url: https://github.com/GoogleCloudPlatform/esp-v2
commit_details: repo_url: https://github.com/GoogleCloudPlatform/esp-v2
author: Teju Nareddy
author_date: 2023-03-28 12:09:59-05:00
msg: Add back in HTTP method override in service control filter (#802)

This is NOT used by ESPv2, but it is needed for CAG temporarily. We can remove once their rollout is complete.

Signed-off-by: Teju Nareddy <nareddyt@google.com>
repository: repo_url: https://github.com/GoogleCloudPlatform/esp-v2
repo_name: GoogleCloudPlatform/esp-v2
description: A service proxy that provides API management capabilities using Google Service Infrastructure.
repo_language: Go
owner: GoogleCloudPlatform
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -12,8 +12,8 @@ When making changes to the config proto files, make sure:
 * No breaking changes, the changes should be backward compatible,
 * If a breaking change is required, increase config version.
 
-When making changes to Config Manager, make sure the new config is compatible
-with older Envoy binaries under the current API version. If it's incompatible,
+When making changes to Config Manager, make sure that **older** config generator binaries are compatible
+with **newer** Envoy binaries under the current API version. If it's incompatible,
 increase the config version.
 
 ## Steps to increase config version

programming_language: Markdown
filename: filter.cc
change_type: ModificationType.MODIFY
diff: @@ -53,6 +53,14 @@ Envoy::Http::FilterHeadersStatus ServiceControlFilter::decodeHeaders(
     return Envoy::Http::FilterHeadersStatus::StopIteration;
   }
 
+  // TODO(b/273531500): Temporary until CAG rollout is complete.
+  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();
 

programming_language: C++