Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

router: Allow CORS to be configured using typed_per_filter_config #9324

Closed
wants to merge 14 commits into from
1 change: 1 addition & 0 deletions api/docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ proto_library(
"//envoy/config/filter/fault/v2:pkg",
"//envoy/config/filter/http/adaptive_concurrency/v2alpha:pkg",
"//envoy/config/filter/http/buffer/v2:pkg",
"//envoy/config/filter/http/cors/v2:pkg",
"//envoy/config/filter/http/csrf/v2:pkg",
"//envoy/config/filter/http/dynamic_forward_proxy/v2alpha:pkg",
"//envoy/config/filter/http/ext_authz/v2:pkg",
Expand Down
20 changes: 16 additions & 4 deletions api/envoy/api/v2/route/route.proto
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,14 @@ message VirtualHost {
// handled by this virtual host.
repeated string response_headers_to_remove = 11;

// Indicates that the virtual host has a CORS policy.
CorsPolicy cors = 8;
// Indicates that the virtual host has a CORS policy. This field is ignored if a
// `typed_per_filter_config` for the CORS filter is found.
//
// .. attention::
//
// This option has been deprecated. Please use `typed_per_filter_config` to configure
// the CORS HTTP filter.
CorsPolicy cors = 8 [deprecated = true];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a note in deprecated.rst, same below


// The per_filter_config field can be used to provide virtual host-specific
// configurations for filters. The key should match the filter name, such as
Expand Down Expand Up @@ -839,8 +845,14 @@ message RouteAction {
// ignoring the rest of the hash policy list.
repeated HashPolicy hash_policy = 15;

// Indicates that the route has a CORS policy.
CorsPolicy cors = 17;
// Indicates that the route has a CORS policy. This field is ignored if a
// `typed_per_filter_config` for the CORS filter is found.
//
// .. attention::
//
// This option has been deprecated. Please use `typed_per_filter_config` to configure
// the CORS HTTP filter.
CorsPolicy cors = 17 [deprecated = true];

// If present, and the request is a gRPC request, use the
// `grpc-timeout header <https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md>`_,
Expand Down
14 changes: 5 additions & 9 deletions api/envoy/api/v3alpha/route/route.proto
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ message VirtualHost {
ALL = 2;
}

reserved 9, 12;
reserved 9, 8, 12;

reserved "per_filter_config";
reserved "cors", "per_filter_config";

// The logical name of the virtual host. This is used when emitting certain
// statistics but is not relevant for routing.
Expand Down Expand Up @@ -116,9 +116,6 @@ message VirtualHost {
// handled by this virtual host.
repeated string response_headers_to_remove = 11;

// Indicates that the virtual host has a CORS policy.
CorsPolicy cors = 8;

// The per_filter_config field can be used to provide virtual host-specific
// configurations for filters. The key should match the filter name, such as
// *envoy.buffer* for the HTTP buffer filter. Use of this field is filter
Expand Down Expand Up @@ -661,7 +658,9 @@ message RouteAction {
google.protobuf.BoolValue enabled = 2;
}

reserved 12, 18, 19, 16, 22, 21;
reserved 12, 18, 19, 16, 22, 21, 17;

reserved "cors";

oneof cluster_specifier {
option (validate.required) = true;
Expand Down Expand Up @@ -822,9 +821,6 @@ message RouteAction {
// ignoring the rest of the hash policy list.
repeated HashPolicy hash_policy = 15;

// Indicates that the route has a CORS policy.
CorsPolicy cors = 17;

// If present, and the request is a gRPC request, use the
// `grpc-timeout header <https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md>`_,
// or its default value (infinity) instead of
Expand Down
12 changes: 12 additions & 0 deletions api/envoy/config/filter/http/cors/v2/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DO NOT EDIT. This file is generated by tools/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/api/v2/core:pkg",
"//envoy/type/matcher:pkg",
],
)
60 changes: 60 additions & 0 deletions api/envoy/config/filter/http/cors/v2/cors.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
syntax = "proto3";

package envoy.config.filter.http.cors.v2;

option java_package = "io.envoyproxy.envoy.config.filter.http.cors.v2";
option java_outer_classname = "CorsProto";
option java_multiple_files = true;

import "envoy/api/v2/core/base.proto";
import "envoy/type/matcher/string.proto";

import "google/protobuf/wrappers.proto";

import "validate/validate.proto";

// [#protodoc-title: Cors]
// Cors :ref:`configuration overview <config_http_filters_cors>`.
// [#extension: envoy.filters.http.cors]

// [#next-free-field: 9]
message PerRouteCorsPolicy {
// Specifies string patterns that match allowed origins. An origin is allowed if any of the
// string matchers match.
repeated type.matcher.StringMatcher allow_origin_string_match = 1;

// Specifies the content for the *access-control-allow-methods* header.
string allow_methods = 2;

// Specifies the content for the *access-control-allow-headers* header.
string allow_headers = 3;

// Specifies the content for the *access-control-expose-headers* header.
string expose_headers = 4;

// Specifies the content for the *access-control-max-age* header.
string max_age = 5;

// Specifies whether the resource allows credentials.
google.protobuf.BoolValue allow_credentials = 6;

// Specifies the % of requests for which the CORS filter is enabled.
//
// If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS
// filter will be enabled for 100% of the requests.
//
// If :ref:`runtime_key <envoy_api_field_core.runtimefractionalpercent.runtime_key>` is
// specified, Envoy will lookup the runtime key to get the percentage of requests to filter.
api.v2.core.RuntimeFractionalPercent filter_enabled = 7;

// Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not
// enforced.
//
// This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those
// fields have to explicitly disable the filter in order for this setting to take effect.
//
// If :ref:`runtime_key <envoy_api_field_core.runtimefractionalpercent.runtime_key>` is specified,
// Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate
// and track the request's *Origin* to determine if it's valid but will not enforce any policies.
api.v2.core.RuntimeFractionalPercent shadow_enabled = 8;
}
14 changes: 14 additions & 0 deletions api/envoy/config/filter/http/cors/v3alpha/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# DO NOT EDIT. This file is generated by tools/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/api/v3alpha/core:pkg",
"//envoy/config/filter/http/cors/v2:pkg",
"//envoy/type/matcher/v3alpha:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
65 changes: 65 additions & 0 deletions api/envoy/config/filter/http/cors/v3alpha/cors.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
syntax = "proto3";

package envoy.config.filter.http.cors.v3alpha;

option java_package = "io.envoyproxy.envoy.config.filter.http.cors.v3alpha";
option java_outer_classname = "CorsProto";
option java_multiple_files = true;

import "envoy/api/v3alpha/core/base.proto";
import "envoy/type/matcher/v3alpha/string.proto";

import "google/protobuf/wrappers.proto";

import "udpa/annotations/versioning.proto";

import "validate/validate.proto";

// [#protodoc-title: Cors]
// Cors :ref:`configuration overview <config_http_filters_cors>`.
// [#extension: envoy.filters.http.cors]

// [#next-free-field: 9]
message PerRouteCorsPolicy {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.cors.v2.PerRouteCorsPolicy";

// Specifies string patterns that match allowed origins. An origin is allowed if any of the
// string matchers match.
repeated type.matcher.v3alpha.StringMatcher allow_origin_string_match = 1;

// Specifies the content for the *access-control-allow-methods* header.
string allow_methods = 2;

// Specifies the content for the *access-control-allow-headers* header.
string allow_headers = 3;

// Specifies the content for the *access-control-expose-headers* header.
string expose_headers = 4;

// Specifies the content for the *access-control-max-age* header.
string max_age = 5;

// Specifies whether the resource allows credentials.
google.protobuf.BoolValue allow_credentials = 6;

// Specifies the % of requests for which the CORS filter is enabled.
//
// If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS
// filter will be enabled for 100% of the requests.
//
// If :ref:`runtime_key <envoy_api_field_core.runtimefractionalpercent.runtime_key>` is
// specified, Envoy will lookup the runtime key to get the percentage of requests to filter.
api.v3alpha.core.RuntimeFractionalPercent filter_enabled = 7;

// Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not
// enforced.
//
// This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those
// fields have to explicitly disable the filter in order for this setting to take effect.
//
// If :ref:`runtime_key <envoy_api_field_core.runtimefractionalpercent.runtime_key>` is specified,
// Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate
// and track the request's *Origin* to determine if it's valid but will not enforce any policies.
api.v3alpha.core.RuntimeFractionalPercent shadow_enabled = 8;
}
1 change: 1 addition & 0 deletions api/test/validate/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ api_cc_test(
"//envoy/config/bootstrap/v2:pkg_cc_proto",
"//envoy/config/filter/accesslog/v2:pkg_cc_proto",
"//envoy/config/filter/http/buffer/v2:pkg_cc_proto",
"//envoy/config/filter/http/cors/v2:pkg_cc_proto",
"//envoy/config/filter/http/fault/v2:pkg_cc_proto",
"//envoy/config/filter/http/gzip/v2:pkg_cc_proto",
"//envoy/config/filter/http/header_to_metadata/v2:pkg_cc_proto",
Expand Down
1 change: 1 addition & 0 deletions api/test/validate/pgv_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "envoy/config/health_checker/redis/v2/redis.pb.validate.h"
#include "envoy/config/filter/accesslog/v2/accesslog.pb.validate.h"
#include "envoy/config/filter/http/buffer/v2/buffer.pb.validate.h"
#include "envoy/config/filter/http/cors/v2/cors.pb.validate.h"
#include "envoy/config/filter/http/fault/v2/fault.pb.validate.h"
#include "envoy/config/filter/http/gzip/v2/gzip.pb.validate.h"
#include "envoy/config/filter/http/health_check/v2/health_check.pb.validate.h"
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Version history
* buffer: remove old implementation
* build: official released binary is now built against libc++.
* cluster: added :ref: `aggregate cluster <arch_overview_aggregate_cluster>` that allows load balancing between clusters.
* cors: allow CORS filter to be configured in routes at the :ref:`virtual host level<envoy_api_field_route.VirtualHost.per_filter_config>` and :ref:`route level<envoy_api_field_route.Route.per_filter_config>`.
* decompressor: remove decompressor hard assert failure and replace with an error flag.
* ext_authz: added :ref:`configurable ability<envoy_api_field_config.filter.http.ext_authz.v2.ExtAuthz.include_peer_certificate>` to send the :ref:`certificate<envoy_api_field_service.auth.v2.AttributeContext.Peer.certificate>` to the `ext_authz` service.
* health check: gRPC health checker sets the gRPC deadline to the configured timeout duration.
Expand Down
24 changes: 12 additions & 12 deletions include/envoy/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,21 @@ class DirectResponseEntry : public ResponseEntry {
virtual const std::string& routeName() const PURE;
};

/**
* All route specific config returned by the method at
* NamedHttpFilterConfigFactory::createRouteSpecificFilterConfig
* should be derived from this class.
*/
class RouteSpecificFilterConfig {
public:
virtual ~RouteSpecificFilterConfig() = default;
};
using RouteSpecificFilterConfigConstSharedPtr = std::shared_ptr<const RouteSpecificFilterConfig>;

/**
* CorsPolicy for Route and VirtualHost.
*/
class CorsPolicy {
class CorsPolicy : public RouteSpecificFilterConfig {
public:
virtual ~CorsPolicy() = default;

Expand Down Expand Up @@ -380,17 +391,6 @@ class VirtualCluster {
class RateLimitPolicy;
class Config;

/**
* All route specific config returned by the method at
* NamedHttpFilterConfigFactory::createRouteSpecificFilterConfig
* should be derived from this class.
*/
class RouteSpecificFilterConfig {
public:
virtual ~RouteSpecificFilterConfig() = default;
};
using RouteSpecificFilterConfigConstSharedPtr = std::shared_ptr<const RouteSpecificFilterConfig>;

/**
* Virtual host definition.
*/
Expand Down
10 changes: 10 additions & 0 deletions source/common/http/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,16 @@ Utility::resolveMostSpecificPerFilterConfigGeneric(const std::string& filter_nam
return maybe_filter_config;
}

std::vector<const Router::RouteSpecificFilterConfig*>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perf nit: you might consider either an std::array of size 3 here, given that I think we know there can be a max of 3 entries? Or potentially for easier programming an absl::InlineVector of size 3 default storage?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh, absl::InlineVector looks pretty cool. Although since we're holding pointers, callers should ideally be verifying non-nullptr anyways (as the cors filter does) so I think std::array would suffice

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still need to finish addressing this before final review

Utility::resolveAllPerFilterConfigGeneric(const std::string& filter_name,
const Router::RouteConstSharedPtr& route) {
std::vector<const Router::RouteSpecificFilterConfig*> configs{};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: {} not needed

traversePerFilterConfigGeneric(
filter_name, route,
[&configs](const Router::RouteSpecificFilterConfig& cfg) { configs.push_back(&cfg); });
return configs;
}

void Utility::traversePerFilterConfigGeneric(
const std::string& filter_name, const Router::RouteConstSharedPtr& route,
std::function<void(const Router::RouteSpecificFilterConfig&)> cb) {
Expand Down
54 changes: 53 additions & 1 deletion source/common/http/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <chrono>
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>

#include "envoy/api/v2/core/http_uri.pb.h"
#include "envoy/api/v2/core/protocol.pb.h"
Expand Down Expand Up @@ -310,7 +312,7 @@ resolveMostSpecificPerFilterConfigGeneric(const std::string& filter_name,
const Router::RouteConstSharedPtr& route);

/**
* Retrieves the route specific config. Route specific config can be in a few
* Retrieves the route specific config. Route specific configs can be in a few
* places, that are checked in order. The first config found is returned. The
* order is:
* - the routeEntry() (for config that's applied on weighted clusters)
Expand Down Expand Up @@ -343,6 +345,56 @@ const ConfigType* resolveMostSpecificPerFilterConfig(const std::string& filter_n
return dynamic_cast<const ConfigType*>(generic_config);
}

/**
* The non template implementation of resolveAllPerFilterConfigGeneric. see
* resolveAllPerFilterConfigGeneric for docs.
*/
std::vector<const Router::RouteSpecificFilterConfig*>
resolveAllPerFilterConfigGeneric(const std::string& filter_name,
const Router::RouteConstSharedPtr& route);

/**
* Retrieves all route specific configs. Route specific configs can be in a few
* places, that are checked in order. All configs that are found are returned. The
* order is:
* - the routeEntry() (for config that's applied on weighted clusters)
* - the route
* - and finally from the virtual host object (routeEntry()->virtualhost()).
*
* To use, simply:
*
* const auto* configs =
* Utility::resolveAllPerFilterConfigGeneric<ConcreteType>(FILTER_NAME,
* stream_callbacks_.route());
*
* See notes about config's lifetime below.
*
* @param filter_name The name of the filter who's route config should be
* fetched.
* @param route The route to check for route configs. nullptr routes will
* result in an empty vector being returned.
*
* @return A vector of pointers of route configs that were found. An empty vector if no configs were
* found. The lifetime of the pointers in the vector is the same as the corresponding route
* parameter.
*/
template <class ConfigType>
std::vector<const ConfigType*>
resolveAllPerFilterConfigGeneric(const std::string& filter_name,
const Router::RouteConstSharedPtr& route) {
static_assert(std::is_base_of<Router::RouteSpecificFilterConfig, ConfigType>::value,
"ConfigType must be a subclass of Router::RouteSpecificFilterConfig");
std::vector<const Router::RouteSpecificFilterConfig*> generic_configs =
resolveAllPerFilterConfigGeneric(filter_name, route);

std::vector<const ConfigType*> typed_configs;
typed_configs.reserve(generic_configs.size());
for (const auto& generic_config : generic_configs) {
typed_configs.push_back(dynamic_cast<const ConfigType*>(generic_config));
}
return typed_configs;
}

/**
* The non template implementation of traversePerFilterConfig. see
* traversePerFilterConfig for docs.
Expand Down
Loading