From 08b641b54ea0f9be0c564e733acf5a29e6817d9e Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Fri, 5 Feb 2021 14:56:27 +0900 Subject: [PATCH 01/28] docs: 1.17.1 kickoff (#14824) * docs: kickoff 1.17.1 Signed-off-by: Shikugawa * add version history Signed-off-by: Shikugawa * add versionversion Signed-off-by: Shikugawa * master -> main Signed-off-by: Shikugawa * fix Signed-off-by: Shikugawa * fix Signed-off-by: Shikugawa * fix kafka server source Signed-off-by: Shikugawa * deps: Add more SHAs to configs and kafka requirements.txt (#14887) Signed-off-by: Dhi Aurrahman Signed-off-by: Shikugawa * conflict Signed-off-by: yanavlasov * examples: test/fix websocket ci flake (#14941) switch to 127.0.0.1 from localhost in ws example tests as this seeems to make ci <> websocat flakey Signed-off-by: Ryan Northey Signed-off-by: Shikugawa Co-authored-by: Dhi Aurrahman Co-authored-by: yanavlasov Co-authored-by: phlax --- .github/workflows/get_build_targets.sh | 2 +- VERSION | 2 +- bazel/repository_locations.bzl | 2 +- ci/do_ci.sh | 2 +- ci/filter_example_setup.sh | 1 + ci/verify_examples.sh | 4 +- configs/requirements.txt | 4 +- docs/root/version_history/current.rst | 101 +-------------- docs/root/version_history/v1.17.0.rst | 121 ++++++++++++++++++ docs/root/version_history/version_history.rst | 1 + examples/websocket/verify.sh | 2 +- .../filters/network/kafka/requirements.txt | 4 +- 12 files changed, 138 insertions(+), 108 deletions(-) create mode 100644 docs/root/version_history/v1.17.0.rst diff --git a/.github/workflows/get_build_targets.sh b/.github/workflows/get_build_targets.sh index 25f67b74af503..877677743265e 100755 --- a/.github/workflows/get_build_targets.sh +++ b/.github/workflows/get_build_targets.sh @@ -23,6 +23,6 @@ function get_targets() { } # Fetching the upstream HEAD to compare with and stored in FETCH_HEAD. -git fetch https://github.com/envoyproxy/envoy.git master 2>/dev/null +git fetch https://github.com/envoyproxy/envoy.git main 2>/dev/null export BUILD_TARGETS_LOCAL=$(echo $(get_targets)) diff --git a/VERSION b/VERSION index 092afa15df4df..3cf57a7b61c64 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.0 +1.17.1-dev diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e6f1b1afe8ddf..6239f73e0f447 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -804,7 +804,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( version = "2.4.1", sha256 = "2177cbd14118999e1d76fec628ca78ace7e6f841219dbc6035027c796bbe1a2a", strip_prefix = "kafka_2.12-{version}", - urls = ["https://mirrors.gigenet.com/apache/kafka/{version}/kafka_2.12-{version}.tgz"], + urls = ["https://archive.apache.org/dist/kafka/{version}/kafka_2.12-{version}.tgz"], release_date = "2020-03-12", use_category = ["test_only"], ), diff --git a/ci/do_ci.sh b/ci/do_ci.sh index b1bf689333348..52e03bf87e17f 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -450,7 +450,7 @@ elif [[ "$CI_TARGET" == "verify_examples" ]]; then export DOCKER_NO_PULL=1 umask 027 chmod -R o-rwx examples/ - ci/verify_examples.sh + ci/verify_examples.sh "*" wasm-cc exit 0 else echo "Invalid do_ci.sh target, see ci/README.md for valid targets." diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index ae0113c2d0857..8b1df0f059cfa 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -24,6 +24,7 @@ sed -e "s|{ENVOY_SRCDIR}|${ENVOY_SRCDIR}|" "${ENVOY_SRCDIR}"/ci/WORKSPACE.filter mkdir -p "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/bazel ln -sf "${ENVOY_SRCDIR}"/bazel/get_workspace_status "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/bazel/ +cp -f --remove-destination "${ENVOY_SRCDIR}"/.bazelversion "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/ cp -f "${ENVOY_SRCDIR}"/.bazelrc "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/ cp -f "$(bazel info workspace)"/*.bazelrc "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/ diff --git a/ci/verify_examples.sh b/ci/verify_examples.sh index 03a26be026a34..8f78d54e1a297 100755 --- a/ci/verify_examples.sh +++ b/ci/verify_examples.sh @@ -1,10 +1,10 @@ #!/bin/bash -E TESTFILTER="${1:-*}" +TESTEXCLUDES="${2}" FAILED=() SRCDIR="${SRCDIR:-$(pwd)}" - trap_errors () { local frame=0 command line sub file if [[ -n "$example" ]]; then @@ -29,7 +29,7 @@ trap exit 1 INT run_examples () { local examples example cd "${SRCDIR}/examples" || exit 1 - examples=$(find . -mindepth 1 -maxdepth 1 -type d -name "$TESTFILTER" | sort) + examples=$(find . -mindepth 1 -maxdepth 1 -type d -name "$TESTFILTER" ! -iname "_*" ! -name "$TESTEXCLUDES" | sort) for example in $examples; do pushd "$example" > /dev/null || return 1 ./verify.sh diff --git a/configs/requirements.txt b/configs/requirements.txt index 07e1fe994fc33..1977ddd0bd3ad 100644 --- a/configs/requirements.txt +++ b/configs/requirements.txt @@ -34,4 +34,6 @@ MarkupSafe==1.1.1 \ --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ + --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c + diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index dc4a84c02a2fd..769fa75f82e54 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,121 +1,24 @@ -1.17.0 (January 11, 2021) -========================= +1.17.1 (Pending) +================ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* -* config: v2 is now fatal-by-default. This may be overridden by setting :option:`--bootstrap-version` 2 on the CLI for a v2 bootstrap file and also enabling the runtime `envoy.reloadable_features.enable_deprecated_v2_api` feature. See - the :ref:`FAQ entry ` for further details. - Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* build: the Alpine based debug images are no longer built in CI, use Ubuntu based images instead. -* decompressor: set the default value of window_bits of the decompressor to 15 to be able to decompress responses compressed by a compressor with any window size. -* expr filter: added `connection.termination_details` property support. -* formatter: the :ref:`text_format ` field no longer requires at least one byte, and may now be the empty string. It has also become :ref:`deprecated <1_17_deprecated>`. -* grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. -* http: upstream protocol will now only be logged if an upstream stream was established. -* jwt_authn filter: added support of JWT time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds ` to configure it. -* listener: injection of the :ref:`TLS inspector ` has been disabled by default. This feature is controlled by the runtime guard `envoy.reloadable_features.disable_tls_inspector_injection`. -* lua: added `always_wrap_body` argument to `body()` API to always return a :ref:`buffer object ` even if the body is empty. -* memory: enabled new tcmalloc with restartable sequences for aarch64 builds. -* mongo proxy metrics: swapped network connection remote and local closed counters previously set reversed (`cx_destroy_local_with_active_rq` and `cx_destroy_remote_with_active_rq`). -* outlier detection: added :ref:`max_ejection_time ` to limit ejection time growth when a node stays unhealthy for extended period of time. By default :ref:`max_ejection_time ` limits ejection time to 5 minutes. Additionally, when the node stays healthy, ejection time decreases. See :ref:`ejection algorithm` for more info. Previously, ejection time could grow without limit and never decreased. -* performance: improved performance when handling large HTTP/1 bodies. -* tcp_proxy: now waits for HTTP tunnel to be established before start streaming the downstream data, the runtime guard `envoy.reloadable_features.http_upstream_wait_connect_response` can be set to "false" to disable this behavior. -* tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. -* watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. -* xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the - subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the `envoy.reloadable_features.vhds_heartbeats` can be set to "false". - Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* -* config: validate that upgrade configs have a non-empty :ref:`upgrade_type `, fixing a bug where an errant "-" could result in unexpected behavior. -* dns: fixed a bug where custom resolvers provided in configuration were not preserved after network issues. -* dns_filter: correctly associate DNS response IDs when multiple queries are received. -* grpc mux: fixed sending node again after stream is reset when :ref:`set_node_on_first_message_only ` is set. -* http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. -* http: reject requests with missing required headers after filter chain processing. -* http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. -* proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. -* proxy_proto: fixed a bug where network filters would not have the correct downstreamRemoteAddress() when accessed from the StreamInfo. This could result in incorrect enforcement of RBAC rules in the RBAC network filter (but not in the RBAC HTTP filter), or incorrect access log addresses from tcp_proxy. -* sds: fixed a bug that clusters sharing same sds target are marked active immediately. -* tls: fixed detection of the upstream connection close event. -* tls: fixed read resumption after triggering buffer high-watermark and all remaining request/response bytes are stored in the SSL connection's internal buffers. -* udp: fixed issue in which receiving truncated UDP datagrams would cause Envoy to crash. -* watchdog: touch the watchdog before most event loop operations to avoid misses when handling bursts of callbacks. - Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` -* dispatcher: removed legacy socket read/write resumption code path and runtime guard `envoy.reloadable_features.activate_fds_next_event_loop`. -* ext_authz: removed auto ignore case in HTTP-based `ext_authz` header matching and the runtime guard `envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher`. To ignore case, set the :ref:`ignore_case ` field to true. -* ext_authz: the deprecated field `use_alpha` is no longer supported and cannot be set anymore. -* http: removed `envoy.reloadable_features.http1_flood_protection` and legacy code path for turning flood protection off. -* http: removed `envoy.reloadable_features.new_codec_behavior` and legacy codecs. - New Features ------------ -* compression: the :ref:`compressor ` filter added support for compressing request payloads. Its configuration is unified with the :ref:`decompressor ` filter with two new fields for different directions - :ref:`requests ` and :ref:`responses `. The latter deprecates the old response-specific fields and, if used, roots the response-specific stats in `.compressor...response.*` instead of `.compressor...*`. -* config: added ability to flush stats when the admin's :ref:`/stats endpoint ` is hit instead of on a timer via :ref:`stats_flush_on_admin `. -* config: added new runtime feature `envoy.features.enable_all_deprecated_features` that allows the use of all deprecated features. -* crash support: added the ability to dump L4 connection data on crash. -* formatter: added new :ref:`text_format_source ` field to support format strings both inline and from a file. -* formatter: added support for custom date formatting to :ref:`%DOWNSTREAM_PEER_CERT_V_START% ` and :ref:`%DOWNSTREAM_PEER_CERT_V_END% `, similar to :ref:`%START_TIME% `. -* grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. -* grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. -* hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. -* health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. -* http: added HCM :ref:`request_headers_timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. -* http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. -* http: added :ref:`stripping any port from host header ` support. -* http: clusters added support for selecting HTTP/1 or HTTP/2 based on ALPN, configurable via :ref:`alpn_config ` in the :ref:`http_protocol_options ` message. -* jwt_authn: added support for :ref:`per-route config `. -* jwt_authn: changed config field :ref:`issuer ` to be optional to comply with JWT `RFC `_ requirements. -* kill_request: added new :ref:`HTTP kill request filter `. -* listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. -* listener: added back the :ref:`use_original_dst field `. -* listener: added the :ref:`Listener.bind_to_port field `. -* log: added a new custom flag ``%_`` to the log pattern to print the actual message to log, but with escaped newlines. -* lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. -* mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. -* network: added a :ref:`transport_socket_connect_timeout config field ` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes. -* overload: added :ref:`envoy.overload_actions.reduce_timeouts ` overload action to enable scaling timeouts down with load. Scaling support :ref:`is limited ` to the HTTP connection and stream idle timeouts. -* ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. -* ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. -* ratelimit: added :ref:`body ` field to support custom response bodies for non-OK responses from the external ratelimit service. -* ratelimit: added :ref:`descriptor extensions `. -* ratelimit: added :ref:`computed descriptors `. -* ratelimit: added :ref:`dynamic_metadata ` field to support setting dynamic metadata from the ratelimit service. -* router: added support for regex rewrites during HTTP redirects using :ref:`regex_rewrite `. -* sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for - :ref:`TlsCertificate ` and - :ref:`CertificateValidationContext `. -* signal: added an extension point for custom actions to run on the thread that has encountered a fatal error. Actions are configurable via :ref:`fatal_actions `. -* start_tls: added new :ref:`transport socket` which starts in clear-text but may programatically be converted to use tls. -* tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. -* thrift_proxy: added a new :ref:`payload_passthrough ` option to skip decoding body in the Thrift message. -* tls: added support for RSA certificates with 4096-bit keys in FIPS mode. -* tracing: added :ref:`SkyWalking tracer `. -* tracing: added support for setting the hostname used when sending spans to a Zipkin collector using the :ref:`collector_hostname ` field. -* xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. - -.. _1_17_deprecated: Deprecated ---------- -* cluster: HTTP configuration for upstream clusters has been reworked. HTTP-specific configuration is now done in the new :ref:`http_protocol_options ` message, configured via the cluster's :ref:`extension_protocol_options`. This replaces explicit HTTP configuration in cluster config, including :ref:`upstream_http_protocol_options` :ref:`common_http_protocol_options` :ref:`http_protocol_options` :ref:`http2_protocol_options` and :ref:`protocol_selection`. Examples of before-and-after configuration can be found in the :ref:`http_protocol_options docs ` and all of Envoy's example configurations have been updated to the new style of config. -* compression: the fields :ref:`content_length `, :ref:`content_type `, :ref:`disable_on_etag_header `, :ref:`remove_accept_encoding_header ` and :ref:`runtime_enabled ` of the :ref:`Compressor ` message have been deprecated in favor of :ref:`response_direction_config `. -* formatter: :ref:`text_format ` is now deprecated in favor of :ref:`text_format_source `. To migrate existing text format strings, use the :ref:`inline_string ` field. -* gzip: :ref:`HTTP Gzip filter ` is rejected now unless explicitly allowed with :ref:`runtime override ` `envoy.deprecated_features.allow_deprecated_gzip_http_filter` set to `true`. Use the :ref:`compressor filter `. -* listener: :ref:`use_proxy_proto ` has been deprecated in favor of adding a :ref:`PROXY protocol listener filter ` explicitly. -* logging: the `--log-format-prefix-with-location` option is removed. -* ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. -* stats: the `--use-fake-symbol-table` option is removed. -* tracing: OpenCensus :ref:`Zipkin configuration ` is now deprecated, the preferred Zipkin export is via Envoy's :ref:`native Zipkin tracer `. diff --git a/docs/root/version_history/v1.17.0.rst b/docs/root/version_history/v1.17.0.rst new file mode 100644 index 0000000000000..dc4a84c02a2fd --- /dev/null +++ b/docs/root/version_history/v1.17.0.rst @@ -0,0 +1,121 @@ +1.17.0 (January 11, 2021) +========================= + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +* config: v2 is now fatal-by-default. This may be overridden by setting :option:`--bootstrap-version` 2 on the CLI for a v2 bootstrap file and also enabling the runtime `envoy.reloadable_features.enable_deprecated_v2_api` feature. See + the :ref:`FAQ entry ` for further details. + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +* build: the Alpine based debug images are no longer built in CI, use Ubuntu based images instead. +* decompressor: set the default value of window_bits of the decompressor to 15 to be able to decompress responses compressed by a compressor with any window size. +* expr filter: added `connection.termination_details` property support. +* formatter: the :ref:`text_format ` field no longer requires at least one byte, and may now be the empty string. It has also become :ref:`deprecated <1_17_deprecated>`. +* grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. +* http: upstream protocol will now only be logged if an upstream stream was established. +* jwt_authn filter: added support of JWT time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds ` to configure it. +* listener: injection of the :ref:`TLS inspector ` has been disabled by default. This feature is controlled by the runtime guard `envoy.reloadable_features.disable_tls_inspector_injection`. +* lua: added `always_wrap_body` argument to `body()` API to always return a :ref:`buffer object ` even if the body is empty. +* memory: enabled new tcmalloc with restartable sequences for aarch64 builds. +* mongo proxy metrics: swapped network connection remote and local closed counters previously set reversed (`cx_destroy_local_with_active_rq` and `cx_destroy_remote_with_active_rq`). +* outlier detection: added :ref:`max_ejection_time ` to limit ejection time growth when a node stays unhealthy for extended period of time. By default :ref:`max_ejection_time ` limits ejection time to 5 minutes. Additionally, when the node stays healthy, ejection time decreases. See :ref:`ejection algorithm` for more info. Previously, ejection time could grow without limit and never decreased. +* performance: improved performance when handling large HTTP/1 bodies. +* tcp_proxy: now waits for HTTP tunnel to be established before start streaming the downstream data, the runtime guard `envoy.reloadable_features.http_upstream_wait_connect_response` can be set to "false" to disable this behavior. +* tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. +* watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. +* xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the + subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the `envoy.reloadable_features.vhds_heartbeats` can be set to "false". + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* config: validate that upgrade configs have a non-empty :ref:`upgrade_type `, fixing a bug where an errant "-" could result in unexpected behavior. +* dns: fixed a bug where custom resolvers provided in configuration were not preserved after network issues. +* dns_filter: correctly associate DNS response IDs when multiple queries are received. +* grpc mux: fixed sending node again after stream is reset when :ref:`set_node_on_first_message_only ` is set. +* http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. +* http: reject requests with missing required headers after filter chain processing. +* http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. +* proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. +* proxy_proto: fixed a bug where network filters would not have the correct downstreamRemoteAddress() when accessed from the StreamInfo. This could result in incorrect enforcement of RBAC rules in the RBAC network filter (but not in the RBAC HTTP filter), or incorrect access log addresses from tcp_proxy. +* sds: fixed a bug that clusters sharing same sds target are marked active immediately. +* tls: fixed detection of the upstream connection close event. +* tls: fixed read resumption after triggering buffer high-watermark and all remaining request/response bytes are stored in the SSL connection's internal buffers. +* udp: fixed issue in which receiving truncated UDP datagrams would cause Envoy to crash. +* watchdog: touch the watchdog before most event loop operations to avoid misses when handling bursts of callbacks. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +* dispatcher: removed legacy socket read/write resumption code path and runtime guard `envoy.reloadable_features.activate_fds_next_event_loop`. +* ext_authz: removed auto ignore case in HTTP-based `ext_authz` header matching and the runtime guard `envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher`. To ignore case, set the :ref:`ignore_case ` field to true. +* ext_authz: the deprecated field `use_alpha` is no longer supported and cannot be set anymore. +* http: removed `envoy.reloadable_features.http1_flood_protection` and legacy code path for turning flood protection off. +* http: removed `envoy.reloadable_features.new_codec_behavior` and legacy codecs. + +New Features +------------ +* compression: the :ref:`compressor ` filter added support for compressing request payloads. Its configuration is unified with the :ref:`decompressor ` filter with two new fields for different directions - :ref:`requests ` and :ref:`responses `. The latter deprecates the old response-specific fields and, if used, roots the response-specific stats in `.compressor...response.*` instead of `.compressor...*`. +* config: added ability to flush stats when the admin's :ref:`/stats endpoint ` is hit instead of on a timer via :ref:`stats_flush_on_admin `. +* config: added new runtime feature `envoy.features.enable_all_deprecated_features` that allows the use of all deprecated features. +* crash support: added the ability to dump L4 connection data on crash. +* formatter: added new :ref:`text_format_source ` field to support format strings both inline and from a file. +* formatter: added support for custom date formatting to :ref:`%DOWNSTREAM_PEER_CERT_V_START% ` and :ref:`%DOWNSTREAM_PEER_CERT_V_END% `, similar to :ref:`%START_TIME% `. +* grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. +* grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. +* hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. +* health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. +* http: added HCM :ref:`request_headers_timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. +* http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. +* http: added :ref:`stripping any port from host header ` support. +* http: clusters added support for selecting HTTP/1 or HTTP/2 based on ALPN, configurable via :ref:`alpn_config ` in the :ref:`http_protocol_options ` message. +* jwt_authn: added support for :ref:`per-route config `. +* jwt_authn: changed config field :ref:`issuer ` to be optional to comply with JWT `RFC `_ requirements. +* kill_request: added new :ref:`HTTP kill request filter `. +* listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. +* listener: added back the :ref:`use_original_dst field `. +* listener: added the :ref:`Listener.bind_to_port field `. +* log: added a new custom flag ``%_`` to the log pattern to print the actual message to log, but with escaped newlines. +* lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. +* mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. +* network: added a :ref:`transport_socket_connect_timeout config field ` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes. +* overload: added :ref:`envoy.overload_actions.reduce_timeouts ` overload action to enable scaling timeouts down with load. Scaling support :ref:`is limited ` to the HTTP connection and stream idle timeouts. +* ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. +* ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. +* ratelimit: added :ref:`body ` field to support custom response bodies for non-OK responses from the external ratelimit service. +* ratelimit: added :ref:`descriptor extensions `. +* ratelimit: added :ref:`computed descriptors `. +* ratelimit: added :ref:`dynamic_metadata ` field to support setting dynamic metadata from the ratelimit service. +* router: added support for regex rewrites during HTTP redirects using :ref:`regex_rewrite `. +* sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for + :ref:`TlsCertificate ` and + :ref:`CertificateValidationContext `. +* signal: added an extension point for custom actions to run on the thread that has encountered a fatal error. Actions are configurable via :ref:`fatal_actions `. +* start_tls: added new :ref:`transport socket` which starts in clear-text but may programatically be converted to use tls. +* tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. +* thrift_proxy: added a new :ref:`payload_passthrough ` option to skip decoding body in the Thrift message. +* tls: added support for RSA certificates with 4096-bit keys in FIPS mode. +* tracing: added :ref:`SkyWalking tracer `. +* tracing: added support for setting the hostname used when sending spans to a Zipkin collector using the :ref:`collector_hostname ` field. +* xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. + +.. _1_17_deprecated: + +Deprecated +---------- +* cluster: HTTP configuration for upstream clusters has been reworked. HTTP-specific configuration is now done in the new :ref:`http_protocol_options ` message, configured via the cluster's :ref:`extension_protocol_options`. This replaces explicit HTTP configuration in cluster config, including :ref:`upstream_http_protocol_options` :ref:`common_http_protocol_options` :ref:`http_protocol_options` :ref:`http2_protocol_options` and :ref:`protocol_selection`. Examples of before-and-after configuration can be found in the :ref:`http_protocol_options docs ` and all of Envoy's example configurations have been updated to the new style of config. +* compression: the fields :ref:`content_length `, :ref:`content_type `, :ref:`disable_on_etag_header `, :ref:`remove_accept_encoding_header ` and :ref:`runtime_enabled ` of the :ref:`Compressor ` message have been deprecated in favor of :ref:`response_direction_config `. +* formatter: :ref:`text_format ` is now deprecated in favor of :ref:`text_format_source `. To migrate existing text format strings, use the :ref:`inline_string ` field. +* gzip: :ref:`HTTP Gzip filter ` is rejected now unless explicitly allowed with :ref:`runtime override ` `envoy.deprecated_features.allow_deprecated_gzip_http_filter` set to `true`. Use the :ref:`compressor filter `. +* listener: :ref:`use_proxy_proto ` has been deprecated in favor of adding a :ref:`PROXY protocol listener filter ` explicitly. +* logging: the `--log-format-prefix-with-location` option is removed. +* ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. +* stats: the `--use-fake-symbol-table` option is removed. +* tracing: OpenCensus :ref:`Zipkin configuration ` is now deprecated, the preferred Zipkin export is via Envoy's :ref:`native Zipkin tracer `. diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index b25ef731208af..32c63cf7a0dc0 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.17.0 v1.16.2 v1.16.1 v1.16.0 diff --git a/examples/websocket/verify.sh b/examples/websocket/verify.sh index 68b463b077ffc..494c1fa7e7ae1 100755 --- a/examples/websocket/verify.sh +++ b/examples/websocket/verify.sh @@ -20,7 +20,7 @@ interact_ws () { fi expect < Date: Fri, 5 Feb 2021 18:13:37 +0900 Subject: [PATCH 02/28] ci: fix docs tag build (#14653) (#14794) Signed-off-by: Lizan Zhou --- .azure-pipelines/pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index ad0ef9b4636f4..c09544790f0c3 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -54,6 +54,7 @@ stages: - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs' workingDirectory: $(Build.SourcesDirectory) env: + AZP_BRANCH: $(Build.SourceBranch) ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance @@ -303,6 +304,7 @@ stages: - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs' workingDirectory: $(Build.SourcesDirectory) env: + AZP_BRANCH: $(Build.SourceBranch) ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance @@ -321,7 +323,6 @@ stages: workingDirectory: $(Build.SourcesDirectory) env: AZP_BRANCH: $(Build.SourceBranch) - AZP_SHA1: $(Build.SourceVersion) - stage: verify dependsOn: ["docker"] From cf6f2b59f289d0cd9143f7149f00b6859b756464 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Fri, 5 Feb 2021 10:51:05 -0800 Subject: [PATCH 03/28] [tls] add missing built in cipher stat names (#14956) Backport of #14676 Signed-off-by: Asra Ali Signed-off-by: Piotr Sikora Co-authored-by: asraa --- .../transport_sockets/tls/context_impl.cc | 5 +++-- .../tls/context_impl_test.cc | 21 ++++++++++++------- tools/spelling/spelling_dictionary.txt | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 2ba6a6ee89be7..f81f2214c8700 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -471,9 +471,10 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c } // Add hardcoded cipher suites from the TLS 1.3 spec: - // https://tools.ietf.org/html/rfc8446 + // https://tools.ietf.org/html/rfc8446#appendix-B.4 + // AES-CCM cipher suites are removed (no BoringSSL support). stat_name_set_->rememberBuiltins( - {"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"}); + {"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"}); // Curves from // https://github.com/google/boringssl/blob/f4d8b969200f1ee2dd872ffb85802e6a0976afe7/ssl/ssl_key_share.cc#L384 diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 4d0c0e204ccf6..86571982f3587 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -1977,11 +1977,16 @@ class SslContextStatsTest : public SslContextImplTest { TEST_F(SslContextStatsTest, IncOnlyKnownCounters) { // Incrementing a value for a cipher that is part of the configuration works, and // we'll be able to find the value in the stats store. - context_->incCounter("ssl.ciphers", "ECDHE-ECDSA-AES256-GCM-SHA384"); - Stats::CounterOptConstRef cipher = - store_.findCounterByString("ssl.ciphers.ECDHE-ECDSA-AES256-GCM-SHA384"); - ASSERT_TRUE(cipher.has_value()); - EXPECT_EQ(1, cipher->get().value()); + for (const auto& cipher : + {"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"}) { + // Test supported built-in TLS v1.3 cipher suites + // https://tools.ietf.org/html/rfc8446#appendix-B.4. + context_->incCounter("ssl.ciphers", cipher); + Stats::CounterOptConstRef stat = + store_.findCounterByString(absl::StrCat("ssl.ciphers.", cipher)); + ASSERT_TRUE(stat.has_value()); + EXPECT_EQ(1, stat->get().value()); + } // Incrementing a stat for a random unknown cipher does not work. A // rate-limited error log message will also be generated but that is hard to @@ -1995,9 +2000,9 @@ TEST_F(SslContextStatsTest, IncOnlyKnownCounters) { // fallback registration does not occur. So we test for the fallback only in // release builds. #ifdef NDEBUG - cipher = store_.findCounterByString("ssl.ciphers.fallback"); - ASSERT_TRUE(cipher.has_value()); - EXPECT_EQ(1, cipher->get().value()); + Stats::CounterOptConstRef stat = store_.findCounterByString("ssl.ciphers.fallback"); + ASSERT_TRUE(stat.has_value()); + EXPECT_EQ(1, stat->get().value()); #endif } diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 8d87a26db7254..0819e488b28f2 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -35,6 +35,7 @@ STATNAME SkyWalking TIDs ceil +CCM CHACHA CHLO CHMOD From 4ccc677823d981a91f47d815320cff35f716b2f4 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Fri, 5 Feb 2021 13:24:50 -0800 Subject: [PATCH 04/28] tls: add missing stats for signature algorithms. (#14955) While there, refresh supported cipher suites and add more warnings. Backport of #14703 Signed-off-by: Piotr Sikora --- .../transport_sockets/tls/context_impl.cc | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index f81f2214c8700..ee84dec354b98 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -464,30 +464,51 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c parsed_alpn_protocols_ = parseAlpnProtocols(config.alpnProtocols()); // Use the SSL library to iterate over the configured ciphers. + // + // Note that if a negotiated cipher suite is outside of this set, we'll issue an ENVOY_BUG. for (TlsContext& tls_context : tls_contexts_) { for (const SSL_CIPHER* cipher : SSL_CTX_get_ciphers(tls_context.ssl_ctx_.get())) { stat_name_set_->rememberBuiltin(SSL_CIPHER_get_name(cipher)); } } - // Add hardcoded cipher suites from the TLS 1.3 spec: + // Add supported cipher suites from the TLS 1.3 spec: // https://tools.ietf.org/html/rfc8446#appendix-B.4 // AES-CCM cipher suites are removed (no BoringSSL support). + // + // Note that if a negotiated cipher suite is outside of this set, we'll issue an ENVOY_BUG. stat_name_set_->rememberBuiltins( {"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"}); - // Curves from - // https://github.com/google/boringssl/blob/f4d8b969200f1ee2dd872ffb85802e6a0976afe7/ssl/ssl_key_share.cc#L384 + // All supported curves. Source: + // https://github.com/google/boringssl/blob/3743aafdacff2f7b083615a043a37101f740fa53/ssl/ssl_key_share.cc#L302-L309 // - // Note that if a curve is configured outside this set, we'll issue an ENVOY_BUG so - // it will hopefully be caught. - stat_name_set_->rememberBuiltins( - {"P-224", "P-256", "P-384", "P-521", "X25519", "CECPQ2", "CECPQ2b"}); + // Note that if a negotiated curve is outside of this set, we'll issue an ENVOY_BUG. + stat_name_set_->rememberBuiltins({"P-224", "P-256", "P-384", "P-521", "X25519", "CECPQ2"}); - // Algorithms - stat_name_set_->rememberBuiltins({"ecdsa_secp256r1_sha256", "rsa_pss_rsae_sha256"}); - - // Versions + // All supported signature algorithms. Source: + // https://github.com/google/boringssl/blob/3743aafdacff2f7b083615a043a37101f740fa53/ssl/ssl_privkey.cc#L436-L453 + // + // Note that if a negotiated algorithm is outside of this set, we'll issue an ENVOY_BUG. + stat_name_set_->rememberBuiltins({ + "rsa_pkcs1_md5_sha1", + "rsa_pkcs1_sha1", + "rsa_pkcs1_sha256", + "rsa_pkcs1_sha384", + "rsa_pkcs1_sha512", + "ecdsa_sha1", + "ecdsa_secp256r1_sha256", + "ecdsa_secp384r1_sha384", + "ecdsa_secp521r1_sha512", + "rsa_pss_rsae_sha256", + "rsa_pss_rsae_sha384", + "rsa_pss_rsae_sha512", + "ed25519", + }); + + // All supported protocol versions. + // + // Note that if a negotiated version is outside of this set, we'll issue an ENVOY_BUG. stat_name_set_->rememberBuiltins({"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"}); } From 31bdbef8d53ee9556235e6da49c416f0abc9d983 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Fri, 12 Feb 2021 02:57:37 +0900 Subject: [PATCH 05/28] backport 1.17: scaled range timer: guard against queue deletion during timer fire (#14799) (#15006) Fix a potential use-after-free error in ScaledRangeTimerManagerImpl by adding a processing_timers_ flag to the timer queues that is set during onQueueTimerFired processing. This flag is checked when a timer is removed to ensure that the timer's queue isn't deleted while it is in a callback triggered by onQueueTimerFired. Signed-off-by: Craig Radcliffe Signed-off-by: Shikugawa --- docs/root/version_history/current.rst | 2 + .../event/scaled_range_timer_manager_impl.cc | 8 ++- .../event/scaled_range_timer_manager_impl.h | 4 ++ .../scaled_range_timer_manager_impl_test.cc | 54 +++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 769fa75f82e54..0a1de40e6e03d 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -13,6 +13,8 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. + Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/event/scaled_range_timer_manager_impl.cc b/source/common/event/scaled_range_timer_manager_impl.cc index 6aab81486929c..e4dc2cec2d8c1 100644 --- a/source/common/event/scaled_range_timer_manager_impl.cc +++ b/source/common/event/scaled_range_timer_manager_impl.cc @@ -212,7 +212,11 @@ void ScaledRangeTimerManagerImpl::removeTimer(ScalingTimerHandle handle) { handle.queue_.range_timers_.erase(handle.iterator_); // Don't keep around empty queues if (handle.queue_.range_timers_.empty()) { - queues_.erase(handle.queue_); + // Skip erasing the queue if we're in the middle of processing timers for the queue. The + // queue will be erased in `onQueueTimerFired` after the queue entries have been processed. + if (!handle.queue_.processing_timers_) { + queues_.erase(handle.queue_); + } return; } @@ -242,12 +246,14 @@ void ScaledRangeTimerManagerImpl::onQueueTimerFired(Queue& queue) { // Pop and trigger timers until the one at the front isn't supposed to have expired yet (given the // current scale factor). + queue.processing_timers_ = true; while (!timers.empty() && computeTriggerTime(timers.front(), queue.duration_, scale_factor_) <= now) { auto item = std::move(queue.range_timers_.front()); queue.range_timers_.pop_front(); item.timer_.trigger(); } + queue.processing_timers_ = false; if (queue.range_timers_.empty()) { // Maintain the invariant that queues are never empty. diff --git a/source/common/event/scaled_range_timer_manager_impl.h b/source/common/event/scaled_range_timer_manager_impl.h index f9bcc7242b7d1..7c3673d9da284 100644 --- a/source/common/event/scaled_range_timer_manager_impl.h +++ b/source/common/event/scaled_range_timer_manager_impl.h @@ -63,6 +63,10 @@ class ScaledRangeTimerManagerImpl : public ScaledRangeTimerManager { // 2) on expiration // 3) when the scale factor changes const TimerPtr timer_; + + // A flag indicating whether the queue is currently processing timers. Used to guard against + // queue deletion during timer processing. + bool processing_timers_{false}; }; /** diff --git a/test/common/event/scaled_range_timer_manager_impl_test.cc b/test/common/event/scaled_range_timer_manager_impl_test.cc index 824a285ee4f61..c6f7476c8af5b 100644 --- a/test/common/event/scaled_range_timer_manager_impl_test.cc +++ b/test/common/event/scaled_range_timer_manager_impl_test.cc @@ -151,6 +151,60 @@ TEST_F(ScaledRangeTimerManagerTest, DisableWhileScalingMax) { simTime().advanceTimeAndRun(std::chrono::seconds(100), dispatcher_, Dispatcher::RunType::Block); } +TEST_F(ScaledRangeTimerManagerTest, InCallbackDisableLastTimerInSameQueue) { + ScaledRangeTimerManagerImpl manager(dispatcher_); + + MockFunction callback1; + auto timer1 = + manager.createTimer(AbsoluteMinimum(std::chrono::seconds(0)), callback1.AsStdFunction()); + MockFunction callback2; + auto timer2 = + manager.createTimer(AbsoluteMinimum(std::chrono::seconds(5)), callback2.AsStdFunction()); + + timer1->enableTimer(std::chrono::seconds(95)); + timer2->enableTimer(std::chrono::seconds(100)); + + simTime().advanceTimeAndRun(std::chrono::seconds(5), dispatcher_, Dispatcher::RunType::Block); + + EXPECT_TRUE(timer1->enabled()); + EXPECT_TRUE(timer2->enabled()); + + EXPECT_CALL(callback1, Call).WillOnce(Invoke([&]() { + timer2->disableTimer(); + timer2.reset(); + })); + + // Run the dispatcher to make sure nothing happens when it's not supposed to. + simTime().advanceTimeAndRun(std::chrono::seconds(100), dispatcher_, Dispatcher::RunType::Block); +} + +TEST_F(ScaledRangeTimerManagerTest, InCallbackDisableTimerInOtherQueue) { + ScaledRangeTimerManagerImpl manager(dispatcher_); + + MockFunction callback1; + auto timer1 = + manager.createTimer(AbsoluteMinimum(std::chrono::seconds(5)), callback1.AsStdFunction()); + MockFunction callback2; + auto timer2 = + manager.createTimer(AbsoluteMinimum(std::chrono::seconds(5)), callback2.AsStdFunction()); + + timer1->enableTimer(std::chrono::seconds(95)); + timer2->enableTimer(std::chrono::seconds(100)); + + simTime().advanceTimeAndRun(std::chrono::seconds(5), dispatcher_, Dispatcher::RunType::Block); + + EXPECT_TRUE(timer1->enabled()); + EXPECT_TRUE(timer2->enabled()); + + EXPECT_CALL(callback1, Call).WillOnce(Invoke([&]() { + timer2->disableTimer(); + timer2.reset(); + })); + + // Run the dispatcher to make sure nothing happens when it's not supposed to. + simTime().advanceTimeAndRun(std::chrono::seconds(100), dispatcher_, Dispatcher::RunType::Block); +} + TEST_F(ScaledRangeTimerManagerTest, DisableWithZeroMinTime) { ScaledRangeTimerManagerImpl manager(dispatcher_); From a7d9915a03a581e5a366f7f2f749908a7996d355 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 18 Feb 2021 16:43:16 -0800 Subject: [PATCH 06/28] wasm: fix root_context_id that was incorrectly cached across threads. (#15016) (#15078) Fixes proxy-wasm/proxy-wasm-cpp-host#125. Signed-off-by: Piotr Sikora --- bazel/repository_locations.bzl | 6 ++--- source/extensions/common/wasm/context.h | 1 + source/extensions/common/wasm/wasm.cc | 10 ++++---- source/extensions/common/wasm/wasm.h | 10 +++++--- .../extensions/common/wasm/wasm_extension.cc | 6 ++--- .../extensions/common/wasm/wasm_extension.h | 2 +- .../filters/http/wasm/wasm_filter.h | 16 +++++++------ .../filters/network/wasm/wasm_filter.h | 16 +++++++------ test/extensions/common/wasm/wasm_test.cc | 24 +++++++++---------- .../filters/network/wasm/config_test.cc | 23 ++++++++++++++++++ 10 files changed, 74 insertions(+), 40 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6239f73e0f447..56d531f499dde 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -846,8 +846,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "6dab125d7a668c7158848b6f48c67fd827c952e6", - sha256 = "b5c73ed053a7079bd8bf53b14c4811e87ae521d9fcf4769ec5b248202a27600d", + version = "a59dd5439070ad6faa6d32ed9a36d0c227a96071", + sha256 = "0b591c4acd172d8203a8af61afc0c3ce9dda9d6d55aee616182324840650b431", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -862,7 +862,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2020-12-16", + release_date = "2021-02-11", cpe = "N/A", ), proxy_wasm_rust_sdk = dict( diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index f222523e0275b..216b7eaed3aac 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -46,6 +46,7 @@ using GrpcService = envoy::config::core::v3::GrpcService; class Wasm; +using PluginBaseSharedPtr = std::shared_ptr; using PluginHandleBaseSharedPtr = std::shared_ptr; using WasmHandleBaseSharedPtr = std::shared_ptr; diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index cc4efd963fdfc..361c798c201eb 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -281,10 +281,12 @@ getCloneFactory(WasmExtension* wasm_extension, Event::Dispatcher& dispatcher, static proxy_wasm::PluginHandleFactory getPluginFactory(WasmExtension* wasm_extension) { auto wasm_plugin_factory = wasm_extension->pluginFactory(); - return [wasm_plugin_factory](WasmHandleBaseSharedPtr base_wasm, - absl::string_view plugin_key) -> std::shared_ptr { - return wasm_plugin_factory(std::static_pointer_cast(base_wasm), plugin_key); - }; + return + [wasm_plugin_factory](WasmHandleBaseSharedPtr base_wasm, + PluginBaseSharedPtr base_plugin) -> std::shared_ptr { + return wasm_plugin_factory(std::static_pointer_cast(base_wasm), + std::static_pointer_cast(base_plugin)); + }; } WasmEvent toWasmEvent(const std::shared_ptr& wasm) { diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 8ade0d66e63d0..a614a2769c4be 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -141,15 +141,19 @@ using WasmHandleSharedPtr = std::shared_ptr; class PluginHandle : public PluginHandleBase, public ThreadLocal::ThreadLocalObject { public: - explicit PluginHandle(const WasmHandleSharedPtr& wasm_handle, absl::string_view plugin_key) - : PluginHandleBase(std::static_pointer_cast(wasm_handle), plugin_key), - wasm_handle_(wasm_handle) {} + explicit PluginHandle(const WasmHandleSharedPtr& wasm_handle, const PluginSharedPtr& plugin) + : PluginHandleBase(std::static_pointer_cast(wasm_handle), + std::static_pointer_cast(plugin)), + wasm_handle_(wasm_handle), + root_context_id_(wasm_handle->wasm()->getRootContext(plugin, false)->id()) {} WasmSharedPtr& wasm() { return wasm_handle_->wasm(); } WasmHandleSharedPtr& wasmHandleForTest() { return wasm_handle_; } + uint32_t rootContextId() { return root_context_id_; } private: WasmHandleSharedPtr wasm_handle_; + const uint32_t root_context_id_; }; using PluginHandleSharedPtr = std::shared_ptr; diff --git a/source/extensions/common/wasm/wasm_extension.cc b/source/extensions/common/wasm/wasm_extension.cc index 6c99a8124dc73..40f4f3946445a 100644 --- a/source/extensions/common/wasm/wasm_extension.cc +++ b/source/extensions/common/wasm/wasm_extension.cc @@ -40,10 +40,10 @@ RegisterWasmExtension::RegisterWasmExtension(WasmExtension* extension) { } PluginHandleExtensionFactory EnvoyWasm::pluginFactory() { - return [](const WasmHandleSharedPtr& base_wasm, - absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return [](const WasmHandleSharedPtr& wasm_handle, + const PluginSharedPtr& plugin) -> PluginHandleBaseSharedPtr { return std::static_pointer_cast( - std::make_shared(base_wasm, plugin_key)); + std::make_shared(wasm_handle, plugin)); }; } diff --git a/source/extensions/common/wasm/wasm_extension.h b/source/extensions/common/wasm/wasm_extension.h index 8fb6b9b07f772..90368f43b1201 100644 --- a/source/extensions/common/wasm/wasm_extension.h +++ b/source/extensions/common/wasm/wasm_extension.h @@ -32,7 +32,7 @@ using WasmHandleSharedPtr = std::shared_ptr; using CreateContextFn = std::function& plugin)>; using PluginHandleExtensionFactory = std::function; + const WasmHandleSharedPtr& wasm_handle, const PluginSharedPtr& plugin)>; using WasmHandleExtensionFactory = std::function { if (handle.has_value()) { wasm = handle->wasm().get(); } - if (plugin_->fail_open_ && (!wasm || wasm->isFailed())) { - return nullptr; + if (!wasm || wasm->isFailed()) { + if (plugin_->fail_open_) { + // Fail open skips adding this filter to callbacks. + return nullptr; + } else { + // Fail closed is handled by an empty Context. + return std::make_shared(nullptr, 0, plugin_); + } } - if (wasm && !root_context_id_) { - root_context_id_ = wasm->getRootContext(plugin_, false)->id(); - } - return std::make_shared(wasm, root_context_id_, plugin_); + return std::make_shared(wasm, handle->rootContextId(), plugin_); } private: - uint32_t root_context_id_{0}; PluginSharedPtr plugin_; ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index 6a6fe2584b2ca..9a166139e23f7 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -31,19 +31,21 @@ class FilterConfig : Logger::Loggable { if (handle.has_value()) { wasm = handle->wasm().get(); } - if (plugin_->fail_open_ && (!wasm || wasm->isFailed())) { - return nullptr; + if (!wasm || wasm->isFailed()) { + if (plugin_->fail_open_) { + // Fail open skips adding this filter to callbacks. + return nullptr; + } else { + // Fail closed is handled by an empty Context. + return std::make_shared(nullptr, 0, plugin_); + } } - if (wasm && !root_context_id_) { - root_context_id_ = wasm->getRootContext(plugin_, false)->id(); - } - return std::make_shared(wasm, root_context_id_, plugin_); + return std::make_shared(wasm, handle->rootContextId(), plugin_); } Wasm* wasmForTest() { return tls_slot_->get()->wasm().get(); } private: - uint32_t root_context_id_{0}; PluginSharedPtr plugin_; ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 5233f535394b5..eb844be718bdb 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -699,10 +699,10 @@ TEST_P(WasmCommonTest, VmCache) { }); return std::make_shared(wasm); }, - [](const WasmHandleBaseSharedPtr& base_wasm, - absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { - return std::make_shared(std::static_pointer_cast(base_wasm), - plugin_key); + [](const WasmHandleBaseSharedPtr& wasm_handle, + const PluginBaseSharedPtr& plugin) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(wasm_handle), + std::static_pointer_cast(plugin)); }); wasm_handle.reset(); wasm_handle2.reset(); @@ -805,10 +805,10 @@ TEST_P(WasmCommonTest, RemoteCode) { }); return std::make_shared(wasm); }, - [](const WasmHandleBaseSharedPtr& base_wasm, - absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { - return std::make_shared(std::static_pointer_cast(base_wasm), - plugin_key); + [](const WasmHandleBaseSharedPtr& wasm_handle, + const PluginBaseSharedPtr& plugin) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(wasm_handle), + std::static_pointer_cast(plugin)); }); wasm_handle.reset(); @@ -922,10 +922,10 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { }); return std::make_shared(wasm); }, - [](const WasmHandleBaseSharedPtr& base_wasm, - absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { - return std::make_shared(std::static_pointer_cast(base_wasm), - plugin_key); + [](const WasmHandleBaseSharedPtr& wasm_handle, + const PluginBaseSharedPtr& plugin) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(wasm_handle), + std::static_pointer_cast(plugin)); }); wasm_handle.reset(); diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 6d93a167f674a..69dd078d7533c 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -154,6 +154,29 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadInlineBadCodeFailOpenNackConfig) { "Unable to create Wasm network filter test"); } +TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailClosed) { + if (GetParam() == "null") { + return; + } + const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( + config: + vm_config: + runtime: "envoy.wasm.runtime.)EOF", + GetParam(), R"EOF(" + code: + local: + filename: "{{ test_rundir }}/test/extensions/filters/network/wasm/test_data/test_cpp.wasm" + )EOF")); + + envoy::extensions::filters::network::wasm::v3::Wasm proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); + filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); + auto context = filter_config.createFilter(); + EXPECT_EQ(context->wasm(), nullptr); + EXPECT_TRUE(context->isFailed()); +} + TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpen) { if (GetParam() == "null") { return; From d6a4496e712d7a2335b26e2f76210d5904002c26 Mon Sep 17 00:00:00 2001 From: asraa Date: Fri, 26 Feb 2021 02:55:12 -0500 Subject: [PATCH 07/28] backport retain JwtUnknownIssuer error for allow_missing (#15199) Signed-off-by: Asra Ali --- VERSION | 2 +- docs/root/version_history/current.rst | 5 +++-- .../extensions/filters/http/jwt_authn/verifier.cc | 15 +++++++-------- .../filters/http/jwt_authn/all_verifier_test.cc | 13 +++++++++++-- .../filters/http/jwt_authn/group_verifier_test.cc | 6 +++--- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/VERSION b/VERSION index 3cf57a7b61c64..511a76e6faf83 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.1-dev +1.17.1 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 0a1de40e6e03d..15c5b4b5ce9e9 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,5 +1,5 @@ -1.17.1 (Pending) -================ +1.17.1 (February 25, 2021) +========================== Incompatible Behavior Changes ----------------------------- @@ -13,6 +13,7 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. * overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. Removed Config or Runtime diff --git a/source/extensions/filters/http/jwt_authn/verifier.cc b/source/extensions/filters/http/jwt_authn/verifier.cc index cb920c1a5477c..83361c8edd7b8 100644 --- a/source/extensions/filters/http/jwt_authn/verifier.cc +++ b/source/extensions/filters/http/jwt_authn/verifier.cc @@ -301,17 +301,16 @@ class AnyVerifierImpl : public BaseGroupVerifierImpl { // Then wait for all children to be done. if (++completion_state.number_completed_children_ == verifiers_.size()) { // Aggregate all children status into a final status. - // JwtMissing should be treated differently than other failure status - // since it simply means there is not Jwt token for the required provider. - // If there is a failure status other than JwtMissing in the children, - // it should be used as the final status. + // JwtMissed and JwtUnknownIssuer should be treated differently than other errors. + // JwtMissed means not Jwt token for the required provider. + // JwtUnknownIssuer means wrong issuer for the required provider. Status final_status = Status::JwtMissed; for (const auto& it : verifiers_) { - // If a Jwt is extracted from a location not specified by the required provider, - // the authenticator returns JwtUnknownIssuer. It should be treated the same as - // JwtMissed. + // Prefer errors which are not JwtMissed nor JwtUnknownIssuer. + // Prefer JwtUnknownIssuer between JwtMissed and JwtUnknownIssuer. Status child_status = context.getCompletionState(it.get()).status_; - if (child_status != Status::JwtMissed && child_status != Status::JwtUnknownIssuer) { + if ((child_status != Status::JwtMissed && child_status != Status::JwtUnknownIssuer) || + final_status == Status::JwtMissed) { final_status = child_status; } } diff --git a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc index 08cea40b92513..ba198bb922a5f 100644 --- a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc @@ -197,7 +197,7 @@ TEST_F(SingleAllowMissingInOrListTest, BadJwt) { } TEST_F(SingleAllowMissingInOrListTest, MissingIssToken) { - EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + EXPECT_CALL(mock_cb_, onComplete(Status::JwtUnknownIssuer)); auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ES256WithoutIssToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); @@ -471,6 +471,15 @@ TEST_F(AllowMissingInOrListTest, OtherGoodJwt) { EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); } +TEST_F(AllowMissingInOrListTest, WrongIssuer) { + EXPECT_CALL(mock_cb_, onComplete(Status::JwtUnknownIssuer)); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, OtherGoodToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + // x-other JWT should be ignored. + EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); +} + TEST_F(AllowMissingInOrListTest, BadAndGoodJwts) { EXPECT_CALL(mock_cb_, onComplete(Status::JwtVerificationFail)); auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, NonExistKidToken}, @@ -589,7 +598,7 @@ TEST_F(AllowMissingInAndOfOrListTest, TwoGoodJwts) { } TEST_F(AllowMissingInAndOfOrListTest, GoodAndBadJwts) { - EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + EXPECT_CALL(mock_cb_, onComplete(Status::JwtUnknownIssuer)); // Use the token with example.com issuer for x-other. auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, GoodToken}}; diff --git a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc index ab14d6243cb09..0ccbe3aac770f 100644 --- a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc @@ -582,8 +582,8 @@ TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButFailed) { callbacks_["other_provider"](Status::JwtExpired); } -// Test RequiresAny with two providers and allow_missing, but OK -TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButOk) { +// Test RequiresAny with two providers and allow_missing, but one returns JwtUnknownIssuer +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButUnknownIssuer) { TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); proto_config_.mutable_rules(0) ->mutable_requires() @@ -592,7 +592,7 @@ TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButOk) { ->mutable_allow_missing(); createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); - EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + EXPECT_CALL(mock_cb_, onComplete(Status::JwtUnknownIssuer)); auto headers = Http::TestRequestHeaderMapImpl{}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); From c1400785679e576c420ef574b4323d85922c4d73 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Tue, 2 Mar 2021 01:38:14 +0900 Subject: [PATCH 08/28] kickoff 1.17.2 (#15209) Signed-off-by: Shikugawa --- VERSION | 2 +- docs/root/version_history/current.rst | 5 +--- docs/root/version_history/v1.17.1.rst | 27 +++++++++++++++++++ docs/root/version_history/version_history.rst | 1 + 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 docs/root/version_history/v1.17.1.rst diff --git a/VERSION b/VERSION index 511a76e6faf83..3fcc8df681aae 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.1 +1.17.2-dev diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 15c5b4b5ce9e9..7a33754f0bae4 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,4 +1,4 @@ -1.17.1 (February 25, 2021) +1.17.2 (Pending) ========================== Incompatible Behavior Changes @@ -13,9 +13,6 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* -* jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. -* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. - Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/docs/root/version_history/v1.17.1.rst b/docs/root/version_history/v1.17.1.rst new file mode 100644 index 0000000000000..15c5b4b5ce9e9 --- /dev/null +++ b/docs/root/version_history/v1.17.1.rst @@ -0,0 +1,27 @@ +1.17.1 (February 25, 2021) +========================== + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. +* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ + +Deprecated +---------- diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 32c63cf7a0dc0..86c565dfb124e 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.17.1 v1.17.0 v1.16.2 v1.16.1 From cffb095d59d7935abda12b9509bcd136808367bb Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Tue, 2 Mar 2021 10:01:52 +0900 Subject: [PATCH 09/28] backport 1.17: master -> main (#15220) Signed-off-by: Shikugawa Co-authored-by: Matt Klein --- .azure-pipelines/cve_scan.yml | 2 +- .azure-pipelines/pipelines.yml | 2 +- .bazelrc | 2 +- .../non--crash-security--bug.md | 4 +-- .github/workflows/get_build_targets.sh | 2 +- CONTRIBUTING.md | 14 ++++---- DEPENDENCY_POLICY.md | 4 +-- DEVELOPER.md | 30 ++++++++--------- EXTENSION_POLICY.md | 8 ++--- GOVERNANCE.md | 8 ++--- PULL_REQUEST_TEMPLATE.md | 2 +- README.md | 4 +-- RELEASES.md | 12 +++---- SECURITY.md | 16 +++++----- api/API_VERSIONING.md | 4 +-- api/CONTRIBUTING.md | 2 +- api/README.md | 4 +-- api/envoy/api/v2/core/protocol.proto | 2 +- api/envoy/config/core/v3/protocol.proto | 2 +- api/envoy/config/core/v4alpha/protocol.proto | 2 +- bazel/EXTERNAL_DEPS.md | 4 +-- bazel/PPROF.md | 6 ++-- bazel/README.md | 24 +++++++------- ci/README.md | 16 +++++----- ci/api_mirror.sh | 4 +-- ci/docker_ci.sh | 14 ++++---- ci/filter_example_mirror.sh | 4 +-- ci/go_mirror.sh | 2 +- ci/repokitteh/modules/newcontributor.star | 2 +- ci/upload_gcs_artifact.sh | 2 +- docs/README.md | 4 +-- docs/publish.sh | 4 +-- docs/root/version_history/v1.11.0.rst | 2 +- docs/root/version_history/v1.14.0.rst | 2 +- docs/root/version_history/v1.4.0.rst | 2 +- docs/root/version_history/v1.8.0.rst | 16 +++++----- docs/root/version_history/v1.9.0.rst | 10 +++--- docs/root/version_history/version_history.rst | 2 +- .../grpc-bridge/docker-compose-protos.yaml | 2 +- .../envoy/api/v2/core/protocol.proto | 2 +- .../envoy/config/core/v3/protocol.proto | 2 +- .../envoy/config/core/v4alpha/protocol.proto | 2 +- include/envoy/http/filter.h | 2 +- security/email-templates.md | 6 ++-- security/postmortems/cve-2019-15225.md | 2 +- security/postmortems/cve-2019-9900.md | 2 +- source/common/config/config_provider_impl.h | 2 +- source/common/http/utility.cc | 2 +- source/common/stats/symbol_table_impl.cc | 4 +-- source/common/stats/thread_local_store.h | 2 +- source/common/stats/utility.h | 10 +++--- source/docs/network_filter_fuzzing.md | 32 +++++++++---------- source/docs/repokitteh.md | 4 +-- source/docs/stats.md | 20 ++++++------ test/README.md | 8 ++--- test/common/upstream/health_check_fuzz.cc | 2 +- .../filters/network/common/fuzz/README.md | 4 +-- test/integration/stats_integration_test.cc | 4 +-- tools/api/generate_go_protobuf.py | 2 +- tools/git/last_github_commit.sh | 4 +-- tools/proto_format/proto_sync.py | 2 +- 61 files changed, 184 insertions(+), 184 deletions(-) diff --git a/.azure-pipelines/cve_scan.yml b/.azure-pipelines/cve_scan.yml index 9cfe244979843..322adae2bb71c 100644 --- a/.azure-pipelines/cve_scan.yml +++ b/.azure-pipelines/cve_scan.yml @@ -9,7 +9,7 @@ schedules: displayName: Hourly CVE scan branches: include: - - master + - main always: true pool: diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index c09544790f0c3..8c875d4684843 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -1,7 +1,7 @@ trigger: branches: include: - - "master" + - "main" - "release/v*" tags: include: diff --git a/.bazelrc b/.bazelrc index d43694e4ccf93..d399f2a5533f2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -245,7 +245,7 @@ build:remote-clang-cl --config=clang-cl build:remote-clang-cl --config=rbe-toolchain-clang-cl # Docker sandbox -# NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/master/toolchains/rbe_toolchains_config.bzl#L8 +# NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:11efa5680d987fff33fde4af3cc5ece105015d04 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker diff --git a/.github/ISSUE_TEMPLATE/non--crash-security--bug.md b/.github/ISSUE_TEMPLATE/non--crash-security--bug.md index 15c784680ecfc..5b80df378a022 100644 --- a/.github/ISSUE_TEMPLATE/non--crash-security--bug.md +++ b/.github/ISSUE_TEMPLATE/non--crash-security--bug.md @@ -22,7 +22,7 @@ returned, etc. > Include sample requests, environment, etc. All data and inputs required to reproduce the bug. ->**Note**: The [Envoy_collect tool](https://github.com/envoyproxy/envoy/blob/master/tools/envoy_collect/README.md) +>**Note**: The [Envoy_collect tool](https://github.com/envoyproxy/envoy/blob/main/tools/envoy_collect/README.md) gathers a tarball with debug logs, config and the following admin endpoints: /stats, /clusters and /server_info. Please note if there are privacy concerns, sanitize the data prior to sharing the tarball/pasting. @@ -46,4 +46,4 @@ sharing. *Call Stack*: > If the Envoy binary is crashing, a call stack is **required**. -Please refer to the [Bazel Stack trace documentation](https://github.com/envoyproxy/envoy/tree/master/bazel#stack-trace-symbol-resolution). +Please refer to the [Bazel Stack trace documentation](https://github.com/envoyproxy/envoy/tree/main/bazel#stack-trace-symbol-resolution). diff --git a/.github/workflows/get_build_targets.sh b/.github/workflows/get_build_targets.sh index 877677743265e..874af4aefcc13 100755 --- a/.github/workflows/get_build_targets.sh +++ b/.github/workflows/get_build_targets.sh @@ -6,7 +6,7 @@ readonly SEARCH_FOLDER="//source/common/..." set -e -o pipefail function get_targets() { - # Comparing the PR HEAD with the upstream master HEAD. + # Comparing the PR HEAD with the upstream main HEAD. git diff --name-only HEAD FETCH_HEAD | while IFS= read -r line do # Only targets under those folders. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91642701329d9..9d0a4a639779a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ versioning guidelines: * Features may be marked as deprecated in a given versioned API at any point in time, but this may only be done when a replacement implementation and configuration path is available in Envoy on - master. Deprecators must implement a conversion from the deprecated configuration to the latest + main. Deprecators must implement a conversion from the deprecated configuration to the latest `vNalpha` (with the deprecated field) that Envoy uses internally. A field may be deprecated if this tool would be able to perform the conversion. For example, removing a field to describe HTTP/2 window settings is valid if a more comprehensive HTTP/2 protocol options field is being @@ -73,7 +73,7 @@ versioning guidelines: `envoy.features.enable_all_deprecated_features` is set to true. Finally, following the deprecation of the API major version where the field was first marked deprecated, the entire implementation code will be removed from the Envoy implementation. -* This policy means that organizations deploying master should have some time to get ready for +* This policy means that organizations deploying main should have some time to get ready for breaking changes at the next major API version. This is typically a window of at least 12 months or until the organization moves to the next major API version. * The breaking change policy also applies to source level extensions (e.g., filters). Code that @@ -144,7 +144,7 @@ versioning guidelines: * If your PR involves any changes to [envoy-filter-example](https://github.com/envoyproxy/envoy-filter-example) (for example making a new branch so that CI can pass) it is your responsibility to follow through with merging those - changes back to master once the CI dance is done. + changes back to main once the CI dance is done. * If your PR is a high risk change, the reviewer may ask that you runtime guard it. See the section on runtime guarding below. @@ -189,18 +189,18 @@ maintainer's discretion. Generally all runtime guarded features will be set true release is cut. Old code paths for refactors can be cleaned up after a release and there has been some production run time. Old code for behavioral changes will be deprecated after six months. Runtime features are set true by default by inclusion in -[source/common/runtime/runtime_features.cc](https://github.com/envoyproxy/envoy/blob/master/source/common/runtime/runtime_features.cc) +[source/common/runtime/runtime_features.cc](https://github.com/envoyproxy/envoy/blob/main/source/common/runtime/runtime_features.cc) There are four suggested options for testing new runtime features: -1. Create a per-test Runtime::LoaderSingleton as done in [DeprecatedFieldsTest.IndividualFieldDisallowedWithRuntimeOverride](https://github.com/envoyproxy/envoy/blob/master/test/common/protobuf/utility_test.cc) +1. Create a per-test Runtime::LoaderSingleton as done in [DeprecatedFieldsTest.IndividualFieldDisallowedWithRuntimeOverride](https://github.com/envoyproxy/envoy/blob/main/test/common/protobuf/utility_test.cc) 2. Create a [parameterized test](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#how-to-write-value-parameterized-tests) where the set up of the test sets the new runtime value explicitly to GetParam() as outlined in (1). 3. Set up integration tests with custom runtime defaults as documented in the - [integration test README](https://github.com/envoyproxy/envoy/blob/master/test/integration/README.md) + [integration test README](https://github.com/envoyproxy/envoy/blob/main/test/integration/README.md) 4. Run a given unit test with the new runtime value explicitly set true or false as done - for [runtime_flag_override_test](https://github.com/envoyproxy/envoy/blob/master/test/common/runtime/BUILD) + for [runtime_flag_override_test](https://github.com/envoyproxy/envoy/blob/main/test/common/runtime/BUILD) Runtime code is held to the same standard as regular Envoy code, so both the old path and the new should have 100% coverage both with the feature defaulting true diff --git a/DEPENDENCY_POLICY.md b/DEPENDENCY_POLICY.md index 588dad437ad23..abefac1fe550a 100644 --- a/DEPENDENCY_POLICY.md +++ b/DEPENDENCY_POLICY.md @@ -40,7 +40,7 @@ Dependency declarations must: and `urls` to reference the version. If you need to reference version `X.Y.Z` as `X_Y_Z`, this may appear in a string as `{underscore_version}`, similarly for `X-Y-Z` you can use `{dash_version}`. -* Versions should prefer release versions over master branch GitHub SHA tarballs. A comment is +* Versions should prefer release versions over main branch GitHub SHA tarballs. A comment is necessary if the latter is used. This comment should contain the reason that a non-release version is being used. * Provide accurate entries for `use_category`. Please think carefully about whether there are data @@ -112,7 +112,7 @@ basis: * Extension [CODEOWNERS](CODEOWNERS) should update extension specific dependencies. -Where possible, we prefer the latest release version for external dependencies, rather than master +Where possible, we prefer the latest release version for external dependencies, rather than main branch GitHub SHA tarballs. ## Dependency shepherds diff --git a/DEVELOPER.md b/DEVELOPER.md index 6786925fa7e8b..7a4ec69f54c6b 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,38 +1,38 @@ # Developer documentation Envoy is built using the Bazel build system. Our CI on Azure Pipelines builds, tests, and runs coverage against -all pull requests and the master branch. +all pull requests and the main branch. -To get started building Envoy locally, see the [Bazel quick start](https://github.com/envoyproxy/envoy/blob/master/bazel/README.md#quick-start-bazel-build-for-developers). -To run tests, there are Bazel [targets](https://github.com/envoyproxy/envoy/blob/master/bazel/README.md#testing-envoy-with-bazel) for Google Test. -To generate a coverage report, there is a [coverage build script](https://github.com/envoyproxy/envoy/blob/master/bazel/README.md#coverage-builds). +To get started building Envoy locally, see the [Bazel quick start](https://github.com/envoyproxy/envoy/blob/main/bazel/README.md#quick-start-bazel-build-for-developers). +To run tests, there are Bazel [targets](https://github.com/envoyproxy/envoy/blob/main/bazel/README.md#testing-envoy-with-bazel) for Google Test. +To generate a coverage report, there is a [coverage build script](https://github.com/envoyproxy/envoy/blob/main/bazel/README.md#coverage-builds). -If you plan to contribute to Envoy, you may find it useful to install the Envoy [development support toolchain](https://github.com/envoyproxy/envoy/blob/master/support/README.md), which helps automate parts of the development process, particularly those involving code review. +If you plan to contribute to Envoy, you may find it useful to install the Envoy [development support toolchain](https://github.com/envoyproxy/envoy/blob/main/support/README.md), which helps automate parts of the development process, particularly those involving code review. Below is a list of additional documentation to aid the development process: - [General build and installation documentation](https://www.envoyproxy.io/docs/envoy/latest/start/start) -- [Building and testing Envoy with Bazel](https://github.com/envoyproxy/envoy/blob/master/bazel/README.md) +- [Building and testing Envoy with Bazel](https://github.com/envoyproxy/envoy/blob/main/bazel/README.md) -- [Managing external dependencies with Bazel](https://github.com/envoyproxy/envoy/blob/master/bazel/EXTERNAL_DEPS.md) +- [Managing external dependencies with Bazel](https://github.com/envoyproxy/envoy/blob/main/bazel/EXTERNAL_DEPS.md) -- [Guide to Envoy Bazel rules (managing `BUILD` files)](https://github.com/envoyproxy/envoy/blob/master/bazel/DEVELOPER.md) +- [Guide to Envoy Bazel rules (managing `BUILD` files)](https://github.com/envoyproxy/envoy/blob/main/bazel/DEVELOPER.md) -- [Using Docker for building and testing](https://github.com/envoyproxy/envoy/tree/master/ci) +- [Using Docker for building and testing](https://github.com/envoyproxy/envoy/tree/main/ci) -- [Guide to contributing to Envoy](https://github.com/envoyproxy/envoy/blob/master/CONTRIBUTING.md) +- [Guide to contributing to Envoy](https://github.com/envoyproxy/envoy/blob/main/CONTRIBUTING.md) -- [Overview of Envoy's testing frameworks](https://github.com/envoyproxy/envoy/blob/master/test/README.md) +- [Overview of Envoy's testing frameworks](https://github.com/envoyproxy/envoy/blob/main/test/README.md) -- [Overview of how to write integration tests for new code](https://github.com/envoyproxy/envoy/blob/master/test/integration/README.md) +- [Overview of how to write integration tests for new code](https://github.com/envoyproxy/envoy/blob/main/test/integration/README.md) - [Envoy filter example project (how to consume and extend Envoy as a submodule)](https://github.com/envoyproxy/envoy-filter-example) -- [Performance testing Envoy with `tcmalloc`/`pprof`](https://github.com/envoyproxy/envoy/blob/master/bazel/PPROF.md) +- [Performance testing Envoy with `tcmalloc`/`pprof`](https://github.com/envoyproxy/envoy/blob/main/bazel/PPROF.md) And some documents on components of Envoy architecture: -- [Envoy flow control](https://github.com/envoyproxy/envoy/blob/master/source/docs/flow_control.md) +- [Envoy flow control](https://github.com/envoyproxy/envoy/blob/main/source/docs/flow_control.md) -- [Envoy's subset load balancer](https://github.com/envoyproxy/envoy/blob/master/source/docs/subset_load_balancer.md) +- [Envoy's subset load balancer](https://github.com/envoyproxy/envoy/blob/main/source/docs/subset_load_balancer.md) diff --git a/EXTENSION_POLICY.md b/EXTENSION_POLICY.md index 2f216813a6ff7..39d41f14f875f 100644 --- a/EXTENSION_POLICY.md +++ b/EXTENSION_POLICY.md @@ -15,7 +15,7 @@ The following procedure will be used when proposing new extensions for inclusion 2. All extensions must be sponsored by an existing maintainer. Sponsorship means that the maintainer will shepherd the extension through design/code reviews. Maintainers can self-sponsor extensions if they are going to write them, shepherd them, and maintain them. - + Sponsorship serves two purposes: * It ensures that the extension will ultimately meet the Envoy quality bar. * It makes sure that incentives are aligned and that extensions are not added to the repo without @@ -24,7 +24,7 @@ The following procedure will be used when proposing new extensions for inclusion *If sponsorship cannot be found from an existing maintainer, an organization can consider [doing the work to become a maintainer](./GOVERNANCE.md#process-for-becoming-a-maintainer) in order to be able to self-sponsor extensions.* - + 3. Each extension must have two reviewers proposed for reviewing PRs to the extension. Neither of the reviewers must be a senior maintainer. Existing maintainers (including the sponsor) and other contributors can count towards this number. The initial reviewers will be codified in the @@ -105,7 +105,7 @@ The `security_posture` is one of: * `unknown`: This is functionally equivalent to `requires_trusted_downstream_and_upstream`, but acts as a placeholder to allow us to identify extensions that need classifying. * `data_plane_agnostic`: Not relevant to data plane threats, e.g. stats sinks. - + An assessment of a robust security posture for an extension is subject to the following guidelines: * Does the extension have fuzz coverage? If it's only receiving fuzzing @@ -122,7 +122,7 @@ An assessment of a robust security posture for an extension is subject to the fo * Does the extension have active [CODEOWNERS](CODEOWNERS) who are willing to vouch for the robustness of the extension? * Is the extension absent a [low coverage - exception](https://github.com/envoyproxy/envoy/blob/master/test/per_file_coverage.sh#L5)? + exception](https://github.com/envoyproxy/envoy/blob/main/test/per_file_coverage.sh#L5)? The current stability and security posture of all extensions can be seen [here](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/threat_model#core-and-extensions). diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 1cb5157443eaf..593a1c02e25ac 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -38,7 +38,7 @@ The areas of specialization listed in [OWNERS.md](OWNERS.md) can be used to help with routing an issue/question to the right person. * Triage build issues - file issues for known flaky builds or bugs, and either fix or find someone - to fix any master build breakages. + to fix any main build breakages. * During GitHub issue triage, apply all applicable [labels](https://github.com/envoyproxy/envoy/labels) to each new issue. Labels are extremely useful for future issue follow up. Which labels to apply is somewhat subjective so just use your best judgment. A few of the most important labels that are @@ -77,7 +77,7 @@ or you can subscribe to the iCal feed [here](webcal://kubernetes.app.opsgenie.co "is:open is:issue milestone:[current milestone]" and either hold off until they are fixed or bump them to the next milestone. * Begin marshalling the ongoing PR flow in this repo. Ask maintainers to hold off merging any - particularly risky PRs until after the release is tagged. This is because we aim for master to be + particularly risky PRs until after the release is tagged. This is because we aim for main to be at release candidate quality at all times. * Do a final check of the [release notes](docs/root/version_history/current.rst): * Make any needed corrections (grammar, punctuation, formatting, etc.). @@ -91,7 +91,7 @@ or you can subscribe to the iCal feed [here](webcal://kubernetes.app.opsgenie.co release, please also make sure there's a stable maintainer signed up for next quarter, and the deadline for the next release is documented in the release schedule. * Get a review and merge. -* Wait for tests to pass on [master](https://dev.azure.com/cncf/envoy/_build). +* Wait for tests to pass on [main](https://dev.azure.com/cncf/envoy/_build). * Create a [tagged release](https://github.com/envoyproxy/envoy/releases). The release should start with "v" and be followed by the version number. E.g., "v1.6.0". **This must match the [VERSION](VERSION).** @@ -106,7 +106,7 @@ or you can subscribe to the iCal feed [here](webcal://kubernetes.app.opsgenie.co * Make sure we tweet the new release: either have Matt do it or email social@cncf.io and ask them to do an Envoy account post. * Do a new PR to setup the next version - * Update [VERSION](VERSION) to the next development release. E.g., "1.7.0-dev". + * Update [VERSION](VERSION) to the next development release. E.g., "1.7.0-dev". * `git mv docs/root/version_history/current.rst docs/root/version_history/v1.6.0.rst`, filling in the previous release version number in the filename and delete empty sections (like Incompatible Behavior Changes, Minor Bahavior Changes, etc). Add an entry for the new file in the `toctree` in diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 366209eed9299..962282df9920e 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -10,7 +10,7 @@ Thank you in advance for helping to keep Envoy secure. --> For an explanation of how to fill out the fields, please see the relevant section -in [PULL_REQUESTS.md](https://github.com/envoyproxy/envoy/blob/master/PULL_REQUESTS.md) +in [PULL_REQUESTS.md](https://github.com/envoyproxy/envoy/blob/main/PULL_REQUESTS.md) Commit Message: Additional Description: diff --git a/README.md b/README.md index 03c0aa0432d6f..1908d191101dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Envoy Logo](https://github.com/envoyproxy/artwork/blob/master/PNG/Envoy_Logo_Final_PANTONE.png) +![Envoy Logo](https://github.com/envoyproxy/artwork/blob/main/PNG/Envoy_Logo_Final_PANTONE.png) [Cloud-native high-performance edge/middle/service proxy](https://www.envoyproxy.io/) @@ -65,7 +65,7 @@ have prior experience. To get started: * [Beginner issues](https://github.com/envoyproxy/envoy/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner) * [Build/test quick start using docker](ci#building-and-running-tests-as-a-developer) * [Developer guide](DEVELOPER.md) -* Consider installing the Envoy [development support toolchain](https://github.com/envoyproxy/envoy/blob/master/support/README.md), which helps automate parts of the development process, particularly those involving code review. +* Consider installing the Envoy [development support toolchain](https://github.com/envoyproxy/envoy/blob/main/support/README.md), which helps automate parts of the development process, particularly those involving code review. * Please make sure that you let us know if you are working on an issue so we don't duplicate work! ## Community Meeting diff --git a/RELEASES.md b/RELEASES.md index 085c58a2ce526..a630f49633e78 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,7 +2,7 @@ ## Active development -Active development is happening on the `master` branch, and a new version is released from it +Active development is happening on the `main` branch, and a new version is released from it at the end of each quarter. ## Stable releases @@ -10,23 +10,23 @@ at the end of each quarter. Stable releases of Envoy include: * Extended maintenance window (any version released in the last 12 months). -* Security fixes backported from the `master` branch (including those deemed not worthy +* Security fixes backported from the `main` branch (including those deemed not worthy of creating a CVE). -* Stability fixes backported from the `master` branch (anything that can result in a crash, +* Stability fixes backported from the `main` branch (anything that can result in a crash, including crashes triggered by a trusted control plane). * Bugfixes, deemed worthwhile by the maintainers of stable releases. ### Hand-off Hand-off to the maintainers of stable releases happens after Envoy maintainers release a new -version from the `master` branch by creating a `vX.Y.0` tag and a corresponding `release/vX.Y` +version from the `main` branch by creating a `vX.Y.0` tag and a corresponding `release/vX.Y` branch, with merge permissions given to the release manager of stable releases, and CI configured to execute tests on it. ### Security releases Critical security fixes are owned by the Envoy security team, which provides fixes for the -`master` branch, and the latest release branch. Once those fixes are ready, the maintainers +`main` branch, and the latest release branch. Once those fixes are ready, the maintainers of stable releases backport them to the remaining supported stable releases. ### Backports @@ -37,7 +37,7 @@ by adding the `backport/review` or `backport/approved` label (this can be done u `/backport` command). Changes nominated by the change author and/or members of the Envoy community are evaluated for backporting on a case-by-case basis, and require approval from either the release manager of stable release, Envoy maintainers, or Envoy security team. Once approved, those fixes -are backported from the `master` branch to all supported stable branches by the maintainers of +are backported from the `main` branch to all supported stable branches by the maintainers of stable releases. New stable versions from non-critical security fixes are released on a regular schedule, initially aiming for the bi-weekly releases. diff --git a/SECURITY.md b/SECURITY.md index 34138877e6b30..53b4de82b7802 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -63,31 +63,31 @@ score >= 4; see below). If the fix relies on another upstream project's disclosu will adjust the process as well. We will work with the upstream project to fit their timeline and best protect our users. -### Released versions and master branch +### Released versions and main branch If the vulnerability affects the last point release version, e.g. 1.10, then the full security release process described in this document will be activated. A security point release will be -created for 1.10, e.g. 1.10.1, together with a fix to master if necessary. Older point releases, +created for 1.10, e.g. 1.10.1, together with a fix to main if necessary. Older point releases, e.g. 1.9, are not supported by the Envoy project and will not have any security release created. -If a security vulnerability affects only these older versions but not master or the last supported +If a security vulnerability affects only these older versions but not main or the last supported point release, the Envoy security team will share this information with the private distributor list, following the standard embargo process, but not create a security release. After the embargo expires, the vulnerability will be described as a GitHub issue. A CVE will be filed if warranted by severity. -If a vulnerability does not affect any point release but only master, additional caveats apply: +If a vulnerability does not affect any point release but only main, additional caveats apply: * If the issue is detected and a fix is available within 7 days of the introduction of the vulnerability, or the issue is deemed a low severity vulnerability by the Envoy maintainer and - security teams, the fix will be publicly reviewed and landed on master. If the severity is at least + security teams, the fix will be publicly reviewed and landed on main. If the severity is at least medium or at maintainer discretion a courtesy e-mail will be sent to envoy-users@googlegroups.com, envoy-dev@googlegroups.com, envoy-security-announce@googlegroups.com and cncf-envoy-distributors-announce@lists.cncf.io. * If the vulnerability has been in existence for more than 7 days and is medium or higher, we will activate the security release process. -We advise distributors and operators working from the master branch to allow at least 5 days soak +We advise distributors and operators working from the main branch to allow at least 5 days soak time after cutting a binary release before distribution or rollout, to allow time for our fuzzers to detect issues during their execution on ClusterFuzz. A soak period of 7 days provides an even stronger guarantee, since we will invoke the security release process for medium or higher severity issues @@ -181,7 +181,7 @@ patches, understand exact mitigation steps, etc. should be reserved for remotely exploitable or privilege escalation issues. Otherwise, this process can be skipped. - The Fix Lead will email the patches to cncf-envoy-distributors-announce@lists.cncf.io so - distributors can prepare builds to be available to users on the day of the issue's announcement. Any + distributors can prepare builds to be available to users on the day of the issue's announcement. Any patches against main will be updated and resent weekly. Distributors should read about the [Private Distributors List](#private-distributors-list) to find out the requirements for being added to this list. @@ -193,7 +193,7 @@ patches, understand exact mitigation steps, etc. - The maintainers will create a new patch release branch from the latest patch release tag + the fix from the security branch. As a practical example if v1.5.3 is the latest patch release in Envoy.git a new branch will be created called v1.5.4 which includes only patches required to fix the issue. -- The Fix Lead will cherry-pick the patches onto the master branch and all relevant release branches. +- The Fix Lead will cherry-pick the patches onto the main branch and all relevant release branches. The Fix Team will LGTM and merge. Maintainers will merge these PRs as quickly as possible. Changes shouldn't be made to the commits even for a typo in the CHANGELOG as this will change the git sha of the commits leading to confusion and potentially conflicts as the fix is cherry-picked around diff --git a/api/API_VERSIONING.md b/api/API_VERSIONING.md index 1757b0f2efcec..6952ca0954efc 100644 --- a/api/API_VERSIONING.md +++ b/api/API_VERSIONING.md @@ -105,7 +105,7 @@ Envoy will support at most three major versions of any API package at all times: for the next stable major version. This is only generated when the current stable major version requires a breaking change at the next cycle, e.g. a deprecation or field rename. This release candidate is mechanically generated via the - [protoxform](https://github.com/envoyproxy/envoy/tree/master/tools/protoxform) tool from the + [protoxform](https://github.com/envoyproxy/envoy/tree/main/tools/protoxform) tool from the current stable major version, making use of annotations such as `deprecated = true`. This is not a human editable artifact. @@ -161,7 +161,7 @@ methods, depending on whether the change is mechanical or manual. ## Mechanical breaking changes Field deprecations, renames, etc. are mechanical changes that are supported by the -[protoxform](https://github.com/envoyproxy/envoy/tree/master/tools/protoxform) tool. These are +[protoxform](https://github.com/envoyproxy/envoy/tree/main/tools/protoxform) tool. These are guided by [annotations](STYLE.md#api-annotations). ## Manual breaking changes diff --git a/api/CONTRIBUTING.md b/api/CONTRIBUTING.md index 01ba39b500b86..847cdf74dc55e 100644 --- a/api/CONTRIBUTING.md +++ b/api/CONTRIBUTING.md @@ -9,7 +9,7 @@ changes. They may be as part of a larger implementation PR. Please follow the st process for validating build/test sanity of `api/` before submitting a PR. *Note: New .proto files should be added to -[BUILD](https://github.com/envoyproxy/envoy/blob/master/api/versioning/BUILD) in order to get the RSTs generated.* +[BUILD](https://github.com/envoyproxy/envoy/blob/main/api/versioning/BUILD) in order to get the RSTs generated.* ## Documentation changes diff --git a/api/README.md b/api/README.md index fa6f3bb1eeba6..94fce1159e461 100644 --- a/api/README.md +++ b/api/README.md @@ -9,9 +9,9 @@ blog post for more information on the universal data plane concept. # Repository structure The API tree can be found at two locations: -* https://github.com/envoyproxy/envoy/tree/master/api - canonical read/write home for the APIs. +* https://github.com/envoyproxy/envoy/tree/main/api - canonical read/write home for the APIs. * https://github.com/envoyproxy/data-plane-api - read-only mirror of - https://github.com/envoyproxy/envoy/tree/master/api, providing the ability to consume the data + https://github.com/envoyproxy/envoy/tree/main/api, providing the ability to consume the data plane APIs without the Envoy implementation. # Further API reading diff --git a/api/envoy/api/v2/core/protocol.proto b/api/envoy/api/v2/core/protocol.proto index 9c47e388ee1af..ae1a86424cf07 100644 --- a/api/envoy/api/v2/core/protocol.proto +++ b/api/envoy/api/v2/core/protocol.proto @@ -201,7 +201,7 @@ message Http2ProtocolOptions { // Still under implementation. DO NOT USE. // // Allows metadata. See [metadata - // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index c294370366df6..80d971c1466ba 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -266,7 +266,7 @@ message Http2ProtocolOptions { // Still under implementation. DO NOT USE. // // Allows metadata. See [metadata - // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; diff --git a/api/envoy/config/core/v4alpha/protocol.proto b/api/envoy/config/core/v4alpha/protocol.proto index e33d83442afcd..60f0b3210805b 100644 --- a/api/envoy/config/core/v4alpha/protocol.proto +++ b/api/envoy/config/core/v4alpha/protocol.proto @@ -273,7 +273,7 @@ message Http2ProtocolOptions { // Still under implementation. DO NOT USE. // // Allows metadata. See [metadata - // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; diff --git a/bazel/EXTERNAL_DEPS.md b/bazel/EXTERNAL_DEPS.md index 4f66ef80eac8b..02ba28e30674a 100644 --- a/bazel/EXTERNAL_DEPS.md +++ b/bazel/EXTERNAL_DEPS.md @@ -78,7 +78,7 @@ documentation for further references. # Updating an external dependency version 1. Update the corresponding entry in -[the repository locations file.](https://github.com/envoyproxy/envoy/blob/master/bazel/repository_locations.bzl) +[the repository locations file.](https://github.com/envoyproxy/envoy/blob/main/bazel/repository_locations.bzl) 2. `bazel test //test/...` # Overriding an external dependency temporarily @@ -88,7 +88,7 @@ specifying Bazel option [`--override_repository`](https://docs.bazel.build/versions/master/command-line-reference.html) to point to a local copy. The option can used multiple times to override multiple dependencies. The name of the dependency can be found in -[the repository locations file.](https://github.com/envoyproxy/envoy/blob/master/bazel/repository_locations.bzl) +[the repository locations file.](https://github.com/envoyproxy/envoy/blob/main/bazel/repository_locations.bzl) The path of the local copy has to be absolute path. For repositories built by `envoy_cmake_external()` in `bazel/foreign_cc/BUILD`, diff --git a/bazel/PPROF.md b/bazel/PPROF.md index 74987b1986b48..2565a807b5e4f 100644 --- a/bazel/PPROF.md +++ b/bazel/PPROF.md @@ -29,7 +29,7 @@ specific place yourself. Static linking is already available (because of a `HeapProfilerDump()` call inside -[`Envoy::Profiler::Heap::stopProfiler())`](https://github.com/envoyproxy/envoy/blob/master/source/common/profiler/profiler.cc#L32-L39)). +[`Envoy::Profiler::Heap::stopProfiler())`](https://github.com/envoyproxy/envoy/blob/main/source/common/profiler/profiler.cc#L32-L39)). ### Compiling a statically-linked Envoy @@ -82,7 +82,7 @@ is controlled by `ProfilerStart()`/`ProfilerStop()`, and the [Gperftools Heap Profiler](https://gperftools.github.io/gperftools/heapprofile.html) is controlled by `HeapProfilerStart()`, `HeapProfilerStop()` and `HeapProfilerDump()`. -These functions are wrapped by Envoy objects defined in [`source/common/profiler/profiler.h`](https://github.com/envoyproxy/envoy/blob/master/source/common/profiler/profiler.h)). +These functions are wrapped by Envoy objects defined in [`source/common/profiler/profiler.h`](https://github.com/envoyproxy/envoy/blob/main/source/common/profiler/profiler.h)). To enable profiling programmatically: @@ -258,7 +258,7 @@ account other memory allocating functions. In case there is a need to measure how long a code path takes time to execute in Envoy you may resort to instrumenting the code with the -[performance annotations](https://github.com/envoyproxy/envoy/blob/master/source/common/common/perf_annotation.h). +[performance annotations](https://github.com/envoyproxy/envoy/blob/main/source/common/common/perf_annotation.h). There are two types of the annotations. The first one is used to measure operations limited by a common lexical scope. For example: diff --git a/bazel/README.md b/bazel/README.md index e4d4d4a9cf22a..8f9203d693d64 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -40,13 +40,13 @@ independently sourced, the following steps should be followed: This section describes how to and what dependencies to install to get started building Envoy with Bazel. If you would rather use a pre-build Docker image with required tools installed, skip to [this section](#building-envoy-with-the-ci-docker-image). -As a developer convenience, a [WORKSPACE](https://github.com/envoyproxy/envoy/blob/master/WORKSPACE) and +As a developer convenience, a [WORKSPACE](https://github.com/envoyproxy/envoy/blob/main/WORKSPACE) and [rules for building a recent -version](https://github.com/envoyproxy/envoy/blob/master/bazel/repositories.bzl) of the various Envoy +version](https://github.com/envoyproxy/envoy/blob/main/bazel/repositories.bzl) of the various Envoy dependencies are provided. These are provided as is, they are only suitable for development and testing purposes. The specific versions of the Envoy dependencies used in this build may not be up-to-date with the latest security patches. See -[this doc](https://github.com/envoyproxy/envoy/blob/master/bazel/EXTERNAL_DEPS.md#updating-an-external-dependency-version) +[this doc](https://github.com/envoyproxy/envoy/blob/main/bazel/EXTERNAL_DEPS.md#updating-an-external-dependency-version) for how to update or override dependencies. 1. Install external dependencies. @@ -84,14 +84,14 @@ for how to update or override dependencies. ``` Note: Either `libc++` or `libstdc++-7-dev` (or higher) must be installed. - + #### Config Flag Choices - Different [config](https://docs.bazel.build/versions/master/guide.html#--config) flags specify the compiler libraries: - + Different [config](https://docs.bazel.build/versions/master/guide.html#--config) flags specify the compiler libraries: + - `--config=libc++` means using `clang` + `libc++` - `--config=clang` means using `clang` + `libstdc++` - no config flag means using `gcc` + `libstdc++` - + ### macOS On macOS, you'll need to install several dependencies. This can be accomplished via [Homebrew](https://brew.sh/): @@ -256,7 +256,7 @@ MSYS2 or Git bash), run: ./ci/run_envoy_docker.sh './ci/windows_ci_steps.sh' ``` -See also the [documentation](https://github.com/envoyproxy/envoy/tree/master/ci) for developer use of the +See also the [documentation](https://github.com/envoyproxy/envoy/tree/main/ci) for developer use of the CI Docker image. ## Building Envoy with Remote Execution @@ -348,7 +348,7 @@ bazel test //test/... An individual test target can be run with a more specific Bazel [label](https://bazel.build/versions/master/docs/build-ref.html#Labels), e.g. to build and run only the units tests in -[test/common/http/async_client_impl_test.cc](https://github.com/envoyproxy/envoy/blob/master/test/common/http/async_client_impl_test.cc): +[test/common/http/async_client_impl_test.cc](https://github.com/envoyproxy/envoy/blob/main/test/common/http/async_client_impl_test.cc): ``` bazel test //test/common/http:async_client_impl_test @@ -749,7 +749,7 @@ test/run_envoy_bazel_coverage.sh **Note** that it is important to ensure that the versions of `clang`, `llvm-cov` and `llvm-profdata` are consistent and that they match the most recent Clang/LLVM toolchain version in use by Envoy (see the [build container -toolchain](https://github.com/envoyproxy/envoy-build-tools/blob/master/build_container/build_container_ubuntu.sh) for reference). +toolchain](https://github.com/envoyproxy/envoy-build-tools/blob/main/build_container/build_container_ubuntu.sh) for reference). The summary results are printed to the standard output and the full coverage report is available in `generated/coverage/coverage.html`. @@ -765,8 +765,8 @@ need to navigate down and open "coverage.html" but then you can navigate per nor have seen some issues with seeing the artifacts tab. If you can't see it, log out of Circle, and then log back in and it should start working. -The latest coverage report for master is available -[here](https://storage.googleapis.com/envoy-postsubmit/master/coverage/index.html). The latest fuzz coverage report for master is available [here](https://storage.googleapis.com/envoy-postsubmit/master/fuzz_coverage/index.html). +The latest coverage report for main is available +[here](https://storage.googleapis.com/envoy-postsubmit/main/coverage/index.html). The latest fuzz coverage report for main is available [here](https://storage.googleapis.com/envoy-postsubmit/main/fuzz_coverage/index.html). It's also possible to specialize the coverage build to a specified test or test dir. This is useful when doing things like exploring the coverage of a fuzzer over its corpus. This can be done by diff --git a/ci/README.md b/ci/README.md index 028e31263b302..9cde25759f94d 100644 --- a/ci/README.md +++ b/ci/README.md @@ -6,24 +6,24 @@ and an image based on Windows2019. ## Ubuntu Envoy image The Ubuntu based Envoy Docker image at [`envoyproxy/envoy-build:`](https://hub.docker.com/r/envoyproxy/envoy-build/) is used for CI checks, -where `` is specified in [`envoy_build_sha.sh`](https://github.com/envoyproxy/envoy/blob/master/ci/envoy_build_sha.sh). Developers -may work with the latest build image SHA in [envoy-build-tools](https://github.com/envoyproxy/envoy-build-tools/blob/master/toolchains/rbe_toolchains_config.bzl#L8) +where `` is specified in [`envoy_build_sha.sh`](https://github.com/envoyproxy/envoy/blob/main/ci/envoy_build_sha.sh). Developers +may work with the latest build image SHA in [envoy-build-tools](https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8) repo to provide a self-contained environment for building Envoy binaries and running tests that reflects the latest built Ubuntu Envoy image. Moreover, the Docker image at [`envoyproxy/envoy-dev:`](https://hub.docker.com/r/envoyproxy/envoy-dev/) is an image that has an Envoy binary at `/usr/local/bin/envoy`. -The `` corresponds to the master commit at which the binary was compiled. Lastly, `envoyproxy/envoy-dev:latest` contains an Envoy -binary built from the latest tip of master that passed tests. +The `` corresponds to the main commit at which the binary was compiled. Lastly, `envoyproxy/envoy-dev:latest` contains an Envoy +binary built from the latest tip of main that passed tests. ## Alpine Envoy image Minimal images based on Alpine Linux allow for quicker deployment of Envoy. The Alpine base image is only built with symbols stripped. To get the binary with symbols, use the corresponding Ubuntu based debug image. The image is pushed with two different tags: `` and `latest`. Parallel to the Ubuntu images above, `` corresponds to the -master commit at which the binary was compiled, and `latest` corresponds to a binary built from the latest tip of master that passed tests. +main commit at which the binary was compiled, and `latest` corresponds to a binary built from the latest tip of main that passed tests. ## Windows 2019 Envoy image The Windows 2019 based Envoy Docker image at [`envoyproxy/envoy-build-windows2019:`](https://hub.docker.com/r/envoyproxy/envoy-build-windows2019/) -is used for CI checks, where `` is specified in [`envoy_build_sha.sh`](https://github.com/envoyproxy/envoy/blob/master/ci/envoy_build_sha.sh). +is used for CI checks, where `` is specified in [`envoy_build_sha.sh`](https://github.com/envoyproxy/envoy/blob/main/ci/envoy_build_sha.sh). Developers may work with the most recent `envoyproxy/envoy-build-windows2019` image to provide a self-contained environment for building Envoy binaries and running tests that reflects the latest built Windows 2019 Envoy image. @@ -44,7 +44,7 @@ We use the Clang compiler for all Linux CI runs with tests. We have an additiona # C++ standard library As of November 2019 after [#8859](https://github.com/envoyproxy/envoy/pull/8859) the official released binary is -[linked against libc++ on Linux](https://github.com/envoyproxy/envoy/blob/master/bazel/README.md#linking-against-libc-on-linux). +[linked against libc++ on Linux](https://github.com/envoyproxy/envoy/blob/main/bazel/README.md#linking-against-libc-on-linux). To override the C++ standard library in your build, set environment variable `ENVOY_STDLIB` to `libstdc++` or `libc++` and run `./ci/do_ci.sh` as described below. @@ -98,7 +98,7 @@ For a debug version of the Envoy binary you can run: The build artifact can be found in `/tmp/envoy-docker-build/envoy/source/exe/envoy-debug` (or wherever `$ENVOY_DOCKER_BUILD_DIR` points). -To leverage a [bazel remote cache](https://github.com/envoyproxy/envoy/tree/master/bazel#advanced-caching-setup) add the http_remote_cache endpoint to +To leverage a [bazel remote cache](https://github.com/envoyproxy/envoy/tree/main/bazel#advanced-caching-setup) add the http_remote_cache endpoint to the BAZEL_BUILD_EXTRA_OPTIONS environment variable ```bash diff --git a/ci/api_mirror.sh b/ci/api_mirror.sh index 03e8ab85d80cb..8a3022b724318 100755 --- a/ci/api_mirror.sh +++ b/ci/api_mirror.sh @@ -3,8 +3,8 @@ set -e CHECKOUT_DIR=../data-plane-api -MAIN_BRANCH="refs/heads/master" -API_MAIN_BRANCH="master" +MAIN_BRANCH="refs/heads/main" +API_MAIN_BRANCH="main" if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then echo "Cloning..." diff --git a/ci/docker_ci.sh b/ci/docker_ci.sh index 3bd584923bdf9..ab1283745037f 100755 --- a/ci/docker_ci.sh +++ b/ci/docker_ci.sh @@ -101,11 +101,11 @@ push_images() { docker push "${BUILD_TAG}" } -MASTER_BRANCH="refs/heads/master" +MAIN_BRANCH="refs/heads/main" RELEASE_BRANCH_REGEX="^refs/heads/release/v.*" RELEASE_TAG_REGEX="^refs/tags/v.*" -# For master builds and release branch builds use the dev repo. Otherwise we assume it's a tag and +# For main builds and release branch builds use the dev repo. Otherwise we assume it's a tag and # we push to the primary repo. if [[ "${AZP_BRANCH}" =~ ${RELEASE_TAG_REGEX} ]]; then IMAGE_POSTFIX="" @@ -146,11 +146,11 @@ ENVOY_DOCKER_TAR="${ENVOY_DOCKER_IMAGE_DIRECTORY}/envoy-docker-images.tar.xz" echo "Saving built images to ${ENVOY_DOCKER_TAR}." docker save "${IMAGES_TO_SAVE[@]}" | xz -T0 -2 >"${ENVOY_DOCKER_TAR}" -# Only push images for master builds, release branch builds, and tag builds. -if [[ "${AZP_BRANCH}" != "${MASTER_BRANCH}" ]] && +# Only push images for main builds, release branch builds, and tag builds. +if [[ "${AZP_BRANCH}" != "${MAIN_BRANCH}" ]] && ! [[ "${AZP_BRANCH}" =~ ${RELEASE_BRANCH_REGEX} ]] && ! [[ "${AZP_BRANCH}" =~ ${RELEASE_TAG_REGEX} ]]; then - echo 'Ignoring non-master branch or tag for docker push.' + echo 'Ignoring non-main branch or tag for docker push.' exit 0 fi @@ -159,8 +159,8 @@ docker login -u "$DOCKERHUB_USERNAME" -p "$DOCKERHUB_PASSWORD" for BUILD_TYPE in "${BUILD_TYPES[@]}"; do push_images "${BUILD_TYPE}" "${DOCKER_IMAGE_PREFIX}${BUILD_TYPE}${IMAGE_POSTFIX}:${IMAGE_NAME}" - # Only push latest on master builds. - if [[ "${AZP_BRANCH}" == "${MASTER_BRANCH}" ]]; then + # Only push latest on main builds. + if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then docker tag "${DOCKER_IMAGE_PREFIX}${BUILD_TYPE}${IMAGE_POSTFIX}:${IMAGE_NAME}" "${DOCKER_IMAGE_PREFIX}${BUILD_TYPE}${IMAGE_POSTFIX}:latest" push_images "${BUILD_TYPE}" "${DOCKER_IMAGE_PREFIX}${BUILD_TYPE}${IMAGE_POSTFIX}:latest" fi diff --git a/ci/filter_example_mirror.sh b/ci/filter_example_mirror.sh index 8602b1677e4b9..a511227714035 100755 --- a/ci/filter_example_mirror.sh +++ b/ci/filter_example_mirror.sh @@ -4,8 +4,8 @@ set -e ENVOY_SRCDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd) CHECKOUT_DIR=../envoy-filter-example -MAIN_BRANCH="refs/heads/master" -FILTER_EXAMPLE_MAIN_BRANCH="master" +MAIN_BRANCH="refs/heads/main" +FILTER_EXAMPLE_MAIN_BRANCH="main" if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then echo "Cloning..." diff --git a/ci/go_mirror.sh b/ci/go_mirror.sh index 63f96d0d79697..96743eef62620 100755 --- a/ci/go_mirror.sh +++ b/ci/go_mirror.sh @@ -2,7 +2,7 @@ set -e -MAIN_BRANCH="refs/heads/master" +MAIN_BRANCH="refs/heads/main" # shellcheck source=ci/setup_cache.sh . "$(dirname "$0")"/setup_cache.sh diff --git a/ci/repokitteh/modules/newcontributor.star b/ci/repokitteh/modules/newcontributor.star index a192bd8613e73..6bc5fc7071b29 100644 --- a/ci/repokitteh/modules/newcontributor.star +++ b/ci/repokitteh/modules/newcontributor.star @@ -4,7 +4,7 @@ Hi @%s, welcome and thank you for your contribution. We will try to review your Pull Request as quickly as possible. -In the meantime, please take a look at the [contribution guidelines](https://github.com/envoyproxy/envoy/blob/master/CONTRIBUTING.md) if you have not done so already. +In the meantime, please take a look at the [contribution guidelines](https://github.com/envoyproxy/envoy/blob/main/CONTRIBUTING.md) if you have not done so already. """ diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index aea9dbc3f24fe..4a4bba6e4603f 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -19,7 +19,7 @@ if [ ! -d "${SOURCE_DIRECTORY}" ]; then fi if [[ "$BUILD_REASON" == "PullRequest" ]]; then - # non-master upload to the last commit sha (first 7 chars) in the developers branch + # non-main upload to the last commit sha (first 7 chars) in the developers branch UPLOAD_PATH="$(git log --pretty=%P -n 1 | cut -d' ' -f2 | head -c7)" else UPLOAD_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" diff --git a/docs/README.md b/docs/README.md index 5cd5444d670bc..27feeffc15648 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,7 @@ In both cases, the generated output can be found in `generated/docs`. ## Building in an existing Envoy development environment -If you have an [existing Envoy development environment](https://github.com/envoyproxy/envoy/tree/master/bazel#quick-start-bazel-build-for-developers), you should have the necessary dependencies and requirements and be able to build the documentation directly. +If you have an [existing Envoy development environment](https://github.com/envoyproxy/envoy/tree/main/bazel#quick-start-bazel-build-for-developers), you should have the necessary dependencies and requirements and be able to build the documentation directly. ```bash ./docs/build.sh @@ -45,7 +45,7 @@ To do this: 1. The docs are published to [docs/envoy/latest](https://github.com/envoyproxy/envoyproxy.github.io/tree/master/docs/envoy/latest) on every commit to master. This process is handled by Azure Pipelines with the - [`publish.sh`](https://github.com/envoyproxy/envoy/blob/master/docs/publish.sh) script. + [`publish.sh`](https://github.com/envoyproxy/envoy/blob/main/docs/publish.sh) script. 2. The docs are published to [docs/envoy](https://github.com/envoyproxy/envoyproxy.github.io/tree/master/docs/envoy) in a directory named after every tagged commit in this repo. Thus, on every tagged release there diff --git a/docs/publish.sh b/docs/publish.sh index 11b75f1b77c97..1c65cdecae50f 100755 --- a/docs/publish.sh +++ b/docs/publish.sh @@ -13,7 +13,7 @@ DOCS_DIR=generated/docs CHECKOUT_DIR=envoy-docs BUILD_SHA=$(git rev-parse HEAD) -MAIN_BRANCH="refs/heads/master" +MAIN_BRANCH="refs/heads/main" RELEASE_TAG_REGEX="^refs/tags/v.*" if [[ "${AZP_BRANCH}" =~ ${RELEASE_TAG_REGEX} ]]; then @@ -25,7 +25,7 @@ else exit 0 fi -DOCS_MAIN_BRANCH="master" +DOCS_MAIN_BRANCH="main" echo 'cloning' git clone git@github.com:envoyproxy/envoyproxy.github.io "${CHECKOUT_DIR}" -b "${DOCS_MAIN_BRANCH}" --depth 1 diff --git a/docs/root/version_history/v1.11.0.rst b/docs/root/version_history/v1.11.0.rst index 1bc4051b7da4a..10b48736b2db3 100644 --- a/docs/root/version_history/v1.11.0.rst +++ b/docs/root/version_history/v1.11.0.rst @@ -80,7 +80,7 @@ Changes * runtime: :ref:`Runtime Discovery Service (RTDS) ` support added to layered runtime configuration. * sandbox: added :ref:`CSRF sandbox `. * server: ``--define manual_stamp=manual_stamp`` was added to allow server stamping outside of binary rules. - more info in the `bazel docs `_. + more info in the `bazel docs `_. * server: added :ref:`server state ` statistic. * server: added :ref:`initialization_time_ms` statistic. * subset: added :ref:`list_as_any` option to diff --git a/docs/root/version_history/v1.14.0.rst b/docs/root/version_history/v1.14.0.rst index 2db9566c6788d..649a34d1cce34 100644 --- a/docs/root/version_history/v1.14.0.rst +++ b/docs/root/version_history/v1.14.0.rst @@ -187,6 +187,6 @@ Deprecated and the previous default can be enabled until the end of the deprecation period by enabling runtime feature `envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`. * The :ref:`source_ip ` field in - `RBAC `_ has been deprecated + `RBAC `_ has been deprecated in favor of :ref:`direct_remote_ip ` and :ref:`remote_ip `. diff --git a/docs/root/version_history/v1.4.0.rst b/docs/root/version_history/v1.4.0.rst index f940deb1b5a65..fb81055386273 100644 --- a/docs/root/version_history/v1.4.0.rst +++ b/docs/root/version_history/v1.4.0.rst @@ -56,7 +56,7 @@ Deprecated * The following log macros have been deprecated: `log_trace`, `log_debug`, `conn_log`, `conn_log_info`, `conn_log_debug`, `conn_log_trace`, `stream_log`, `stream_log_info`, `stream_log_debug`, `stream_log_trace`. For replacements, please see - `logger.h `_. + `logger.h `_. * The connectionId() and ssl() callbacks of StreamFilterCallbacks have been deprecated and replaced with a more general connection() callback, which, when not returning a nullptr, can be used to get the connection id and SSL connection from the returned Connection object pointer. diff --git a/docs/root/version_history/v1.8.0.rst b/docs/root/version_history/v1.8.0.rst index 5f05f5d7d8eb1..e99c0e2c459c5 100644 --- a/docs/root/version_history/v1.8.0.rst +++ b/docs/root/version_history/v1.8.0.rst @@ -106,24 +106,24 @@ Deprecated * Use of the legacy `ratelimit.proto `_ is deprecated, in favor of the proto defined in - `date-plane-api `_ + `date-plane-api `_ Prior to 1.8.0, Envoy can use either proto to send client requests to a ratelimit server with the use of the - `use_data_plane_proto` boolean flag in the `ratelimit configuration `_. + `use_data_plane_proto` boolean flag in the `ratelimit configuration `_. However, when using the deprecated client a warning is logged. * Use of the --v2-config-only flag. * Use of both `use_websocket` and `websocket_config` in - `route.proto `_ + `route.proto `_ is deprecated. Please use the new `upgrade_configs` in the - `HttpConnectionManager `_ + `HttpConnectionManager `_ instead. -* Use of the integer `percent` field in `FaultDelay `_ - and in `FaultAbort `_ is deprecated in favor +* Use of the integer `percent` field in `FaultDelay `_ + and in `FaultAbort `_ is deprecated in favor of the new `FractionalPercent` based `percentage` field. * Setting hosts via `hosts` field in `Cluster` is deprecated. Use `load_assignment` instead. * Use of `response_headers_to_*` and `request_headers_to_add` are deprecated at the `RouteAction` level. Please use the configuration options at the `Route` level. * Use of `runtime` in `RouteMatch`, found in - `route.proto `_. + `route.proto `_. Set the `runtime_fraction` field instead. -* Use of the string `user` field in `Authenticated` in `rbac.proto `_ +* Use of the string `user` field in `Authenticated` in `rbac.proto `_ is deprecated in favor of the new `StringMatcher` based `principal_name` field. diff --git a/docs/root/version_history/v1.9.0.rst b/docs/root/version_history/v1.9.0.rst index 54cd1e4783360..12614ca1497b0 100644 --- a/docs/root/version_history/v1.9.0.rst +++ b/docs/root/version_history/v1.9.0.rst @@ -102,12 +102,12 @@ Changes Deprecated ---------- -* Order of execution of the network write filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced `bugfix_reverse_write_filter_order` in `lds.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. -* Order of execution of the HTTP encoder filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced `bugfix_reverse_encode_order` in `http_connection_manager.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. +* Order of execution of the network write filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced `bugfix_reverse_write_filter_order` in `lds.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. +* Order of execution of the HTTP encoder filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced `bugfix_reverse_encode_order` in `http_connection_manager.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. * Use of the v1 REST_LEGACY ApiConfigSource is deprecated. * Use of std::hash in the ring hash load balancer is deprecated. -* Use of `rate_limit_service` configuration in the `bootstrap configuration `_ is deprecated. +* Use of `rate_limit_service` configuration in the `bootstrap configuration `_ is deprecated. * Use of `runtime_key` in `RequestMirrorPolicy`, found in - `route.proto `_ + `route.proto `_ is deprecated. Set the `runtime_fraction` field instead. -* Use of buffer filter `max_request_time` is deprecated in favor of the request timeout found in `HttpConnectionManager `_ +* Use of buffer filter `max_request_time` is deprecated in favor of the request timeout found in `HttpConnectionManager `_ diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 86c565dfb124e..afb1363d28840 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -61,7 +61,7 @@ Deprecation Policy ^^^^^^^^^^^^^^^^^^ As of release 1.3.0, Envoy will follow a -`Breaking Change Policy `_. +`Breaking Change Policy `_. Features in the deprecated list for each version have been DEPRECATED and will be removed in the specified release cycle. A logged warning diff --git a/examples/grpc-bridge/docker-compose-protos.yaml b/examples/grpc-bridge/docker-compose-protos.yaml index 42da7d7407c73..543fe4bf5aca7 100644 --- a/examples/grpc-bridge/docker-compose-protos.yaml +++ b/examples/grpc-bridge/docker-compose-protos.yaml @@ -1,7 +1,7 @@ version: "3.7" # This is the conversion from a script to a dockerized version of the script -# https://github.com/envoyproxy/envoy/blob/master/examples/grpc-bridge/service/script/gen +# https://github.com/envoyproxy/envoy/blob/main/examples/grpc-bridge/service/script/gen services: # $ docker run -ti -v $(pwd):/protos -v $(pwd)/stubs:/stubs grpc/go protoc --go_out=plugins=grpc:/stubs -I/protos /protos/kv.proto diff --git a/generated_api_shadow/envoy/api/v2/core/protocol.proto b/generated_api_shadow/envoy/api/v2/core/protocol.proto index 9c47e388ee1af..ae1a86424cf07 100644 --- a/generated_api_shadow/envoy/api/v2/core/protocol.proto +++ b/generated_api_shadow/envoy/api/v2/core/protocol.proto @@ -201,7 +201,7 @@ message Http2ProtocolOptions { // Still under implementation. DO NOT USE. // // Allows metadata. See [metadata - // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; diff --git a/generated_api_shadow/envoy/config/core/v3/protocol.proto b/generated_api_shadow/envoy/config/core/v3/protocol.proto index c294370366df6..80d971c1466ba 100644 --- a/generated_api_shadow/envoy/config/core/v3/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v3/protocol.proto @@ -266,7 +266,7 @@ message Http2ProtocolOptions { // Still under implementation. DO NOT USE. // // Allows metadata. See [metadata - // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; diff --git a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto index 2063d6d6793eb..c9fc21d4cfa4a 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto @@ -269,7 +269,7 @@ message Http2ProtocolOptions { // Still under implementation. DO NOT USE. // // Allows metadata. See [metadata - // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; diff --git a/include/envoy/http/filter.h b/include/envoy/http/filter.h index 3fb1210bb110f..31927e7c6bd89 100644 --- a/include/envoy/http/filter.h +++ b/include/envoy/http/filter.h @@ -49,7 +49,7 @@ enum class FilterHeadersStatus { // injectDecodedDataToFilterChain()/injectEncodedDataToFilterChain(), possibly multiple times // if the body needs to be divided into several chunks. The filter may need to handle // watermark events when injecting a body, see: - // https://github.com/envoyproxy/envoy/blob/master/source/docs/flow_control.md. + // https://github.com/envoyproxy/envoy/blob/main/source/docs/flow_control.md. // // The last call to inject data MUST have end_stream set to true to conclude the stream. // If the filter cannot provide a body the stream should be reset. diff --git a/security/email-templates.md b/security/email-templates.md index ec76e6d827487..3e6c178978661 100644 --- a/security/email-templates.md +++ b/security/email-templates.md @@ -36,14 +36,14 @@ Hello Envoy Distributors, The Envoy security team would like to provide advanced notice to the Envoy Private Distributors List of some details on the pending Envoy $VERSION security release, following the process described at -https://github.com/envoyproxy/envoy/blob/master/SECURITY.md. +https://github.com/envoyproxy/envoy/blob/main/SECURITY.md. This release will be made available on the $ORDINALDAY of $MONTH $YEAR at $PDTHOUR PDT ($GMTHOUR GMT). This release will fix $NUMDEFECTS security defect(s). The highest rated security defect is considered $SEVERITY severity. Below we provide details of these vulnerabilities under our embargo policy -(https://github.com/envoyproxy/envoy/blob/master/SECURITY.md#embargo-policy). +(https://github.com/envoyproxy/envoy/blob/main/SECURITY.md#embargo-policy). This information should be treated as confidential until public release by the Envoy maintainers on the Envoy GitHub. @@ -86,7 +86,7 @@ As a reminder, these patches are under embargo until $ORDINALDAY of $MONTH $YEAR at $PDTHOUR PDT ($GMTHOUR GMT). The information below should be treated as confidential and shared only on a need-to-know basis. The rules outline in our embargo policy -(https://github.com/envoyproxy/envoy/blob/master/SECURITY.md#embargo-policy) +(https://github.com/envoyproxy/envoy/blob/main/SECURITY.md#embargo-policy) still apply, and it is extremely important that any communication related to these CVEs are not forwarded further. diff --git a/security/postmortems/cve-2019-15225.md b/security/postmortems/cve-2019-15225.md index 1fbd027278229..7b3635ca70c15 100644 --- a/security/postmortems/cve-2019-15225.md +++ b/security/postmortems/cve-2019-15225.md @@ -130,7 +130,7 @@ amplify the effect of the O(n^2) process enough to produce a timeout. * The fixes for CVE-2019-15226 were straightforward and localized. * The security release occurred on time and followed the guidelines established in - https://github.com/envoyproxy/envoy/blob/master/SECURITY.md + https://github.com/envoyproxy/envoy/blob/main/SECURITY.md ### What went wrong diff --git a/security/postmortems/cve-2019-9900.md b/security/postmortems/cve-2019-9900.md index d6b6d38af5792..2766d1a647bae 100644 --- a/security/postmortems/cve-2019-9900.md +++ b/security/postmortems/cve-2019-9900.md @@ -303,7 +303,7 @@ All times US/Pacific number of users. 2019-04-04: -* 15:41 The Envoy master branch was frozen to prepare for the security release. PRs were rebased +* 15:41 The Envoy main branch was frozen to prepare for the security release. PRs were rebased against master and prepared for the release push. * 18:33 Envoy security team was contacted by a distributor who had noticed public visibility of binary images with the fix patch by other vendors. After discussion, we agreed on a general diff --git a/source/common/config/config_provider_impl.h b/source/common/config/config_provider_impl.h index 7334796b377aa..f1ecf3cf8932c 100644 --- a/source/common/config/config_provider_impl.h +++ b/source/common/config/config_provider_impl.h @@ -35,7 +35,7 @@ namespace Config { // and/or stream/request (if required by the configuration being processed). // // Dynamic configuration is distributed via xDS APIs (see -// https://github.com/envoyproxy/data-plane-api/blob/master/xds_protocol.rst). The framework exposed +// https://github.com/envoyproxy/data-plane-api/blob/main/xds_protocol.rst). The framework exposed // by these classes simplifies creation of client xDS implementations following a shared ownership // model, where according to the config source specification, a config subscription, config protos // received over the subscription and the subsequent config "implementation" (i.e., data structures diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index ff2cae86e2624..86660479c683a 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -554,7 +554,7 @@ void Utility::sendLocalReply(const bool& is_reset, const EncodeFunctions& encode // TODO(dio): Probably it is worth to consider caching the encoded message based on gRPC // status. // JsonFormatter adds a '\n' at the end. For header value, it should be removed. - // https://github.com/envoyproxy/envoy/blob/master/source/common/formatter/substitution_formatter.cc#L129 + // https://github.com/envoyproxy/envoy/blob/main/source/common/formatter/substitution_formatter.cc#L129 if (body_text[body_text.length() - 1] == '\n') { body_text = body_text.substr(0, body_text.length() - 1); } diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index f7799a7fc55f0..22529eadab7ea 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -246,12 +246,12 @@ void SymbolTableImpl::incRefCount(const StatName& stat_name) { ASSERT(decode_search != decode_map_.end(), "Please see " - "https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#" + "https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#" "debugging-symbol-table-assertions"); auto encode_search = encode_map_.find(decode_search->second->toStringView()); ASSERT(encode_search != encode_map_.end(), "Please see " - "https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#" + "https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#" "debugging-symbol-table-assertions"); ++encode_search->second.ref_count_; diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index dd66d137b491a..946a84ed874cf 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -141,7 +141,7 @@ using ParentHistogramImplSharedPtr = RefcountPtr; /** * Store implementation with thread local caching. For design details see - * https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md + * https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md */ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRoot { public: diff --git a/source/common/stats/utility.h b/source/common/stats/utility.h index 7ca48a2a10efa..90c1aa54ff10f 100644 --- a/source/common/stats/utility.h +++ b/source/common/stats/utility.h @@ -68,7 +68,7 @@ class Utility { * Creates a nested scope from a vector of tokens which are used to create the * name. The tokens can be specified as DynamicName or StatName. For * tokens specified as DynamicName, a dynamic StatName will be created. See - * https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#dynamic-stat-tokens + * https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#dynamic-stat-tokens * for more detail on why symbolic StatNames are preferred when possible. * * See also scopeFromStatNames, which is slightly faster but does not allow @@ -97,7 +97,7 @@ class Utility { * Creates a counter from a vector of tokens which are used to create the * name. The tokens can be specified as DynamicName or StatName. For * tokens specified as DynamicName, a dynamic StatName will be created. See - * https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#dynamic-stat-tokens + * https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#dynamic-stat-tokens * for more detail on why symbolic StatNames are preferred when possible. * * See also counterFromStatNames, which is slightly faster but does not allow @@ -130,7 +130,7 @@ class Utility { * Creates a gauge from a vector of tokens which are used to create the * name. The tokens can be specified as DynamicName or StatName. For * tokens specified as DynamicName, a dynamic StatName will be created. See - * https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#dynamic-stat-tokens + * https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#dynamic-stat-tokens * for more detail on why symbolic StatNames are preferred when possible. * * See also gaugeFromStatNames, which is slightly faster but does not allow @@ -167,7 +167,7 @@ class Utility { * Creates a histogram from a vector of tokens which are used to create the * name. The tokens can be specified as DynamicName or StatName. For * tokens specified as DynamicName, a dynamic StatName will be created. See - * https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#dynamic-stat-tokens + * https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#dynamic-stat-tokens * for more detail on why symbolic StatNames are preferred when possible. * * See also histogramFromStatNames, which is slightly faster but does not allow @@ -204,7 +204,7 @@ class Utility { * Creates a TextReadout from a vector of tokens which are used to create the * name. The tokens can be specified as DynamicName or StatName. For * tokens specified as DynamicName, a dynamic StatName will be created. See - * https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#dynamic-stat-tokens + * https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#dynamic-stat-tokens * for more detail on why symbolic StatNames are preferred when possible. * * See also TextReadoutFromStatNames, which is slightly faster but does not allow diff --git a/source/docs/network_filter_fuzzing.md b/source/docs/network_filter_fuzzing.md index 1777c77767745..8e40210cac24e 100644 --- a/source/docs/network_filter_fuzzing.md +++ b/source/docs/network_filter_fuzzing.md @@ -1,17 +1,17 @@ # Generic network-level filter fuzzers overview -Network filters need to be fuzzed. Filters come in two flavors, each with their own fuzzer. Read filters should be added into the [Generic ReadFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc). Write Filters should added into the [Generic WriteFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_writefilter_fuzz_test.cc). Some filters are both raed and write filters: They should be added into both fuzzers. +Network filters need to be fuzzed. Filters come in two flavors, each with their own fuzzer. Read filters should be added into the [Generic ReadFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc). Write Filters should added into the [Generic WriteFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_writefilter_fuzz_test.cc). Some filters are both raed and write filters: They should be added into both fuzzers. Before adding the new filter into the fuzzers, please make sure the filter is designed to accept untrusted inputs, or ready to be hardened to accept untrusted inputs. # Add a new ReadFilter into Generic Readfilter Fuzzer ## Step1. Make sure the filter can be linked into the fuzzer -There are two ways to link it into the fuzzer. -* [Recommended] In the file [extensions_build_config.bzl](https://github.com/envoyproxy/envoy/blob/master/source/extensions/extensions_build_config.bzl), the name of the filter should have a prefix `envoy.filters.network`. If it has such a prefix, the filter will be automatically linked into Generic ReadFilter Fuzzer. -* [Not recommended]If for some reasons the filter's name doesn't have such a prefix, the config of the filter must be added into the `deps` field of `network_readfilter_fuzz_test` module in the file [BUILD](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/BUILD). +There are two ways to link it into the fuzzer. +* [Recommended] In the file [extensions_build_config.bzl](https://github.com/envoyproxy/envoy/blob/main/source/extensions/extensions_build_config.bzl), the name of the filter should have a prefix `envoy.filters.network`. If it has such a prefix, the filter will be automatically linked into Generic ReadFilter Fuzzer. +* [Not recommended]If for some reasons the filter's name doesn't have such a prefix, the config of the filter must be added into the `deps` field of `network_readfilter_fuzz_test` module in the file [BUILD](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/BUILD). ### Step2. Add the filter name into supported_filter_names -In [uber_per_readfilter.cc](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/uber_per_readfilter.cc), add the filter name into the vector `supported_filter_names` in method `UberFilterFuzzer::filterNames()`. +In [uber_per_readfilter.cc](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/uber_per_readfilter.cc), add the filter name into the vector `supported_filter_names` in method `UberFilterFuzzer::filterNames()`. ``` const std::vector supported_filter_names = { ... @@ -22,7 +22,7 @@ NetworkFilterNames::get().ExtAuthorization, NetworkFilterNames::get().TheNewFilt # Add a new WriteFilter into Generic Writefilter Fuzzer ## Step 1. Make sure the filter can be linked into the fuzzer -For WriteFilter, the config of the filter must be added into the `deps` field of `network_writefilter_fuzz_test` module in the file [BUILD](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/BUILD). +For WriteFilter, the config of the filter must be added into the `deps` field of `network_writefilter_fuzz_test` module in the file [BUILD](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/BUILD). ``` envoy_cc_fuzz_test( name = "network_writefilter_fuzz_test", @@ -43,7 +43,7 @@ envoy_cc_fuzz_test( ) ``` ## Step 2. Add the filter name into supported_filter_names -In [uber_per_writefilter.cc](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/uber_per_writefilter.cc), add the filter name into the vector `supported_filter_names` in method `UberWriteFilterFuzzer::filterNames()`. +In [uber_per_writefilter.cc](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/uber_per_writefilter.cc), add the filter name into the vector `supported_filter_names` in method `UberWriteFilterFuzzer::filterNames()`. ``` const std::vector supported_filter_names = { ... @@ -54,8 +54,8 @@ const std::vector supported_filter_names = { # Add test cases into corpus Good test cases can provide good examples for fuzzers to find more paths in the code, increase the coverage and help find bugs more efficiently. -Each test case is a file under the folder [network_readfilter_corpus](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_readfilter_corpus) or [network_writefilter_corpus](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_writefilter_corpus). It consists of two parts: `config` and `actions`. -`config` is the protobuf to instantiate a filter, and `actions` are sequences of actions to take in order to test the filter. +Each test case is a file under the folder [network_readfilter_corpus](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_readfilter_corpus) or [network_writefilter_corpus](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_writefilter_corpus). It consists of two parts: `config` and `actions`. +`config` is the protobuf to instantiate a filter, and `actions` are sequences of actions to take in order to test the filter. An example for testing MongoProxy filter: ``` config { @@ -80,14 +80,14 @@ actions { } } ``` -* `config.name` is the name of the filter. -* `config.typed_config.type_url` is the type url of the filter config API. +* `config.name` is the name of the filter. +* `config.typed_config.type_url` is the type url of the filter config API. * `config.typed_config.value` is the serialized string of the config protobuf, and in C++ we can call`config.SerializeAsString()` to obtain this. This string may contain special characters. Recommend using octal or hexadecimal sequence for the string. * `actions.on_data.data` (or `actions.on_write.data`) is the buffer parameter `data`(in string format) for testing ReadFilter's method onData() (or for testing WriteFilter's method onWrite()). This string may contain special characters. Recommend using octal or hexadecimal sequence for the string. * `actions.on_data.end_stream` (or `actions.on_write.end_stream`) is the bool parameter `end_stream` for testing ReadFilter's method onData() (or for testing WriteFilter's method onWrite()). * `actions.on_new_connection` is an action to call `onNewConnection` method of a ReadFilter. * `actions.advance_time.milliseconds` is the duration in milliseconds for the simulatedSystemTime to advance by. -For more details, see the APIs for [ReadFilter Fuzz Testcase](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.proto) and [WriteFilter Fuzz Testcase](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_writefilter_fuzz.proto). +For more details, see the APIs for [ReadFilter Fuzz Testcase](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.proto) and [WriteFilter Fuzz Testcase](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_writefilter_fuzz.proto). ## Convert a unit test case to a fuzz test case manually This section explains an approach to generate a corpus from unit tests. It is an optional step for users who want to generate the highest possible coverage. @@ -105,12 +105,12 @@ static std::string toOct(const std::string& source, const std::string& info) { } ``` In the unit test code, we temporarily add a function(finally we will remove it) like the above one. -Then we can fill in `config.typed_config.value` with the value returned or printed by -```toOct(config.SerializeAsString(), "config serialized string: ")``` +Then we can fill in `config.typed_config.value` with the value returned or printed by +```toOct(config.SerializeAsString(), "config serialized string: ")``` where `config` is the config protobuf in a unit test case. -We can also fill in `actions.on_data.data` or `actions.on_write.data` with the value returned or printed by -```toOct(buffer.toString(), "buffer:")``` +We can also fill in `actions.on_data.data` or `actions.on_write.data` with the value returned or printed by +```toOct(buffer.toString(), "buffer:")``` where `buffer` is the buffer to pass to `onData()` or `onWrite()` in a unit test case. Please note that the two fuzzers use the "real input" for fuzzers. If you are using a mock decoder and pass an empty buffer to onData(), that test case won't help cover much code in the fuzzers(but the config protobuf is still helpful). diff --git a/source/docs/repokitteh.md b/source/docs/repokitteh.md index 0d07ba9ddc26c..f8a442798afe0 100644 --- a/source/docs/repokitteh.md +++ b/source/docs/repokitteh.md @@ -8,7 +8,7 @@ The application is installed on specific GitHub repositories and interacts with these by receiving webhooks and making GitHub API calls. A root `repokitteh.star` script tells the application what to do based on the webhook received. ## Integration with Envoy -The file [repokitteh.star](https://github.com/envoyproxy/envoy/blob/master/repokitteh.star), which resides in the root of the Envoy repository tells RepoKitteh what functionality to use. The file is written in the [Starlark language](https://github.com/bazelbuild/starlark/), which is a Python dialect with well defined threading and hermeticity guarantees. +The file [repokitteh.star](https://github.com/envoyproxy/envoy/blob/main/repokitteh.star), which resides in the root of the Envoy repository tells RepoKitteh what functionality to use. The file is written in the [Starlark language](https://github.com/bazelbuild/starlark/), which is a Python dialect with well defined threading and hermeticity guarantees. For example, the statement ``` @@ -75,7 +75,7 @@ Sets the label `waiting:any` on a PR. When a new commit is pushed or any comment [Demo PR](https://github.com/envoyproxy/envoybot/pull/15) -### [Azure Pipelines Retest](https://github.com/envoyproxy/envoy/blob/master/ci/repokitteh/modules/azure_pipelines.star) +### [Azure Pipelines Retest](https://github.com/envoyproxy/envoy/blob/main/ci/repokitteh/modules/azure_pipelines.star) Restart failed Azure pipelines. Example: diff --git a/source/docs/stats.md b/source/docs/stats.md index af0b0f7eca39b..de9a92048f696 100644 --- a/source/docs/stats.md +++ b/source/docs/stats.md @@ -25,7 +25,7 @@ https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 for details. This requires lock-free access to stats on the fast path -- when proxying requests. For stats, this is implemented in -[ThreadLocalStore](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/thread_local_store.h), supporting the following features: +[ThreadLocalStore](https://github.com/envoyproxy/envoy/blob/main/source/common/stats/thread_local_store.h), supporting the following features: * Thread local per scope stat caching. * Overlapping scopes with proper reference counting (2 scopes with the same name will point to @@ -94,12 +94,12 @@ maintain data continuity as scopes are re-created during operation. Stat names are replicated in several places in various forms. * Held with the stat values, in `CounterImpl`, `GaugeImpl` and `TextReadoutImpl`, which are defined in - [allocator_impl.cc](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/allocator_impl.cc) - * In [MetricImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/metric_impl.h) + [allocator_impl.cc](https://github.com/envoyproxy/envoy/blob/main/source/common/stats/allocator_impl.cc) + * In [MetricImpl](https://github.com/envoyproxy/envoy/blob/main/source/common/stats/metric_impl.h) in a transformed state, with tags extracted into vectors of name/value strings. * In static strings across the codebase where stats are referenced * In a [set of - regexes](https://github.com/envoyproxy/envoy/blob/master/source/common/config/well_known_names.cc) + regexes](https://github.com/envoyproxy/envoy/blob/main/source/common/config/well_known_names.cc) used to perform tag extraction. There are stat maps in `ThreadLocalStore` for capturing all stats in a scope, @@ -127,7 +127,7 @@ The transformation between flattened string and symbolized form is CPU-intensive at scale. It requires parsing, encoding, and lookups in a shared map, which must be mutex-protected. To avoid adding latency and CPU overhead while serving requests, the tokens can be symbolized and saved in context classes, such as -[Http::CodeStatsImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/http/codes.h). +[Http::CodeStatsImpl](https://github.com/envoyproxy/envoy/blob/main/source/common/http/codes.h). Symbolization can occur on startup or when new hosts or clusters are configured dynamically. Users of stats that are allocated dynamically per cluster, host, etc, must explicitly store partial stat-names their class instances, which later @@ -184,7 +184,7 @@ showing the memory layout for a few scenarios of constructing and joining symbol There are several ways to create hot-path contention looking up stats by name, and there is no bulletproof way to prevent it from occurring. - * The [stats macros](https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/stats_macros.h) may be used in a data structure which is constructed in response to requests. In this + * The [stats macros](https://github.com/envoyproxy/envoy/blob/main/include/envoy/stats/stats_macros.h) may be used in a data structure which is constructed in response to requests. In this scenario, consider factoring out the symbolization phase using MAKE_STAT_NAMES_STRUCT in a factory or context during startup, and using MAKE_STATS_STRUCT in the hot-path and during control-plane updates, so that we do not need to take symbol-table locks. As an example, see @@ -210,7 +210,7 @@ SymbolTableImpl::Encoding | | Helper class for incrementally encoding strings in StatName | | Provides an API and a view into a StatName (dynamic or symbolized). Like absl::string_view, the backing store must be separately maintained. StatNameStorageBase | | Holds storage (an array of bytes) for a dynamic or symbolized StatName StatNameStorage | StatNameStorageBase | Holds storage for a symbolized StatName. Must be explicitly freed (not just destructed). -StatNameManagedStorage | StatNameStorage | Like StatNameStorage, but is 8 bytes larger, and can be destructed without free(). +StatNameManagedStorage | StatNameStorage | Like StatNameStorage, but is 8 bytes larger, and can be destructed without free(). StatNameDynamicStorage | StatNameStorageBase | Holds StatName storage for a dynamic (not symbolized) StatName. StatNamePool | | Holds backing store for any number of symbolized StatNames. StatNameDynamicPool | | Holds backing store for any number of dynamic StatNames. @@ -251,7 +251,7 @@ deployments with O(10k) clusters or hosts. To improve visibility for this memory growth, there are [memory-usage integration -tests](https://github.com/envoyproxy/envoy/blob/master/test/integration/stats_integration_test.cc). +tests](https://github.com/envoyproxy/envoy/blob/main/test/integration/stats_integration_test.cc). If a PR fails the tests in that file due to unexpected memory consumption, it gives the author and reviewer an opportunity to consider the cost/value of the @@ -275,8 +275,8 @@ If you are visiting this section because you saw a message like: ```bash [...][16][critical][assert] [source/common/stats/symbol_table_impl.cc:251] assert failure: -decode_search != decode_map_.end(). Details: Please see -https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#debugging-symbol-table-assertions +decode_search != decode_map_.end(). Details: Please see +https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#debugging-symbol-table-assertions ``` then you have come to the right place. diff --git a/test/README.md b/test/README.md index 2746efe98c8db..85f12625902f1 100644 --- a/test/README.md +++ b/test/README.md @@ -10,7 +10,7 @@ various classes, macros, and matchers that Envoy uses from those frameworks. Envoy contains an integration testing framework, for testing downstream-Envoy-upstream communication. -[See the framework's README for more information.](https://github.com/envoyproxy/envoy/blob/master/test/integration/README.md) +[See the framework's README for more information.](https://github.com/envoyproxy/envoy/blob/main/test/integration/README.md) ## Custom matchers @@ -93,7 +93,7 @@ EXPECT_THAT(response->headers(), IsSupersetOfHeaders(required_headers)); ## Controlling time in tests In Envoy production code, time and timers are managed via -[`Event::TimeSystem`](https://github.com/envoyproxy/envoy/blob/master/include/envoy/event/timer.h), +[`Event::TimeSystem`](https://github.com/envoyproxy/envoy/blob/main/include/envoy/event/timer.h), which provides a mechanism for querying the time and setting up time-based callbacks. Bypassing this abstraction in Envoy code is flagged as a format violation in CI. @@ -127,7 +127,7 @@ Envoy uses [Google Benchmark](https://github.com/google/benchmark/) for microbenchmarks. There are custom bazel rules, `envoy_cc_benchmark_binary` and `envoy_benchmark_test`, to execute them locally and in CI environments respectively. `envoy_benchmark_test` rules call the benchmark binary from a -[script](https://github.com/envoyproxy/envoy/blob/master/bazel/test_for_benchmark_wrapper.sh) +[script](https://github.com/envoyproxy/envoy/blob/main/bazel/test_for_benchmark_wrapper.sh) which runs the benchmark with a minimal number of iterations and skipping expensive benchmarks to quickly verify that the binary is able to run to completion. In order to collect meaningful bechmarks, `bazel run -c opt` the @@ -135,4 +135,4 @@ benchmark binary target on a quiescent machine. If you would like to detect when your benchmark test is running under the wrapper, call -[`Envoy::benchmark::skipExpensiveBechmarks()`](https://github.com/envoyproxy/envoy/blob/master/test/benchmark/main.h). +[`Envoy::benchmark::skipExpensiveBechmarks()`](https://github.com/envoyproxy/envoy/blob/main/test/benchmark/main.h). diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc index 1d109d74bda4f..d2e37392b296e 100644 --- a/test/common/upstream/health_check_fuzz.cc +++ b/test/common/upstream/health_check_fuzz.cc @@ -304,7 +304,7 @@ void TcpHealthCheckFuzz::raiseEvent(const Network::ConnectionEvent& event_type, } // In the specific case of: - // https://github.com/envoyproxy/envoy/blob/master/source/common/upstream/health_checker_impl.cc#L489 + // https://github.com/envoyproxy/envoy/blob/main/source/common/upstream/health_checker_impl.cc#L489 // This blows away client, should create a new one if (event_type == Network::ConnectionEvent::Connected && empty_response_) { ENVOY_LOG_MISC(trace, "Will create client from connected event and empty response."); diff --git a/test/extensions/filters/network/common/fuzz/README.md b/test/extensions/filters/network/common/fuzz/README.md index 21181c8402a4f..d35fb5d92dbcf 100644 --- a/test/extensions/filters/network/common/fuzz/README.md +++ b/test/extensions/filters/network/common/fuzz/README.md @@ -1,2 +1,2 @@ -Network filters need to be fuzzed. Filters come in two flavors, each with their own fuzzer. Read filters should be added into the [Generic ReadFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc). Write Filters should added into the [Generic WriteFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/master/test/extensions/filters/network/common/fuzz/network_writefilter_fuzz_test.cc). Some filters are both raed and write filters: They should be added into both fuzzers. -To add a new filter into generic network level filter fuzzers, see the [doc](https://github.com/envoyproxy/envoy/blob/master/source/docs/network_filter_fuzzing.md). \ No newline at end of file +Network filters need to be fuzzed. Filters come in two flavors, each with their own fuzzer. Read filters should be added into the [Generic ReadFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc). Write Filters should added into the [Generic WriteFilter Fuzzer](https://github.com/envoyproxy/envoy/blob/main/test/extensions/filters/network/common/fuzz/network_writefilter_fuzz_test.cc). Some filters are both raed and write filters: They should be added into both fuzzers. +To add a new filter into generic network level filter fuzzers, see the [doc](https://github.com/envoyproxy/envoy/blob/main/source/docs/network_filter_fuzzing.md). \ No newline at end of file diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index f2b7bee41230d..7a6981a5d9a39 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -279,7 +279,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSize) { // vary. // // If you encounter a failure here, please see - // https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#stats-memory-tests + // https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#stats-memory-tests // for details on how to fix. // // We only run the exact test for ipv6 because ipv4 in some cases may allocate a @@ -325,7 +325,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeHostSizeWithStats) { // at the logs. // // If you encounter a failure here, please see - // https://github.com/envoyproxy/envoy/blob/master/source/docs/stats.md#stats-memory-tests + // https://github.com/envoyproxy/envoy/blob/main/source/docs/stats.md#stats-memory-tests // for details on how to fix. // // We only run the exact test for ipv6 because ipv4 in some cases may allocate a diff --git a/tools/api/generate_go_protobuf.py b/tools/api/generate_go_protobuf.py index 5b25de2dbb0a4..23ef195c84cd9 100755 --- a/tools/api/generate_go_protobuf.py +++ b/tools/api/generate_go_protobuf.py @@ -16,7 +16,7 @@ IMPORT_BASE = 'github.com/envoyproxy/go-control-plane' OUTPUT_BASE = 'build_go' REPO_BASE = 'go-control-plane' -BRANCH = 'master' +BRANCH = 'main' MIRROR_MSG = 'Mirrored from envoyproxy/envoy @ ' USER_NAME = 'go-control-plane(Azure Pipelines)' USER_EMAIL = 'go-control-plane@users.noreply.github.com' diff --git a/tools/git/last_github_commit.sh b/tools/git/last_github_commit.sh index 9746d259ac3ba..2ca2fb0fda29e 100755 --- a/tools/git/last_github_commit.sh +++ b/tools/git/last_github_commit.sh @@ -1,8 +1,8 @@ #!/bin/bash -# Looking back from HEAD, find the first commit that was merged onto master by GitHub. This is +# Looking back from HEAD, find the first commit that was merged onto main by GitHub. This is # likely the last non-local change on a given branch. There may be some exceptions for this -# heuristic, e.g. when patches are manually merged for security fixes on master, but this is very +# heuristic, e.g. when patches are manually merged for security fixes on main, but this is very # rare. git rev-list --no-merges --committer="GitHub " --max-count=1 HEAD diff --git a/tools/proto_format/proto_sync.py b/tools/proto_format/proto_sync.py index f6fc70a13a665..adbaa011257dc 100755 --- a/tools/proto_format/proto_sync.py +++ b/tools/proto_format/proto_sync.py @@ -433,7 +433,7 @@ def Sync(api_root, mode, labels, shadow): if deleted_files: print('The following files will be deleted: %s' % sorted(deleted_files)) print( - 'If this is not intended, please see https://github.com/envoyproxy/envoy/blob/master/api/STYLE.md#adding-an-extension-configuration-to-the-api.' + 'If this is not intended, please see https://github.com/envoyproxy/envoy/blob/main/api/STYLE.md#adding-an-extension-configuration-to-the-api.' ) if input('Delete files? [yN] ').strip().lower() == 'y': subprocess.run(['patch', '-p1'], input=diff, cwd=str(api_root_path.resolve())) From 3fce6a9fb81cda5c69443d45eb1e5956e81b0e27 Mon Sep 17 00:00:00 2001 From: Christoph Pakulski Date: Wed, 3 Mar 2021 01:29:11 -0500 Subject: [PATCH 10/28] backport to 1.17: (#15155) outlier_detector: accept large base_ejection_time when max_ejection_time not specified (#14962) Changed logic in config verification when max_ejection_time is not specified and base_ejection_time is larger than max_ejection_time's default. Signed-off-by: Christoph Pakulski --- .../config/cluster/v3/outlier_detection.proto | 4 +- .../cluster/v4alpha/outlier_detection.proto | 4 +- .../config/cluster/v3/outlier_detection.proto | 4 +- .../cluster/v4alpha/outlier_detection.proto | 4 +- .../common/upstream/outlier_detection_impl.cc | 7 +++- .../common/upstream/outlier_detection_impl.h | 40 +++++++++---------- .../upstream/outlier_detection_impl_test.cc | 20 ++++++++++ 7 files changed, 53 insertions(+), 30 deletions(-) diff --git a/api/envoy/config/cluster/v3/outlier_detection.proto b/api/envoy/config/cluster/v3/outlier_detection.proto index 9bb5633e6269b..e69b446918543 100644 --- a/api/envoy/config/cluster/v3/outlier_detection.proto +++ b/api/envoy/config/cluster/v3/outlier_detection.proto @@ -151,7 +151,7 @@ message OutlierDetection { google.protobuf.UInt32Value failure_percentage_request_volume = 20; // The maximum time that a host is ejected for. See :ref:`base_ejection_time` - // for more information. - // Defaults to 300000ms or 300s. + // for more information. If not specified, the default value (300000ms or 300s) or + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/api/envoy/config/cluster/v4alpha/outlier_detection.proto b/api/envoy/config/cluster/v4alpha/outlier_detection.proto index 9b2efeb53146d..d4e76b4f135ae 100644 --- a/api/envoy/config/cluster/v4alpha/outlier_detection.proto +++ b/api/envoy/config/cluster/v4alpha/outlier_detection.proto @@ -151,7 +151,7 @@ message OutlierDetection { google.protobuf.UInt32Value failure_percentage_request_volume = 20; // The maximum time that a host is ejected for. See :ref:`base_ejection_time` - // for more information. - // Defaults to 300000ms or 300s. + // for more information. If not specified, the default value (300000ms or 300s) or + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto b/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto index 9bb5633e6269b..e69b446918543 100644 --- a/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto +++ b/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto @@ -151,7 +151,7 @@ message OutlierDetection { google.protobuf.UInt32Value failure_percentage_request_volume = 20; // The maximum time that a host is ejected for. See :ref:`base_ejection_time` - // for more information. - // Defaults to 300000ms or 300s. + // for more information. If not specified, the default value (300000ms or 300s) or + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto b/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto index 9b2efeb53146d..d4e76b4f135ae 100644 --- a/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto +++ b/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto @@ -151,7 +151,7 @@ message OutlierDetection { google.protobuf.UInt32Value failure_percentage_request_volume = 20; // The maximum time that a host is ejected for. See :ref:`base_ejection_time` - // for more information. - // Defaults to 300000ms or 300s. + // for more information. If not specified, the default value (300000ms or 300s) or + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/source/common/upstream/outlier_detection_impl.cc b/source/common/upstream/outlier_detection_impl.cc index cd9deefede3c0..4fece59f3ed8c 100644 --- a/source/common/upstream/outlier_detection_impl.cc +++ b/source/common/upstream/outlier_detection_impl.cc @@ -250,8 +250,11 @@ DetectorConfig::DetectorConfig(const envoy::config::cluster::v3::OutlierDetectio enforcing_local_origin_success_rate_(static_cast( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_local_origin_success_rate, DEFAULT_ENFORCING_LOCAL_ORIGIN_SUCCESS_RATE))), - max_ejection_time_ms_(static_cast( - PROTOBUF_GET_MS_OR_DEFAULT(config, max_ejection_time, DEFAULT_MAX_EJECTION_TIME_MS))) {} + // If max_ejection_time was not specified in the config, apply the default or + // base_ejection_time whatever is larger. + max_ejection_time_ms_(static_cast(PROTOBUF_GET_MS_OR_DEFAULT( + config, max_ejection_time, + std::max(DEFAULT_MAX_EJECTION_TIME_MS, base_ejection_time_ms_)))) {} DetectorImpl::DetectorImpl(const Cluster& cluster, const envoy::config::cluster::v3::OutlierDetection& config, diff --git a/source/common/upstream/outlier_detection_impl.h b/source/common/upstream/outlier_detection_impl.h index cf4b6adcdf44e..d783240e96dbe 100644 --- a/source/common/upstream/outlier_detection_impl.h +++ b/source/common/upstream/outlier_detection_impl.h @@ -343,26 +343,26 @@ class DetectorConfig { const uint64_t enforcing_local_origin_success_rate_; const uint64_t max_ejection_time_ms_; - static const uint64_t DEFAULT_INTERVAL_MS = 10000; - static const uint64_t DEFAULT_BASE_EJECTION_TIME_MS = 30000; - static const uint64_t DEFAULT_CONSECUTIVE_5XX = 5; - static const uint64_t DEFAULT_CONSECUTIVE_GATEWAY_FAILURE = 5; - static const uint64_t DEFAULT_MAX_EJECTION_PERCENT = 10; - static const uint64_t DEFAULT_SUCCESS_RATE_MINIMUM_HOSTS = 5; - static const uint64_t DEFAULT_SUCCESS_RATE_REQUEST_VOLUME = 100; - static const uint64_t DEFAULT_SUCCESS_RATE_STDEV_FACTOR = 1900; - static const uint64_t DEFAULT_FAILURE_PERCENTAGE_THRESHOLD = 85; - static const uint64_t DEFAULT_FAILURE_PERCENTAGE_MINIMUM_HOSTS = 5; - static const uint64_t DEFAULT_FAILURE_PERCENTAGE_REQUEST_VOLUME = 50; - static const uint64_t DEFAULT_ENFORCING_CONSECUTIVE_5XX = 100; - static const uint64_t DEFAULT_ENFORCING_CONSECUTIVE_GATEWAY_FAILURE = 0; - static const uint64_t DEFAULT_ENFORCING_SUCCESS_RATE = 100; - static const uint64_t DEFAULT_ENFORCING_FAILURE_PERCENTAGE = 0; - static const uint64_t DEFAULT_ENFORCING_FAILURE_PERCENTAGE_LOCAL_ORIGIN = 0; - static const uint64_t DEFAULT_CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 5; - static const uint64_t DEFAULT_ENFORCING_CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 100; - static const uint64_t DEFAULT_ENFORCING_LOCAL_ORIGIN_SUCCESS_RATE = 100; - static const uint64_t DEFAULT_MAX_EJECTION_TIME_MS = 10 * DEFAULT_BASE_EJECTION_TIME_MS; + static constexpr uint64_t DEFAULT_INTERVAL_MS = 10000; + static constexpr uint64_t DEFAULT_BASE_EJECTION_TIME_MS = 30000; + static constexpr uint64_t DEFAULT_CONSECUTIVE_5XX = 5; + static constexpr uint64_t DEFAULT_CONSECUTIVE_GATEWAY_FAILURE = 5; + static constexpr uint64_t DEFAULT_MAX_EJECTION_PERCENT = 10; + static constexpr uint64_t DEFAULT_SUCCESS_RATE_MINIMUM_HOSTS = 5; + static constexpr uint64_t DEFAULT_SUCCESS_RATE_REQUEST_VOLUME = 100; + static constexpr uint64_t DEFAULT_SUCCESS_RATE_STDEV_FACTOR = 1900; + static constexpr uint64_t DEFAULT_FAILURE_PERCENTAGE_THRESHOLD = 85; + static constexpr uint64_t DEFAULT_FAILURE_PERCENTAGE_MINIMUM_HOSTS = 5; + static constexpr uint64_t DEFAULT_FAILURE_PERCENTAGE_REQUEST_VOLUME = 50; + static constexpr uint64_t DEFAULT_ENFORCING_CONSECUTIVE_5XX = 100; + static constexpr uint64_t DEFAULT_ENFORCING_CONSECUTIVE_GATEWAY_FAILURE = 0; + static constexpr uint64_t DEFAULT_ENFORCING_SUCCESS_RATE = 100; + static constexpr uint64_t DEFAULT_ENFORCING_FAILURE_PERCENTAGE = 0; + static constexpr uint64_t DEFAULT_ENFORCING_FAILURE_PERCENTAGE_LOCAL_ORIGIN = 0; + static constexpr uint64_t DEFAULT_CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 5; + static constexpr uint64_t DEFAULT_ENFORCING_CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 100; + static constexpr uint64_t DEFAULT_ENFORCING_LOCAL_ORIGIN_SUCCESS_RATE = 100; + static constexpr uint64_t DEFAULT_MAX_EJECTION_TIME_MS = 10 * DEFAULT_BASE_EJECTION_TIME_MS; }; /** diff --git a/test/common/upstream/outlier_detection_impl_test.cc b/test/common/upstream/outlier_detection_impl_test.cc index 1a985ddae542f..a47766d2cfbbe 100644 --- a/test/common/upstream/outlier_detection_impl_test.cc +++ b/test/common/upstream/outlier_detection_impl_test.cc @@ -210,6 +210,26 @@ max_ejection_time: 3s EnvoyException); } +// Test verifies that legacy config without max_ejection_time value +// specified and base_ejection_time value larger than default value +// of max_ejection_time will be accepted. +// Values of base_ejection_time and max_ejection_time will be equal. +TEST_F(OutlierDetectorImplTest, DetectorStaticConfigBaseLargerThanMaxNoMax) { + const std::string yaml = R"EOF( +interval: 0.1s +base_ejection_time: 400s + )EOF"; + envoy::config::cluster::v3::OutlierDetection outlier_detection; + TestUtility::loadFromYaml(yaml, outlier_detection); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(100), _)); + std::shared_ptr detector(DetectorImpl::create( + cluster_, outlier_detection, dispatcher_, runtime_, time_system_, event_logger_)); + + EXPECT_EQ(100UL, detector->config().intervalMs()); + EXPECT_EQ(400000UL, detector->config().baseEjectionTimeMs()); + EXPECT_EQ(400000UL, detector->config().maxEjectionTimeMs()); +} + TEST_F(OutlierDetectorImplTest, DestroyWithActive) { ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); From 8f83d63df99b9656646973ed9e33507c4a2334d5 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Wed, 3 Mar 2021 23:24:29 +0900 Subject: [PATCH 11/28] backport 1.17: http: reinstating prior connect timeout behavior (#15273) #10854 inadvertently changed the behavior of connect timeouts. This reinstates prior behavior. Risk Level: Low (reinstating prior behavior) Testing: added regression test Docs Changes: n/a Release Notes: inline Signed-off-by: Shikugawa Co-authored-by: alyssawilk --- docs/root/version_history/current.rst | 2 + source/common/router/upstream_request.cc | 7 ++- source/common/runtime/runtime_features.cc | 1 + test/common/router/router_test.cc | 72 +++++++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 7a33754f0bae4..77b1b5b6d733e 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -13,6 +13,8 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. + Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 6a9243370d27c..f0f42fb8c362d 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -337,7 +337,12 @@ void UpstreamRequest::onPoolFailure(ConnectionPool::PoolFailureReason reason, reset_reason = Http::StreamResetReason::ConnectionFailure; break; case ConnectionPool::PoolFailureReason::Timeout: - reset_reason = Http::StreamResetReason::LocalReset; + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure")) { + reset_reason = Http::StreamResetReason::ConnectionFailure; + } else { + reset_reason = Http::StreamResetReason::LocalReset; + } } // Mimic an upstream reset. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index b2d19539c3753..ed9fe45973c60 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -86,6 +86,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.stop_faking_paths", "envoy.reloadable_features.strict_1xx_and_204_response_headers", "envoy.reloadable_features.tls_use_io_handle_bio", + "envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure", "envoy.reloadable_features.vhds_heartbeats", "envoy.reloadable_features.unify_grpc_handling", "envoy.restart_features.use_apple_api_for_dns_lookups", diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index f28662ff31009..a540901ec2844 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -555,6 +555,78 @@ TEST_F(RouterTest, PoolFailureWithPriority) { "upstream_reset_before_response_started{connection failure,tls version mismatch}"); } +TEST_F(RouterTest, PoolFailureDueToConnectTimeout) { + ON_CALL(callbacks_.route_->route_entry_, priority()) + .WillByDefault(Return(Upstream::ResourcePriority::High)); + EXPECT_CALL(cm_.thread_local_cluster_, + httpConnPool(Upstream::ResourcePriority::High, _, &router_)); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder&, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + callbacks.onPoolFailure(ConnectionPool::PoolFailureReason::Timeout, "connect_timeout", + cm_.thread_local_cluster_.conn_pool_.host_); + return nullptr; + })); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "503"}, {"content-length", "134"}, {"content-type", "text/plain"}}; + EXPECT_CALL(callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), false)); + EXPECT_CALL(callbacks_, encodeData(_, true)); + EXPECT_CALL(callbacks_.stream_info_, + setResponseFlag(StreamInfo::ResponseFlag::UpstreamConnectionFailure)); + EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) + .WillOnce(Invoke([&](const Upstream::HostDescriptionConstSharedPtr host) -> void { + EXPECT_EQ(host_address_, host->address()); + })); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); + // Pool failure, so upstream request was not initiated. + EXPECT_EQ(0U, + callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); + EXPECT_EQ(callbacks_.details(), + "upstream_reset_before_response_started{connection failure,connect_timeout}"); +} + +TEST_F(RouterTest, PoolFailureDueToConnectTimeoutLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure", "false"}}); + ON_CALL(callbacks_.route_->route_entry_, priority()) + .WillByDefault(Return(Upstream::ResourcePriority::High)); + EXPECT_CALL(cm_.thread_local_cluster_, + httpConnPool(Upstream::ResourcePriority::High, _, &router_)); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder&, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + callbacks.onPoolFailure(ConnectionPool::PoolFailureReason::Timeout, "connect_timeout", + cm_.thread_local_cluster_.conn_pool_.host_); + return nullptr; + })); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "503"}, {"content-length", "127"}, {"content-type", "text/plain"}}; + EXPECT_CALL(callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), false)); + EXPECT_CALL(callbacks_, encodeData(_, true)); + EXPECT_CALL(callbacks_.stream_info_, setResponseFlag(StreamInfo::ResponseFlag::LocalReset)); + EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) + .WillOnce(Invoke([&](const Upstream::HostDescriptionConstSharedPtr host) -> void { + EXPECT_EQ(host_address_, host->address()); + })); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); + // Pool failure, so upstream request was not initiated. + EXPECT_EQ(0U, + callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); + EXPECT_EQ(callbacks_.details(), + "upstream_reset_before_response_started{local reset,connect_timeout}"); +} + TEST_F(RouterTest, Http1Upstream) { EXPECT_CALL(cm_.thread_local_cluster_, httpConnPool(_, absl::optional(), _)); EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _)) From b9613239137dfb4767081eedef03d7470f2afca0 Mon Sep 17 00:00:00 2001 From: Christoph Pakulski Date: Wed, 3 Mar 2021 09:57:24 -0500 Subject: [PATCH 12/28] backport to 1.17: cluster: destroy on main thread (#14954) (#15197) * Dispatcher: keeps a stack of tracked objects. (#14573) Dispatcher will now keep a stack of tracked objects; on crash it'll "unwind" and have those objects dump their state. Moreover, it'll invoke fatal actions with the tracked objects. This allows us to dump more information during crash. See related PR: #14509 Will follow up with another PR dumping information at the codec/parser level. Signed-off-by: Kevin Baichoo Signed-off-by: Christoph Pakulski * cluster: destroy on main thread (#14954) Signed-off-by: Yuchen Dai Signed-off-by: Christoph Pakulski * Updated release notes. Signed-off-by: Christoph Pakulski Co-authored-by: Kevin Baichoo Co-authored-by: Yuchen Dai --- docs/root/version_history/current.rst | 1 + include/envoy/event/BUILD | 6 + include/envoy/event/dispatcher.h | 29 ++- .../envoy/event/dispatcher_thread_deletable.h | 21 +++ include/envoy/server/fatal_action_config.h | 8 +- source/common/common/scope_tracker.h | 25 ++- source/common/event/BUILD | 3 + source/common/event/dispatcher_impl.cc | 101 ++++++++++- source/common/event/dispatcher_impl.h | 47 +++-- source/common/grpc/async_client_impl.cc | 1 + source/common/http/async_client_impl.cc | 3 + source/common/network/connection_impl.cc | 4 + source/common/upstream/upstream_impl.cc | 19 +- source/common/upstream/upstream_impl.h | 4 +- source/server/config_validation/server.cc | 1 + source/server/server.cc | 1 + source/server/worker_impl.cc | 1 + test/common/common/BUILD | 12 ++ test/common/common/scope_tracker_test.cc | 37 ++++ test/common/event/dispatcher_impl_test.cc | 170 +++++++++++++++++- .../scaled_range_timer_manager_impl_test.cc | 10 +- test/common/http/conn_manager_impl_test.cc | 6 +- test/common/http/conn_manager_impl_test_2.cc | 23 ++- test/common/http/filter_manager_test.cc | 5 +- test/common/network/connection_impl_test.cc | 4 +- test/common/router/router_test.cc | 14 +- .../common/router/router_upstream_log_test.cc | 3 +- test/common/signal/fatal_action_test.cc | 12 +- test/common/signal/signals_test.cc | 17 +- .../upstream/cluster_manager_impl_test.cc | 2 + .../upstream/logical_dns_cluster_test.cc | 2 +- .../upstream/original_dst_cluster_test.cc | 2 +- .../filters/http/fault/fault_filter_test.cc | 58 +++--- .../filters/http/squash/squash_filter_test.cc | 17 +- .../sds_dynamic_integration_test.cc | 129 +++++++++++++ test/mocks/event/mocks.cc | 1 + test/mocks/event/mocks.h | 6 +- test/mocks/event/wrapped_dispatcher.h | 14 +- test/mocks/router/router_filter_interface.cc | 3 +- test/server/server_test.cc | 4 +- 40 files changed, 707 insertions(+), 119 deletions(-) create mode 100644 include/envoy/event/dispatcher_thread_deletable.h create mode 100644 test/common/common/scope_tracker_test.cc diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 77b1b5b6d733e..3ae7afe1afa8e 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -21,6 +21,7 @@ Removed Config or Runtime New Features ------------ +* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. Deprecated ---------- diff --git a/include/envoy/event/BUILD b/include/envoy/event/BUILD index a4c865a2d2255..75a7b759a9f7e 100644 --- a/include/envoy/event/BUILD +++ b/include/envoy/event/BUILD @@ -13,11 +13,17 @@ envoy_cc_library( hdrs = ["deferred_deletable.h"], ) +envoy_cc_library( + name = "dispatcher_thread_deletable", + hdrs = ["dispatcher_thread_deletable.h"], +) + envoy_cc_library( name = "dispatcher_interface", hdrs = ["dispatcher.h"], deps = [ ":deferred_deletable", + ":dispatcher_thread_deletable", ":file_event_interface", ":schedulable_cb_interface", ":signal_interface", diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index 599617e87d28d..f6ffdc2ccc8af 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -8,6 +8,7 @@ #include "envoy/common/scope_tracker.h" #include "envoy/common/time.h" +#include "envoy/event/dispatcher_thread_deletable.h" #include "envoy/event/file_event.h" #include "envoy/event/schedulable_cb.h" #include "envoy/event/signal.h" @@ -86,15 +87,18 @@ class DispatcherBase { virtual Event::SchedulableCallbackPtr createSchedulableCallback(std::function cb) PURE; /** - * Sets a tracked object, which is currently operating in this Dispatcher. - * This should be cleared with another call to setTrackedObject() when the object is done doing - * work. Calling setTrackedObject(nullptr) results in no object being tracked. + * Appends a tracked object to the current stack of tracked objects operating + * in the dispatcher. * - * This is optimized for performance, to avoid allocation where we do scoped object tracking. - * - * @return The previously tracked object or nullptr if there was none. + * It's recommended to use ScopeTrackerScopeState to manage the object's tracking. If directly + * invoking, there needs to be a subsequent call to popTrackedObject(). */ - virtual const ScopeTrackedObject* setTrackedObject(const ScopeTrackedObject* object) PURE; + virtual void pushTrackedObject(const ScopeTrackedObject* object) PURE; + + /** + * Removes the top of the stack of tracked object and asserts that it was expected. + */ + virtual void popTrackedObject(const ScopeTrackedObject* expected_object) PURE; /** * Validates that an operation is thread-safe with respect to this dispatcher; i.e. that the @@ -242,6 +246,12 @@ class Dispatcher : public DispatcherBase { */ virtual void post(PostCb callback) PURE; + /** + * Post the deletable to this dispatcher. The deletable objects are guaranteed to be destroyed on + * the dispatcher's thread before dispatcher destroy. This is safe cross thread. + */ + virtual void deleteInDispatcherThread(DispatcherThreadDeletableConstPtr deletable) PURE; + /** * Runs the event loop. This will not return until exit() is called either from within a callback * or from a different thread. @@ -269,6 +279,11 @@ class Dispatcher : public DispatcherBase { * Updates approximate monotonic time to current value. */ virtual void updateApproximateMonotonicTime() PURE; + + /** + * Shutdown the dispatcher by clear dispatcher thread deletable. + */ + virtual void shutdown() PURE; }; using DispatcherPtr = std::unique_ptr; diff --git a/include/envoy/event/dispatcher_thread_deletable.h b/include/envoy/event/dispatcher_thread_deletable.h new file mode 100644 index 0000000000000..bf5b1808e0e33 --- /dev/null +++ b/include/envoy/event/dispatcher_thread_deletable.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace Envoy { +namespace Event { + +/** + * If an object derives from this class, it can be passed to the destination dispatcher who + * guarantees to delete it in that dispatcher thread. The common use case is to ensure config + * related objects are deleted in the main thread. + */ +class DispatcherThreadDeletable { +public: + virtual ~DispatcherThreadDeletable() = default; +}; + +using DispatcherThreadDeletableConstPtr = std::unique_ptr; + +} // namespace Event +} // namespace Envoy diff --git a/include/envoy/server/fatal_action_config.h b/include/envoy/server/fatal_action_config.h index c8768dced40a9..1e5914ac25927 100644 --- a/include/envoy/server/fatal_action_config.h +++ b/include/envoy/server/fatal_action_config.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "envoy/common/pure.h" #include "envoy/config/bootstrap/v3/bootstrap.pb.h" @@ -17,11 +18,10 @@ class FatalAction { public: virtual ~FatalAction() = default; /** - * Callback function to run when we are crashing. - * @param current_object the object we were working on when we started - * crashing. + * Callback function to run when Envoy is crashing. + * @param tracked_objects a span of objects Envoy was working on when Envoy started crashing. */ - virtual void run(const ScopeTrackedObject* current_object) PURE; + virtual void run(absl::Span tracked_objects) PURE; /** * @return whether the action is async-signal-safe. diff --git a/source/common/common/scope_tracker.h b/source/common/common/scope_tracker.h index bed58c3fa8c09..4426bbaca5cc1 100644 --- a/source/common/common/scope_tracker.h +++ b/source/common/common/scope_tracker.h @@ -3,24 +3,35 @@ #include "envoy/common/scope_tracker.h" #include "envoy/event/dispatcher.h" +#include "common/common/assert.h" + namespace Envoy { -// A small class for tracking the scope of the object which is currently having +// A small class for managing the scope of a tracked object which is currently having // work done in this thread. // -// When created, it sets the tracked object in the dispatcher, and when destroyed it points the -// dispatcher at the previously tracked object. +// When created, it appends the tracked object to the dispatcher's stack of tracked objects, and +// when destroyed it pops the dispatcher's stack of tracked object, which should be the object it +// registered. class ScopeTrackerScopeState { public: ScopeTrackerScopeState(const ScopeTrackedObject* object, Event::Dispatcher& dispatcher) - : dispatcher_(dispatcher) { - latched_object_ = dispatcher_.setTrackedObject(object); + : registered_object_(object), dispatcher_(dispatcher) { + dispatcher_.pushTrackedObject(registered_object_); + } + + ~ScopeTrackerScopeState() { + // If ScopeTrackerScopeState is always used for managing tracked objects, + // then the object popped off should be the object we registered. + dispatcher_.popTrackedObject(registered_object_); } - ~ScopeTrackerScopeState() { dispatcher_.setTrackedObject(latched_object_); } + // Make this object stack-only, it doesn't make sense for it + // to be on the heap since it's tracking a stack of active operations. + void* operator new(std::size_t) = delete; private: - const ScopeTrackedObject* latched_object_; + const ScopeTrackedObject* registered_object_; Event::Dispatcher& dispatcher_; }; diff --git a/source/common/event/BUILD b/source/common/event/BUILD index c14a6ee08e99b..09d640b6b8179 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -112,6 +112,9 @@ envoy_cc_library( "file_event_impl.h", "schedulable_cb_impl.h", ], + external_deps = [ + "abseil_inlined_vector", + ], deps = [ ":libevent_lib", ":libevent_scheduler_lib", diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 558d82b9230d9..281caa0dd8c56 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -7,10 +7,12 @@ #include #include "envoy/api/api.h" +#include "envoy/common/scope_tracker.h" #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" #include "common/buffer/buffer_impl.h" +#include "common/common/assert.h" #include "common/common/lock_guard.h" #include "common/common/thread.h" #include "common/event/file_event_impl.h" @@ -44,6 +46,8 @@ DispatcherImpl::DispatcherImpl(const std::string& name, Api::Api& api, buffer_factory_(factory != nullptr ? factory : std::make_shared()), scheduler_(time_system.createScheduler(base_scheduler_, base_scheduler_)), + thread_local_delete_cb_( + base_scheduler_.createSchedulableCallback([this]() -> void { runThreadLocalDelete(); })), deferred_delete_cb_(base_scheduler_.createSchedulableCallback( [this]() -> void { clearDeferredDeleteList(); })), post_cb_(base_scheduler_.createSchedulableCallback([this]() -> void { runPostCallbacks(); })), @@ -55,7 +59,12 @@ DispatcherImpl::DispatcherImpl(const std::string& name, Api::Api& api, std::bind(&DispatcherImpl::updateApproximateMonotonicTime, this)); } -DispatcherImpl::~DispatcherImpl() { FatalErrorHandler::removeFatalErrorHandler(*this); } +DispatcherImpl::~DispatcherImpl() { + ENVOY_LOG(debug, "destroying dispatcher {}", name_); + FatalErrorHandler::removeFatalErrorHandler(*this); + // TODO(lambdai): Resolve https://github.com/envoyproxy/envoy/issues/15072 and enable + // ASSERT(deletable_in_dispatcher_thread_.empty()) +} void DispatcherImpl::registerWatchdog(const Server::WatchDogSharedPtr& watchdog, std::chrono::milliseconds min_touch_interval) { @@ -236,9 +245,23 @@ void DispatcherImpl::post(std::function callback) { } } +void DispatcherImpl::deleteInDispatcherThread(DispatcherThreadDeletableConstPtr deletable) { + bool need_schedule; + { + Thread::LockGuard lock(thread_local_deletable_lock_); + need_schedule = deletables_in_dispatcher_thread_.empty(); + deletables_in_dispatcher_thread_.emplace_back(std::move(deletable)); + // TODO(lambdai): Enable below after https://github.com/envoyproxy/envoy/issues/15072 + // ASSERT(!shutdown_called_, "inserted after shutdown"); + } + + if (need_schedule) { + thread_local_delete_cb_->scheduleCallbackCurrentIteration(); + } +} + void DispatcherImpl::run(RunType type) { run_tid_ = api_.threadFactory().currentThreadId(); - // Flush all post callbacks before we run the event loop. We do this because there are post // callbacks that have to get run before the initial event loop starts running. libevent does // not guarantee that events are run in any particular order. So even if we post() and call @@ -251,12 +274,56 @@ MonotonicTime DispatcherImpl::approximateMonotonicTime() const { return approximate_monotonic_time_; } +void DispatcherImpl::shutdown() { + // TODO(lambdai): Resolve https://github.com/envoyproxy/envoy/issues/15072 and loop delete below + // below 3 lists until all lists are empty. The 3 lists are list of deferred delete objects, post + // callbacks and dispatcher thread deletable objects. + ASSERT(isThreadSafe()); + auto deferred_deletables_size = current_to_delete_->size(); + std::list>::size_type post_callbacks_size; + { + Thread::LockGuard lock(post_lock_); + post_callbacks_size = post_callbacks_.size(); + } + + std::list local_deletables; + { + Thread::LockGuard lock(thread_local_deletable_lock_); + local_deletables = std::move(deletables_in_dispatcher_thread_); + } + auto thread_local_deletables_size = local_deletables.size(); + while (!local_deletables.empty()) { + local_deletables.pop_front(); + } + ASSERT(!shutdown_called_); + shutdown_called_ = true; + ENVOY_LOG( + trace, + "{} destroyed {} thread local objects. Peek {} deferred deletables, {} post callbacks. ", + __FUNCTION__, deferred_deletables_size, post_callbacks_size, thread_local_deletables_size); +} + void DispatcherImpl::updateApproximateMonotonicTime() { updateApproximateMonotonicTimeInternal(); } void DispatcherImpl::updateApproximateMonotonicTimeInternal() { approximate_monotonic_time_ = api_.timeSource().monotonicTime(); } +void DispatcherImpl::runThreadLocalDelete() { + std::list to_be_delete; + { + Thread::LockGuard lock(thread_local_deletable_lock_); + to_be_delete = std::move(deletables_in_dispatcher_thread_); + ASSERT(deletables_in_dispatcher_thread_.empty()); + } + while (!to_be_delete.empty()) { + // Touch the watchdog before deleting the objects to avoid spurious watchdog miss events when + // executing complicated destruction. + touchWatchdog(); + // Delete in FIFO order. + to_be_delete.pop_front(); + } +} void DispatcherImpl::runPostCallbacks() { // Clear the deferred delete list before running post callbacks to reduce non-determinism in // callback processing, and more easily detect if a scheduled post callback refers to one of the @@ -287,6 +354,16 @@ void DispatcherImpl::runPostCallbacks() { } } +void DispatcherImpl::onFatalError(std::ostream& os) const { + // Dump the state of the tracked objects in the dispatcher if thread safe. This generally + // results in dumping the active state only for the thread which caused the fatal error. + if (isThreadSafe()) { + for (auto iter = tracked_object_stack_.rbegin(); iter != tracked_object_stack_.rend(); ++iter) { + (*iter)->dumpState(os); + } + } +} + void DispatcherImpl::runFatalActionsOnTrackedObject( const FatalAction::FatalActionPtrList& actions) const { // Only run if this is the dispatcher of the current thread and @@ -296,7 +373,7 @@ void DispatcherImpl::runFatalActionsOnTrackedObject( } for (const auto& action : actions) { - action->run(current_object_); + action->run(tracked_object_stack_); } } @@ -306,5 +383,23 @@ void DispatcherImpl::touchWatchdog() { } } +void DispatcherImpl::pushTrackedObject(const ScopeTrackedObject* object) { + ASSERT(isThreadSafe()); + ASSERT(object != nullptr); + tracked_object_stack_.push_back(object); + ASSERT(tracked_object_stack_.size() <= ExpectedMaxTrackedObjectStackDepth); +} + +void DispatcherImpl::popTrackedObject(const ScopeTrackedObject* expected_object) { + ASSERT(isThreadSafe()); + ASSERT(expected_object != nullptr); + RELEASE_ASSERT(!tracked_object_stack_.empty(), "Tracked Object Stack is empty, nothing to pop!"); + + const ScopeTrackedObject* top = tracked_object_stack_.back(); + tracked_object_stack_.pop_back(); + ASSERT(top == expected_object, + "Popped the top of the tracked object stack, but it wasn't the expected object!"); +} + } // namespace Event } // namespace Envoy diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index bd3b698af11fa..1d61f3f8a2fd1 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -20,9 +20,16 @@ #include "common/event/libevent_scheduler.h" #include "common/signal/fatal_error_handler.h" +#include "absl/container/inlined_vector.h" + namespace Envoy { namespace Event { +// The tracked object stack likely won't grow larger than this initial +// reservation; this should make appends constant time since the stack +// shouldn't have to grow larger. +inline constexpr size_t ExpectedMaxTrackedObjectStackDepth = 10; + /** * libevent implementation of Event::Dispatcher. */ @@ -72,27 +79,17 @@ class DispatcherImpl : Logger::Loggable, void exit() override; SignalEventPtr listenForSignal(signal_t signal_num, SignalCb cb) override; void post(std::function callback) override; + void deleteInDispatcherThread(DispatcherThreadDeletableConstPtr deletable) override; void run(RunType type) override; Buffer::WatermarkFactory& getWatermarkFactory() override { return *buffer_factory_; } - const ScopeTrackedObject* setTrackedObject(const ScopeTrackedObject* object) override { - const ScopeTrackedObject* return_object = current_object_; - current_object_ = object; - return return_object; - } + void pushTrackedObject(const ScopeTrackedObject* object) override; + void popTrackedObject(const ScopeTrackedObject* expected_object) override; MonotonicTime approximateMonotonicTime() const override; void updateApproximateMonotonicTime() override; + void shutdown() override; // FatalErrorInterface - void onFatalError(std::ostream& os) const override { - // Dump the state of the tracked object if it is in the current thread. This generally results - // in dumping the active state only for the thread which caused the fatal error. - if (isThreadSafe()) { - if (current_object_) { - current_object_->dumpState(os); - } - } - } - + void onFatalError(std::ostream& os) const override; void runFatalActionsOnTrackedObject(const FatalAction::FatalActionPtrList& actions) const override; @@ -125,6 +122,8 @@ class DispatcherImpl : Logger::Loggable, TimerPtr createTimerInternal(TimerCb cb); void updateApproximateMonotonicTimeInternal(); void runPostCallbacks(); + void runThreadLocalDelete(); + // Helper used to touch the watchdog after most schedulable, fd, and timer callbacks. void touchWatchdog(); @@ -143,14 +142,26 @@ class DispatcherImpl : Logger::Loggable, Buffer::WatermarkFactorySharedPtr buffer_factory_; LibeventScheduler base_scheduler_; SchedulerPtr scheduler_; + + SchedulableCallbackPtr thread_local_delete_cb_; + Thread::MutexBasicLockable thread_local_deletable_lock_; + // `deletables_in_dispatcher_thread` must be destroyed last to allow other callbacks populate. + std::list + deletables_in_dispatcher_thread_ ABSL_GUARDED_BY(thread_local_deletable_lock_); + bool shutdown_called_{false}; + SchedulableCallbackPtr deferred_delete_cb_; + SchedulableCallbackPtr post_cb_; + Thread::MutexBasicLockable post_lock_; + std::list> post_callbacks_ ABSL_GUARDED_BY(post_lock_); + std::vector to_delete_1_; std::vector to_delete_2_; std::vector* current_to_delete_; - Thread::MutexBasicLockable post_lock_; - std::list> post_callbacks_ ABSL_GUARDED_BY(post_lock_); - const ScopeTrackedObject* current_object_{}; + + absl::InlinedVector + tracked_object_stack_; bool deferred_deleting_{}; MonotonicTime approximate_monotonic_time_; WatchdogRegistrationPtr watchdog_registration_; diff --git a/source/common/grpc/async_client_impl.cc b/source/common/grpc/async_client_impl.cc index 9128b296ccb72..6d1152ef9f45a 100644 --- a/source/common/grpc/async_client_impl.cc +++ b/source/common/grpc/async_client_impl.cc @@ -210,6 +210,7 @@ void AsyncStreamImpl::cleanup() { // This will destroy us, but only do so if we are actually in a list. This does not happen in // the immediate failure case. if (LinkedObject::inserted()) { + ASSERT(dispatcher_->isThreadSafe()); dispatcher_->deferredDelete( LinkedObject::removeFromList(parent_.active_streams_)); } diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 361fdfb173ac8..ffe65b5ed62f5 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -149,6 +149,7 @@ void AsyncStreamImpl::sendHeaders(RequestHeaderMap& headers, bool end_stream) { } void AsyncStreamImpl::sendData(Buffer::Instance& data, bool end_stream) { + ASSERT(dispatcher().isThreadSafe()); // Map send calls after local closure to no-ops. The send call could have been queued prior to // remote reset or closure, and/or closure could have occurred synchronously in response to a // previous send. In these cases the router will have already cleaned up stream state. This @@ -169,6 +170,7 @@ void AsyncStreamImpl::sendData(Buffer::Instance& data, bool end_stream) { } void AsyncStreamImpl::sendTrailers(RequestTrailerMap& trailers) { + ASSERT(dispatcher().isThreadSafe()); // See explanation in sendData. if (local_closed_) { return; @@ -226,6 +228,7 @@ void AsyncStreamImpl::reset() { } void AsyncStreamImpl::cleanup() { + ASSERT(dispatcher().isThreadSafe()); local_closed_ = remote_closed_ = true; // This will destroy us, but only do so if we are actually in a list. This does not happen in // the immediate failure case. diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 4ce8ec63dd387..4a7a9260bbac6 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -210,6 +210,7 @@ Connection::State ConnectionImpl::state() const { void ConnectionImpl::closeConnectionImmediately() { closeSocket(ConnectionEvent::LocalClose); } void ConnectionImpl::setTransportSocketIsReadable() { + ASSERT(dispatcher_.isThreadSafe()); // Remember that the transport requested read resumption, in case the resumption event is not // scheduled immediately or is "lost" because read was disabled. transport_wants_read_ = true; @@ -300,6 +301,7 @@ void ConnectionImpl::noDelay(bool enable) { } void ConnectionImpl::onRead(uint64_t read_buffer_size) { + ASSERT(dispatcher_.isThreadSafe()); if (inDelayedClose() || !filterChainWantsData()) { return; } @@ -419,6 +421,7 @@ void ConnectionImpl::raiseEvent(ConnectionEvent event) { bool ConnectionImpl::readEnabled() const { // Calls to readEnabled on a closed socket are considered to be an error. ASSERT(state() == State::Open); + ASSERT(dispatcher_.isThreadSafe()); return read_disable_count_ == 0; } @@ -436,6 +439,7 @@ void ConnectionImpl::write(Buffer::Instance& data, bool end_stream) { void ConnectionImpl::write(Buffer::Instance& data, bool end_stream, bool through_filter_chain) { ASSERT(!end_stream || enable_half_close_); + ASSERT(dispatcher_.isThreadSafe()); if (write_end_stream_) { // It is an API violation to write more data after writing end_stream, but a duplicate diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 18025dfca211d..4d44e88846fa4 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -919,9 +919,16 @@ ClusterImplBase::ClusterImplBase( auto socket_matcher = std::make_unique( cluster.transport_socket_matches(), factory_context, socket_factory, *stats_scope); - info_ = std::make_unique(cluster, factory_context.clusterManager().bindConfig(), - runtime, std::move(socket_matcher), - std::move(stats_scope), added_via_api, factory_context); + auto& dispatcher = factory_context.dispatcher(); + info_ = std::shared_ptr( + new ClusterInfoImpl(cluster, factory_context.clusterManager().bindConfig(), runtime, + std::move(socket_matcher), std::move(stats_scope), added_via_api, + factory_context), + [&dispatcher](const ClusterInfoImpl* self) { + ENVOY_LOG(trace, "Schedule destroy cluster info {}", self->name()); + dispatcher.deleteInDispatcherThread( + std::unique_ptr(self)); + }); if ((info_->features() & ClusterInfoImpl::Features::USE_ALPN) && !raw_factory_pointer->supportsAlpn()) { @@ -1098,7 +1105,7 @@ void ClusterImplBase::reloadHealthyHostsHelper(const HostSharedPtr&) { for (size_t priority = 0; priority < host_sets.size(); ++priority) { const auto& host_set = host_sets[priority]; // TODO(htuch): Can we skip these copies by exporting out const shared_ptr from HostSet? - HostVectorConstSharedPtr hosts_copy(new HostVector(host_set->hosts())); + HostVectorConstSharedPtr hosts_copy = std::make_shared(host_set->hosts()); HostsPerLocalityConstSharedPtr hosts_per_locality_copy = host_set->hostsPerLocality().clone(); prioritySet().updateHosts(priority, @@ -1289,10 +1296,10 @@ void PriorityStateManager::registerHostForPriority( auto metadata = lb_endpoint.has_metadata() ? parent_.constMetadataSharedPool()->getObject(lb_endpoint.metadata()) : nullptr; - const HostSharedPtr host(new HostImpl( + const auto host = std::make_shared( parent_.info(), hostname, address, metadata, lb_endpoint.load_balancing_weight().value(), locality_lb_endpoint.locality(), lb_endpoint.endpoint().health_check_config(), - locality_lb_endpoint.priority(), lb_endpoint.health_status(), time_source)); + locality_lb_endpoint.priority(), lb_endpoint.health_status(), time_source); registerHostForPriority(host, locality_lb_endpoint); } diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 3ff38c4b770d6..7a24014251612 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -517,7 +517,9 @@ class PrioritySetImpl : public PrioritySet { /** * Implementation of ClusterInfo that reads from JSON. */ -class ClusterInfoImpl : public ClusterInfo, protected Logger::Loggable { +class ClusterInfoImpl : public ClusterInfo, + public Event::DispatcherThreadDeletable, + protected Logger::Loggable { public: using HttpProtocolOptionsConfigImpl = Envoy::Extensions::Upstreams::Http::ProtocolOptionsConfigImpl; diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index e4130e383ff21..a2bfd270dbb27 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -116,6 +116,7 @@ void ValidationInstance::shutdown() { config_.clusterManager()->shutdown(); } thread_local_.shutdownThread(); + dispatcher_->shutdown(); } } // namespace Server diff --git a/source/server/server.cc b/source/server/server.cc index 26795a8b2aed0..e4757d638654c 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -139,6 +139,7 @@ InstanceImpl::~InstanceImpl() { ENVOY_LOG(debug, "destroying listener manager"); listener_manager_.reset(); ENVOY_LOG(debug, "destroyed listener manager"); + dispatcher_->shutdown(); } Upstream::ClusterManager& InstanceImpl::clusterManager() { return *config_.clusterManager(); } diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index 760b7ca630bcb..30c519c501093 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -134,6 +134,7 @@ void WorkerImpl::threadRoutine(GuardDog& guard_dog) { dispatcher_->run(Event::Dispatcher::RunType::Block); ENVOY_LOG(debug, "worker exited dispatch loop"); guard_dog.stopWatching(watch_dog_); + dispatcher_->shutdown(); // We must close all active connections before we actually exit the thread. This prevents any // destructors from running on the main thread which might reference thread locals. Destroying diff --git a/test/common/common/BUILD b/test/common/common/BUILD index f0e0b68986ca7..2a451dd886159 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -361,3 +361,15 @@ envoy_cc_test( srcs = ["interval_value_test.cc"], deps = ["//source/common/common:interval_value"], ) + +envoy_cc_test( + name = "scope_tracker_test", + srcs = ["scope_tracker_test.cc"], + deps = [ + "//source/common/api:api_lib", + "//source/common/common:scope_tracker", + "//source/common/event:dispatcher_lib", + "//test/mocks:common_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/common/common/scope_tracker_test.cc b/test/common/common/scope_tracker_test.cc new file mode 100644 index 0000000000000..1c5451660f049 --- /dev/null +++ b/test/common/common/scope_tracker_test.cc @@ -0,0 +1,37 @@ +#include + +#include "common/api/api_impl.h" +#include "common/common/scope_tracker.h" +#include "common/event/dispatcher_impl.h" + +#include "test/mocks/common.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace { + +using testing::_; + +TEST(ScopeTrackerScopeStateTest, ShouldManageTrackedObjectOnDispatcherStack) { + Api::ApiPtr api(Api::createApiForTest()); + Event::DispatcherPtr dispatcher(api->allocateDispatcher("test_thread")); + MockScopedTrackedObject tracked_object; + { + ScopeTrackerScopeState scope(&tracked_object, *dispatcher); + // Check that the tracked_object is on the tracked object stack + dispatcher->popTrackedObject(&tracked_object); + + // Restore it to the top, it should be removed in the dtor of scope. + dispatcher->pushTrackedObject(&tracked_object); + } + + // Check nothing is tracked now. + EXPECT_CALL(tracked_object, dumpState(_, _)).Times(0); + static_cast(dispatcher.get())->onFatalError(std::cerr); +} + +} // namespace +} // namespace Envoy diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index 8c612144db509..2bca020fb4b4a 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -1,10 +1,13 @@ #include +#include "envoy/common/scope_tracker.h" #include "envoy/thread/thread.h" #include "common/api/api_impl.h" #include "common/api/os_sys_calls_impl.h" #include "common/common/lock_guard.h" +#include "common/common/scope_tracker.h" +#include "common/common/utility.h" #include "common/event/deferred_task.h" #include "common/event/dispatcher_impl.h" #include "common/event/timer_impl.h" @@ -234,6 +237,15 @@ class TestDeferredDeletable : public DeferredDeletable { std::function on_destroy_; }; +class TestDispatcherThreadDeletable : public DispatcherThreadDeletable { +public: + TestDispatcherThreadDeletable(std::function on_destroy) : on_destroy_(on_destroy) {} + ~TestDispatcherThreadDeletable() override { on_destroy_(); } + +private: + std::function on_destroy_; +}; + TEST(DeferredDeleteTest, DeferredDelete) { InSequence s; Api::ApiPtr api = Api::createApiForTest(); @@ -476,6 +488,100 @@ TEST_F(DispatcherImplTest, RunPostCallbacksLocking) { } } +TEST_F(DispatcherImplTest, DispatcherThreadDeleted) { + dispatcher_->deleteInDispatcherThread(std::make_unique( + [this, id = api_->threadFactory().currentThreadId()]() { + ASSERT(id != api_->threadFactory().currentThreadId()); + { + Thread::LockGuard lock(mu_); + ASSERT(!work_finished_); + work_finished_ = true; + } + cv_.notifyOne(); + })); + + Thread::LockGuard lock(mu_); + while (!work_finished_) { + cv_.wait(mu_); + } +} + +TEST(DispatcherThreadDeletedImplTest, DispatcherThreadDeletedAtNextCycle) { + Api::ApiPtr api_(Api::createApiForTest()); + DispatcherPtr dispatcher(api_->allocateDispatcher("test_thread")); + std::vector> watchers; + watchers.reserve(3); + for (int i = 0; i < 3; ++i) { + watchers.push_back(std::make_unique()); + } + dispatcher->deleteInDispatcherThread( + std::make_unique([&watchers]() { watchers[0]->ready(); })); + EXPECT_CALL(*watchers[0], ready()); + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + dispatcher->deleteInDispatcherThread( + std::make_unique([&watchers]() { watchers[1]->ready(); })); + dispatcher->deleteInDispatcherThread( + std::make_unique([&watchers]() { watchers[2]->ready(); })); + EXPECT_CALL(*watchers[1], ready()); + EXPECT_CALL(*watchers[2], ready()); + dispatcher->run(Event::Dispatcher::RunType::NonBlock); +} + +class DispatcherShutdownTest : public testing::Test { +protected: + DispatcherShutdownTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + + Api::ApiPtr api_; + DispatcherPtr dispatcher_; +}; + +TEST_F(DispatcherShutdownTest, ShutdownClearThreadLocalDeletables) { + ReadyWatcher watcher; + + dispatcher_->deleteInDispatcherThread( + std::make_unique([&watcher]() { watcher.ready(); })); + EXPECT_CALL(watcher, ready()); + dispatcher_->shutdown(); +} + +TEST_F(DispatcherShutdownTest, ShutdownDoesnotClearDeferredListOrPostCallback) { + ReadyWatcher watcher; + ReadyWatcher deferred_watcher; + ReadyWatcher post_watcher; + + { + InSequence s; + + dispatcher_->deferredDelete(std::make_unique( + [&deferred_watcher]() { deferred_watcher.ready(); })); + dispatcher_->post([&post_watcher]() { post_watcher.ready(); }); + dispatcher_->deleteInDispatcherThread( + std::make_unique([&watcher]() { watcher.ready(); })); + EXPECT_CALL(watcher, ready()); + dispatcher_->shutdown(); + + ::testing::Mock::VerifyAndClearExpectations(&watcher); + EXPECT_CALL(deferred_watcher, ready()); + dispatcher_.reset(); + } +} + +TEST_F(DispatcherShutdownTest, DestroyClearAllList) { + ReadyWatcher watcher; + ReadyWatcher deferred_watcher; + dispatcher_->deferredDelete( + std::make_unique([&deferred_watcher]() { deferred_watcher.ready(); })); + dispatcher_->deleteInDispatcherThread( + std::make_unique([&watcher]() { watcher.ready(); })); + { + InSequence s; + EXPECT_CALL(deferred_watcher, ready()); + EXPECT_CALL(watcher, ready()); + dispatcher_.reset(); + } +} + TEST_F(DispatcherImplTest, Timer) { timerTest([](Timer& timer) { timer.enableTimer(std::chrono::milliseconds(0)); }); timerTest([](Timer& timer) { timer.enableTimer(std::chrono::milliseconds(50)); }); @@ -533,9 +639,71 @@ TEST_F(DispatcherImplTest, IsThreadSafe) { EXPECT_FALSE(dispatcher_->isThreadSafe()); } +TEST_F(DispatcherImplTest, ShouldDumpNothingIfNoTrackedObjects) { + std::array buffer; + OutputBufferStream ostream{buffer.data(), buffer.size()}; + + // Call on FatalError to trigger dumps of tracked objects. + dispatcher_->post([this, &ostream]() { + Thread::LockGuard lock(mu_); + static_cast(dispatcher_.get())->onFatalError(ostream); + work_finished_ = true; + cv_.notifyOne(); + }); + + Thread::LockGuard lock(mu_); + while (!work_finished_) { + cv_.wait(mu_); + } + + // Check ostream still empty. + EXPECT_EQ(ostream.contents(), ""); +} + +class MessageTrackedObject : public ScopeTrackedObject { +public: + MessageTrackedObject(absl::string_view sv) : sv_(sv) {} + void dumpState(std::ostream& os, int /*indent_level*/) const override { os << sv_; } + +private: + absl::string_view sv_; +}; + +TEST_F(DispatcherImplTest, ShouldDumpTrackedObjectsInFILO) { + std::array buffer; + OutputBufferStream ostream{buffer.data(), buffer.size()}; + + // Call on FatalError to trigger dumps of tracked objects. + dispatcher_->post([this, &ostream]() { + Thread::LockGuard lock(mu_); + + // Add several tracked objects to the dispatcher + MessageTrackedObject first{"first"}; + ScopeTrackerScopeState first_state{&first, *dispatcher_}; + MessageTrackedObject second{"second"}; + ScopeTrackerScopeState second_state{&second, *dispatcher_}; + MessageTrackedObject third{"third"}; + ScopeTrackerScopeState third_state{&third, *dispatcher_}; + + static_cast(dispatcher_.get())->onFatalError(ostream); + work_finished_ = true; + cv_.notifyOne(); + }); + + Thread::LockGuard lock(mu_); + while (!work_finished_) { + cv_.wait(mu_); + } + + // Check the dump includes and registered objects in a FILO order. + EXPECT_EQ(ostream.contents(), "thirdsecondfirst"); +} + class TestFatalAction : public Server::Configuration::FatalAction { public: - void run(const ScopeTrackedObject* /*current_object*/) override { ++times_ran_; } + void run(absl::Span /*tracked_objects*/) override { + ++times_ran_; + } bool isAsyncSignalSafe() const override { return true; } int getNumTimesRan() { return times_ran_; } diff --git a/test/common/event/scaled_range_timer_manager_impl_test.cc b/test/common/event/scaled_range_timer_manager_impl_test.cc index c6f7476c8af5b..29e6a19aa5291 100644 --- a/test/common/event/scaled_range_timer_manager_impl_test.cc +++ b/test/common/event/scaled_range_timer_manager_impl_test.cc @@ -1,5 +1,6 @@ #include +#include "envoy/common/scope_tracker.h" #include "envoy/event/timer.h" #include "common/event/dispatcher_impl.h" @@ -24,9 +25,14 @@ class ScopeTrackingDispatcher : public WrappedDispatcher { ScopeTrackingDispatcher(DispatcherPtr dispatcher) : WrappedDispatcher(*dispatcher), dispatcher_(std::move(dispatcher)) {} - const ScopeTrackedObject* setTrackedObject(const ScopeTrackedObject* object) override { + void pushTrackedObject(const ScopeTrackedObject* object) override { scope_ = object; - return impl_.setTrackedObject(object); + return impl_.pushTrackedObject(object); + } + + void popTrackedObject(const ScopeTrackedObject* expected_object) override { + scope_ = nullptr; + return impl_.popTrackedObject(expected_object); } const ScopeTrackedObject* scope_{nullptr}; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index cd9944e0d7ccb..04df5b2070e64 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -2656,7 +2656,8 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutCallbackDisarmsAndReturns408 EXPECT_CALL(response_encoder_, encodeData(_, true)).WillOnce(AddBufferToString(&response_body)); conn_manager_->newStream(response_encoder_); - EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, popTrackedObject(_)); request_timer->invokeCallback(); return Http::okStatus(); })); @@ -2886,7 +2887,8 @@ TEST_F(HttpConnectionManagerImplTest, RequestHeaderTimeoutCallbackDisarmsAndRetu EXPECT_CALL(*request_header_timer, enableTimer(request_headers_timeout_, _)); conn_manager_->newStream(response_encoder_); - EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, popTrackedObject(_)); return Http::okStatus(); })); diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 76495244f5087..b7dfd0298ab91 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2348,9 +2348,10 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { { RequestHeaderMapPtr headers{ new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "POST"}}}; - EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)) - .Times(2) - .WillOnce(Invoke([](const ScopeTrackedObject* object) -> const ScopeTrackedObject* { + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, pushTrackedObject(_)) + .Times(1) + .WillOnce(Invoke([](const ScopeTrackedObject* object) -> void { ASSERT(object != nullptr); // On the first call, this should be the active stream. std::stringstream out; object->dumpState(out); @@ -2358,9 +2359,8 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { EXPECT_THAT(state, testing::HasSubstr("filter_manager_callbacks_.requestHeaders(): null")); EXPECT_THAT(state, testing::HasSubstr("protocol_: 1")); - return nullptr; - })) - .WillRepeatedly(Return(nullptr)); + })); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, popTrackedObject(_)); EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, false)) .WillOnce(Invoke([](HeaderMap&, bool) -> FilterHeadersStatus { return FilterHeadersStatus::StopIteration; @@ -2371,9 +2371,9 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { // Send trailers to that stream, and verify by this point headers are in logged state. { RequestTrailerMapPtr trailers{new TestRequestTrailerMapImpl{{"foo", "bar"}}}; - EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)) - .Times(2) - .WillOnce(Invoke([](const ScopeTrackedObject* object) -> const ScopeTrackedObject* { + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, pushTrackedObject(_)) + .Times(1) + .WillOnce(Invoke([](const ScopeTrackedObject* object) -> void { ASSERT(object != nullptr); // On the first call, this should be the active stream. std::stringstream out; object->dumpState(out); @@ -2381,9 +2381,8 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { EXPECT_THAT(state, testing::HasSubstr("filter_manager_callbacks_.requestHeaders(): \n")); EXPECT_THAT(state, testing::HasSubstr("':authority', 'host'\n")); EXPECT_THAT(state, testing::HasSubstr("protocol_: 1")); - return nullptr; - })) - .WillRepeatedly(Return(nullptr)); + })); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, popTrackedObject(_)); EXPECT_CALL(*decoder_filters_[0], decodeComplete()); EXPECT_CALL(*decoder_filters_[0], decodeTrailers(_)) .WillOnce(Return(FilterTrailersStatus::StopIteration)); diff --git a/test/common/http/filter_manager_test.cc b/test/common/http/filter_manager_test.cc index 85d755e864cb4..0328cd4a04251 100644 --- a/test/common/http/filter_manager_test.cc +++ b/test/common/http/filter_manager_test.cc @@ -198,7 +198,8 @@ TEST_F(FilterManagerTest, MatchTreeSkipActionDecodingHeaders) { TEST_F(FilterManagerTest, MatchTreeSkipActionRequestAndResponseHeaders) { initialize(); - EXPECT_CALL(dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(dispatcher_, popTrackedObject(_)); // This stream filter will skip further callbacks once it sees both the request and response // header. As such, it should see the decoding callbacks but none of the encoding callbacks. @@ -251,4 +252,4 @@ TEST_F(FilterManagerTest, MatchTreeSkipActionRequestAndResponseHeaders) { } } // namespace } // namespace Http -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 63361228acbe5..cfbccc6c4b8d2 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -1958,6 +1958,7 @@ class FakeReadFilter : public Network::ReadFilter { class MockTransportConnectionImplTest : public testing::Test { public: MockTransportConnectionImplTest() : stream_info_(dispatcher_.timeSource(), nullptr) { + EXPECT_CALL(dispatcher_, isThreadSafe()).WillRepeatedly(Return(true)); EXPECT_CALL(dispatcher_.buffer_factory_, create_(_, _, _)) .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, std::function above_overflow) -> Buffer::Instance* { @@ -1978,7 +1979,8 @@ class MockTransportConnectionImplTest : public testing::Test { TransportSocketPtr(transport_socket_), stream_info_, true); connection_->addConnectionCallbacks(callbacks_); // File events will trigger setTrackedObject on the dispatcher. - EXPECT_CALL(dispatcher_, setTrackedObject(_)).WillRepeatedly(Return(nullptr)); + EXPECT_CALL(dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(dispatcher_, popTrackedObject(_)).Times(AnyNumber()); } ~MockTransportConnectionImplTest() override { connection_->close(ConnectionCloseType::NoFlush); } diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index a540901ec2844..26525ebf3536b 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -116,8 +116,9 @@ class RouterTestBase : public testing::Test { // Make the "system time" non-zero, because 0 is considered invalid by DateUtil. test_time_.setMonotonicTime(std::chrono::milliseconds(50)); - // Allow any number of setTrackedObject calls for the dispatcher strict mock. - EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(AnyNumber()); + // Allow any number of (append|pop)TrackedObject calls for the dispatcher strict mock. + EXPECT_CALL(callbacks_.dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(callbacks_.dispatcher_, popTrackedObject(_)).Times(AnyNumber()); } void expectResponseTimerCreate() { @@ -294,7 +295,8 @@ class RouterTestBase : public testing::Test { [&](Http::ResponseDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder_ = &decoder; - EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(testing::AtLeast(2)); + EXPECT_CALL(callbacks_.dispatcher_, pushTrackedObject(_)).Times(testing::AtLeast(1)); + EXPECT_CALL(callbacks_.dispatcher_, popTrackedObject(_)).Times(testing::AtLeast(1)); callbacks.onPoolReady(original_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, upstream_stream_info_, Http::Protocol::Http10); return nullptr; @@ -2262,7 +2264,8 @@ TEST_F(RouterTest, GrpcOk) { EXPECT_EQ(1U, callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); - EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(callbacks_.dispatcher_, popTrackedObject(_)); Http::ResponseHeaderMapPtr response_headers( new Http::TestResponseHeaderMapImpl{{":status", "200"}}); EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, @@ -2270,7 +2273,8 @@ TEST_F(RouterTest, GrpcOk) { response_decoder->decodeHeaders(std::move(response_headers), false); EXPECT_TRUE(verifyHostUpstreamStats(0, 0)); - EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(callbacks_.dispatcher_, popTrackedObject(_)); Http::ResponseTrailerMapPtr response_trailers( new Http::TestResponseTrailerMapImpl{{"grpc-status", "0"}}); response_decoder->decodeTrailers(std::move(response_trailers)); diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index 7821291c80a55..8b66f739140a5 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -99,7 +99,8 @@ class RouterUpstreamLogTest : public testing::Test { ShadowWriterPtr(new MockShadowWriter()), router_proto); router_ = std::make_shared(*config_); router_->setDecoderFilterCallbacks(callbacks_); - EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(testing::AnyNumber()); + EXPECT_CALL(callbacks_.dispatcher_, pushTrackedObject(_)).Times(testing::AnyNumber()); + EXPECT_CALL(callbacks_.dispatcher_, popTrackedObject(_)).Times(testing::AnyNumber()); upstream_locality_.set_zone("to_az"); context_.cluster_manager_.initializeThreadLocalClusters({"fake_cluster"}); diff --git a/test/common/signal/fatal_action_test.cc b/test/common/signal/fatal_action_test.cc index 9a286c9f9fb1e..1e276f016e4a7 100644 --- a/test/common/signal/fatal_action_test.cc +++ b/test/common/signal/fatal_action_test.cc @@ -1,3 +1,6 @@ +#include + +#include "envoy/common/scope_tracker.h" #include "envoy/server/fatal_action_config.h" #include "common/signal/fatal_action.h" @@ -23,9 +26,10 @@ class TestFatalErrorHandler : public FatalErrorHandlerInterface { void onFatalError(std::ostream& /*os*/) const override {} void runFatalActionsOnTrackedObject(const FatalAction::FatalActionPtrList& actions) const override { - // Call the Fatal Actions with nullptr + // Call the Fatal Actions with a non-empty vector so it runs the action. + std::vector tracked_objects{nullptr}; for (const Server::Configuration::FatalActionPtr& action : actions) { - action->run(nullptr); + action->run(tracked_objects); } } }; @@ -33,7 +37,9 @@ class TestFatalErrorHandler : public FatalErrorHandlerInterface { class TestFatalAction : public Server::Configuration::FatalAction { public: TestFatalAction(bool is_safe, int* const counter) : is_safe_(is_safe), counter_(counter) {} - void run(const ScopeTrackedObject* /*current_object*/) override { ++(*counter_); } + void run(absl::Span /*tracked_objects*/) override { + ++(*counter_); + } bool isAsyncSignalSafe() const override { return is_safe_; } private: diff --git a/test/common/signal/signals_test.cc b/test/common/signal/signals_test.cc index 3ecf49f6695ba..f2e5ddde8c8dc 100644 --- a/test/common/signal/signals_test.cc +++ b/test/common/signal/signals_test.cc @@ -1,6 +1,9 @@ #include #include +#include + +#include "envoy/common/scope_tracker.h" #include "common/signal/fatal_error_handler.h" #include "common/signal/signal_action.h" @@ -28,21 +31,27 @@ extern void resetFatalActionStateForTest(); // Use this test handler instead of a mock, because fatal error handlers must be // signal-safe and a mock might allocate memory. class TestFatalErrorHandler : public FatalErrorHandlerInterface { +public: void onFatalError(std::ostream& os) const override { os << "HERE!"; } void runFatalActionsOnTrackedObject(const FatalAction::FatalActionPtrList& actions) const override { // Run the actions for (const auto& action : actions) { - action->run(nullptr); + action->run(tracked_objects_); } } + +private: + std::vector tracked_objects_{nullptr}; }; // Use this to test fatal actions get called, as well as the order they run. class EchoFatalAction : public Server::Configuration::FatalAction { public: EchoFatalAction(absl::string_view echo_msg) : echo_msg_(echo_msg) {} - void run(const ScopeTrackedObject* /*current_object*/) override { std::cerr << echo_msg_; } + void run(absl::Span /*tracked_objects*/) override { + std::cerr << echo_msg_; + } bool isAsyncSignalSafe() const override { return true; } private: @@ -52,7 +61,9 @@ class EchoFatalAction : public Server::Configuration::FatalAction { // Use this to test failing while in a signal handler. class SegfaultFatalAction : public Server::Configuration::FatalAction { public: - void run(const ScopeTrackedObject* /*current_object*/) override { raise(SIGSEGV); } + void run(absl::Span /*tracked_objects*/) override { + raise(SIGSEGV); + } bool isAsyncSignalSafe() const override { return false; } }; diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index df0cfd1aba772..194d41180f595 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -869,8 +869,10 @@ TEST_F(ClusterManagerImplTest, HttpHealthChecker) { createClientConnection_( PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.1:11001")), _, _, _)) .WillOnce(Return(connection)); + EXPECT_CALL(factory_.dispatcher_, deleteInDispatcherThread(_)); create(parseBootstrapFromV3Yaml(yaml)); factory_.tls_.shutdownThread(); + factory_.dispatcher_.to_delete_.clear(); } TEST_F(ClusterManagerImplTest, UnknownCluster) { diff --git a/test/common/upstream/logical_dns_cluster_test.cc b/test/common/upstream/logical_dns_cluster_test.cc index db15117750677..5e0e1b107aaf1 100644 --- a/test/common/upstream/logical_dns_cluster_test.cc +++ b/test/common/upstream/logical_dns_cluster_test.cc @@ -202,11 +202,11 @@ class LogicalDnsClusterTest : public Event::TestUsingSimulatedTime, public testi Network::DnsResolver::ResolveCb dns_callback_; NiceMock tls_; Event::MockTimer* resolve_timer_; - std::shared_ptr cluster_; ReadyWatcher membership_updated_; ReadyWatcher initialized_; NiceMock runtime_; NiceMock dispatcher_; + std::shared_ptr cluster_; NiceMock local_info_; NiceMock admin_; Singleton::ManagerImpl singleton_manager_{Thread::threadFactoryForTest()}; diff --git a/test/common/upstream/original_dst_cluster_test.cc b/test/common/upstream/original_dst_cluster_test.cc index 26f15b226453e..9ecd87ede145f 100644 --- a/test/common/upstream/original_dst_cluster_test.cc +++ b/test/common/upstream/original_dst_cluster_test.cc @@ -94,11 +94,11 @@ class OriginalDstClusterTest : public Event::TestUsingSimulatedTime, public test Stats::TestUtil::TestStore stats_store_; Ssl::MockContextManager ssl_context_manager_; + NiceMock dispatcher_; OriginalDstClusterSharedPtr cluster_; ReadyWatcher membership_updated_; ReadyWatcher initialized_; NiceMock runtime_; - NiceMock dispatcher_; Event::MockTimer* cleanup_timer_; NiceMock random_; NiceMock local_info_; diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index 63707e4d96e92..570ef954e94d6 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -122,15 +122,16 @@ class FaultFilterTest : public testing::Test { const std::string v2_empty_fault_config_yaml = "{}"; - void SetUpTest(const envoy::extensions::filters::http::fault::v3::HTTPFault fault) { + void setUpTest(const envoy::extensions::filters::http::fault::v3::HTTPFault fault) { config_ = std::make_shared(fault, runtime_, "prefix.", stats_, time_system_); filter_ = std::make_unique(config_); filter_->setDecoderFilterCallbacks(decoder_filter_callbacks_); filter_->setEncoderFilterCallbacks(encoder_filter_callbacks_); - EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, popTrackedObject(_)).Times(AnyNumber()); } - void SetUpTest(const std::string& yaml) { SetUpTest(convertYamlStrToProtoConfig(yaml)); } + void setUpTest(const std::string& yaml) { setUpTest(convertYamlStrToProtoConfig(yaml)); } void expectDelayTimer(uint64_t duration_ms) { timer_ = new Event::MockTimer(&decoder_filter_callbacks_.dispatcher_); @@ -228,7 +229,7 @@ TEST_F(FaultFilterTest, AbortWithHttpStatus) { fault.mutable_abort()->mutable_percentage()->set_denominator( envoy::type::v3::FractionalPercent::HUNDRED); fault.mutable_abort()->set_http_status(429); - SetUpTest(fault); + setUpTest(fault); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -274,7 +275,7 @@ TEST_F(FaultFilterTest, AbortWithHttpStatus) { } TEST_F(FaultFilterTest, HeaderAbortWithHttpStatus) { - SetUpTest(header_abort_only_yaml); + setUpTest(header_abort_only_yaml); request_headers_.addCopy("x-envoy-fault-abort-request", "429"); @@ -329,7 +330,7 @@ TEST_F(FaultFilterTest, AbortWithGrpcStatus) { fault.mutable_abort()->mutable_percentage()->set_denominator( envoy::type::v3::FractionalPercent::HUNDRED); fault.mutable_abort()->set_grpc_status(5); - SetUpTest(fault); + setUpTest(fault); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -377,7 +378,7 @@ TEST_F(FaultFilterTest, AbortWithGrpcStatus) { TEST_F(FaultFilterTest, HeaderAbortWithGrpcStatus) { decoder_filter_callbacks_.is_grpc_request_ = true; - SetUpTest(header_abort_only_yaml); + setUpTest(header_abort_only_yaml); request_headers_.addCopy("x-envoy-fault-abort-grpc-request", "5"); @@ -427,7 +428,7 @@ TEST_F(FaultFilterTest, HeaderAbortWithGrpcStatus) { } TEST_F(FaultFilterTest, HeaderAbortWithHttpAndGrpcStatus) { - SetUpTest(header_abort_only_yaml); + setUpTest(header_abort_only_yaml); request_headers_.addCopy("x-envoy-fault-abort-request", "429"); request_headers_.addCopy("x-envoy-fault-abort-grpc-request", "5"); @@ -478,7 +479,7 @@ TEST_F(FaultFilterTest, HeaderAbortWithHttpAndGrpcStatus) { } TEST_F(FaultFilterTest, FixedDelayZeroDuration) { - SetUpTest(fixed_delay_only_yaml); + setUpTest(fixed_delay_only_yaml); // Delay related calls EXPECT_CALL( @@ -506,7 +507,7 @@ TEST_F(FaultFilterTest, FixedDelayZeroDuration) { } TEST_F(FaultFilterTest, Overflow) { - SetUpTest(fixed_delay_only_yaml); + setUpTest(fixed_delay_only_yaml); // Delay related calls EXPECT_CALL( @@ -532,7 +533,7 @@ TEST_F(FaultFilterTest, Overflow) { // Verifies that we don't increment the active_faults gauge when not applying a fault. TEST_F(FaultFilterTest, Passthrough) { envoy::extensions::filters::http::fault::v3::HTTPFault fault; - SetUpTest(fault); + setUpTest(fault); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); @@ -545,7 +546,7 @@ TEST_F(FaultFilterTest, FixedDelayDeprecatedPercentAndNonZeroDuration) { fault.mutable_delay()->mutable_percentage()->set_denominator( envoy::type::v3::FractionalPercent::HUNDRED); fault.mutable_delay()->mutable_fixed_delay()->set_seconds(5); - SetUpTest(fault); + setUpTest(fault); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -588,7 +589,7 @@ TEST_F(FaultFilterTest, FixedDelayDeprecatedPercentAndNonZeroDuration) { } TEST_F(FaultFilterTest, DelayForDownstreamCluster) { - SetUpTest(fixed_delay_only_yaml); + setUpTest(fixed_delay_only_yaml); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -624,7 +625,8 @@ TEST_F(FaultFilterTest, DelayForDownstreamCluster) { EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); - EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, popTrackedObject(_)); timer_->invokeCallback(); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); @@ -636,7 +638,7 @@ TEST_F(FaultFilterTest, DelayForDownstreamCluster) { } TEST_F(FaultFilterTest, FixedDelayAndAbortDownstream) { - SetUpTest(fixed_delay_and_abort_yaml); + setUpTest(fixed_delay_and_abort_yaml); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -702,7 +704,7 @@ TEST_F(FaultFilterTest, FixedDelayAndAbortDownstream) { } TEST_F(FaultFilterTest, FixedDelayAndAbort) { - SetUpTest(fixed_delay_and_abort_yaml); + setUpTest(fixed_delay_and_abort_yaml); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -758,7 +760,7 @@ TEST_F(FaultFilterTest, FixedDelayAndAbort) { } TEST_F(FaultFilterTest, FixedDelayAndAbortDownstreamNodes) { - SetUpTest(fixed_delay_and_abort_nodes_yaml); + setUpTest(fixed_delay_and_abort_nodes_yaml); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -812,13 +814,13 @@ TEST_F(FaultFilterTest, FixedDelayAndAbortDownstreamNodes) { } TEST_F(FaultFilterTest, NoDownstreamMatch) { - SetUpTest(fixed_delay_and_abort_nodes_yaml); + setUpTest(fixed_delay_and_abort_nodes_yaml); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); } TEST_F(FaultFilterTest, FixedDelayAndAbortHeaderMatchSuccess) { - SetUpTest(fixed_delay_and_abort_match_headers_yaml); + setUpTest(fixed_delay_and_abort_match_headers_yaml); request_headers_.addCopy("x-foo1", "Bar"); request_headers_.addCopy("x-foo2", "RandomValue"); @@ -875,7 +877,7 @@ TEST_F(FaultFilterTest, FixedDelayAndAbortHeaderMatchSuccess) { } TEST_F(FaultFilterTest, FixedDelayAndAbortHeaderMatchFail) { - SetUpTest(fixed_delay_and_abort_match_headers_yaml); + setUpTest(fixed_delay_and_abort_match_headers_yaml); request_headers_.addCopy("x-foo1", "Bar"); request_headers_.addCopy("x-foo3", "Baz"); @@ -903,7 +905,7 @@ TEST_F(FaultFilterTest, FixedDelayAndAbortHeaderMatchFail) { } TEST_F(FaultFilterTest, TimerResetAfterStreamReset) { - SetUpTest(fixed_delay_only_yaml); + setUpTest(fixed_delay_only_yaml); EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.max_active_faults", std::numeric_limits::max())) @@ -954,7 +956,7 @@ TEST_F(FaultFilterTest, TimerResetAfterStreamReset) { } TEST_F(FaultFilterTest, FaultWithTargetClusterMatchSuccess) { - SetUpTest(delay_with_upstream_cluster_yaml); + setUpTest(delay_with_upstream_cluster_yaml); const std::string upstream_cluster("www1"); EXPECT_CALL(decoder_filter_callbacks_.route_->route_entry_, clusterName()) @@ -999,7 +1001,7 @@ TEST_F(FaultFilterTest, FaultWithTargetClusterMatchSuccess) { } TEST_F(FaultFilterTest, FaultWithTargetClusterMatchFail) { - SetUpTest(delay_with_upstream_cluster_yaml); + setUpTest(delay_with_upstream_cluster_yaml); const std::string upstream_cluster("mismatch"); EXPECT_CALL(decoder_filter_callbacks_.route_->route_entry_, clusterName()) @@ -1027,7 +1029,7 @@ TEST_F(FaultFilterTest, FaultWithTargetClusterMatchFail) { } TEST_F(FaultFilterTest, FaultWithTargetClusterNullRoute) { - SetUpTest(delay_with_upstream_cluster_yaml); + setUpTest(delay_with_upstream_cluster_yaml); const std::string upstream_cluster("www1"); EXPECT_CALL(*decoder_filter_callbacks_.route_, routeEntry()).WillRepeatedly(Return(nullptr)); @@ -1108,7 +1110,7 @@ TEST_F(FaultFilterTest, RouteFaultOverridesListenerFault) { // route-level fault overrides listener-level fault { - SetUpTest(v2_empty_fault_config_yaml); // This is a valid listener level fault + setUpTest(v2_empty_fault_config_yaml); // This is a valid listener level fault TestPerFilterConfigFault(&delay_fault, nullptr); } @@ -1116,7 +1118,7 @@ TEST_F(FaultFilterTest, RouteFaultOverridesListenerFault) { { config_->stats().aborts_injected_.reset(); config_->stats().delays_injected_.reset(); - SetUpTest(v2_empty_fault_config_yaml); + setUpTest(v2_empty_fault_config_yaml); TestPerFilterConfigFault(nullptr, &delay_fault); } @@ -1124,7 +1126,7 @@ TEST_F(FaultFilterTest, RouteFaultOverridesListenerFault) { { config_->stats().aborts_injected_.reset(); config_->stats().delays_injected_.reset(); - SetUpTest(v2_empty_fault_config_yaml); + setUpTest(v2_empty_fault_config_yaml); TestPerFilterConfigFault(&delay_fault, &abort_fault); } } @@ -1135,7 +1137,7 @@ class FaultFilterRateLimitTest : public FaultFilterTest { envoy::extensions::filters::http::fault::v3::HTTPFault fault; fault.mutable_response_rate_limit()->mutable_fixed_limit()->set_limit_kbps(1); fault.mutable_response_rate_limit()->mutable_percentage()->set_numerator(100); - SetUpTest(fault); + setUpTest(fault); EXPECT_CALL( runtime_.snapshot_, diff --git a/test/extensions/filters/http/squash/squash_filter_test.cc b/test/extensions/filters/http/squash/squash_filter_test.cc index 5cb81564c4bea..260ba53ca2a14 100644 --- a/test/extensions/filters/http/squash/squash_filter_test.cc +++ b/test/extensions/filters/http/squash/squash_filter_test.cc @@ -2,6 +2,7 @@ #include #include +#include "envoy/common/scope_tracker.h" #include "envoy/extensions/filters/http/squash/v3/squash.pb.h" #include "common/http/message_impl.h" @@ -328,7 +329,8 @@ TEST_F(SquashFilterTest, Timeout) { EXPECT_CALL(request_, cancel()); EXPECT_CALL(filter_callbacks_, continueDecoding()); - EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.dispatcher_, popTrackedObject(_)); attachmentTimeout_timer_->invokeCallback(); EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->decodeData(buffer, false)); @@ -357,7 +359,9 @@ TEST_F(SquashFilterTest, CheckRetryPollingAttachment) { // Expect the second get attachment request expectAsyncClientSend(); - EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.dispatcher_, popTrackedObject(_)); + retry_timer->invokeCallback(); EXPECT_CALL(filter_callbacks_, continueDecoding()); completeGetStatusRequest("attached"); @@ -377,7 +381,8 @@ TEST_F(SquashFilterTest, PollingAttachmentNoCluster) { // Expect the second get attachment request ON_CALL(factory_context_.cluster_manager_, getThreadLocalCluster("squash")) .WillByDefault(Return(nullptr)); - EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.dispatcher_, popTrackedObject(_)); EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod(), _)); retry_timer->invokeCallback(); } @@ -395,7 +400,8 @@ TEST_F(SquashFilterTest, CheckRetryPollingAttachmentOnFailure) { // Expect the second get attachment request expectAsyncClientSend(); - EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.dispatcher_, popTrackedObject(_)); retry_timer->invokeCallback(); EXPECT_CALL(filter_callbacks_, continueDecoding()); @@ -466,7 +472,8 @@ TEST_F(SquashFilterTest, TimerExpiresInline) { attachmentTimeout_timer_->scope_ = scope; attachmentTimeout_timer_->enabled_ = true; // timer expires inline - EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); + EXPECT_CALL(filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(filter_callbacks_.dispatcher_, popTrackedObject(_)); attachmentTimeout_timer_->invokeCallback(); })); diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index 6023882ab9d60..c1dcb3501a8bc 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -673,5 +673,134 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, WrongSecretFirst) { EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_rejected")->value()); } +// Test CDS with SDS. A cluster provided by CDS raises new SDS request for upstream cert. +class SdsCdsIntegrationTest : public SdsDynamicIntegrationBaseTest { +public: + void initialize() override { + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Create the dynamic cluster. This cluster will be using sds. + dynamic_cluster_ = bootstrap.mutable_static_resources()->clusters(0); + dynamic_cluster_.set_name("dynamic"); + dynamic_cluster_.mutable_connect_timeout()->MergeFrom( + ProtobufUtil::TimeUtil::MillisecondsToDuration(500000)); + auto* transport_socket = dynamic_cluster_.mutable_transport_socket(); + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + auto* secret_config = + tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); + setUpSdsConfig(secret_config, "client_cert"); + + transport_socket->set_name("envoy.transport_sockets.tls"); + transport_socket->mutable_typed_config()->PackFrom(tls_context); + + // Add cds cluster first. + auto* cds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + cds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + cds_cluster->set_name("cds_cluster"); + ConfigHelper::setHttp2(*cds_cluster); + // Then add sds cluster. + auto* sds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + sds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + sds_cluster->set_name("sds_cluster"); + ConfigHelper::setHttp2(*sds_cluster); + + const std::string cds_yaml = R"EOF( + resource_api_version: V3 + api_config_source: + api_type: GRPC + transport_api_version: V3 + grpc_services: + envoy_grpc: + cluster_name: cds_cluster + set_node_on_first_message_only: true +)EOF"; + auto* cds = bootstrap.mutable_dynamic_resources()->mutable_cds_config(); + TestUtility::loadFromYaml(cds_yaml, *cds); + }); + + HttpIntegrationTest::initialize(); + } + + void TearDown() override { + { + AssertionResult result = sds_connection_->close(); + RELEASE_ASSERT(result, result.message()); + result = sds_connection_->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + sds_connection_.reset(); + } + cleanUpXdsConnection(); + cleanupUpstreamAndDownstream(); + codec_client_.reset(); + test_server_.reset(); + fake_upstreams_.clear(); + } + + void createUpstreams() override { + // Static cluster. + addFakeUpstream(FakeHttpConnection::Type::HTTP1); + // Cds Cluster. + addFakeUpstream(FakeHttpConnection::Type::HTTP2); + // Sds Cluster. + addFakeUpstream(FakeHttpConnection::Type::HTTP2); + } + + void sendCdsResponse() { + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse( + Config::TypeUrl::get().Cluster, {dynamic_cluster_}, {dynamic_cluster_}, {}, "55"); + } + + void sendSdsResponse2(const envoy::extensions::transport_sockets::tls::v3::Secret& secret, + FakeStream& sds_stream) { + API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; + discovery_response.set_version_info("1"); + discovery_response.set_type_url(Config::TypeUrl::get().Secret); + discovery_response.add_resources()->PackFrom(secret); + sds_stream.sendGrpcMessage(discovery_response); + } + envoy::config::cluster::v3::Cluster dynamic_cluster_; + FakeHttpConnectionPtr sds_connection_; + FakeStreamPtr sds_stream_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, SdsCdsIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS); + +TEST_P(SdsCdsIntegrationTest, BasicSuccess) { + on_server_init_function_ = [this]() { + { + // CDS. + AssertionResult result = + fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->startGrpcStream(); + sendCdsResponse(); + } + { + // SDS. + AssertionResult result = + fake_upstreams_[2]->waitForHttpConnection(*dispatcher_, sds_connection_); + RELEASE_ASSERT(result, result.message()); + + result = sds_connection_->waitForNewStream(*dispatcher_, sds_stream_); + RELEASE_ASSERT(result, result.message()); + sds_stream_->startGrpcStream(); + sendSdsResponse2(getClientSecret(), *sds_stream_); + } + }; + initialize(); + + test_server_->waitForCounterGe( + "cluster.dynamic.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + // The 4 clusters are CDS,SDS,static and dynamic cluster. + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, {}, {}, + {}, "42"); + // Successfully removed the dynamic cluster. + test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); +} + } // namespace Ssl } // namespace Envoy diff --git a/test/mocks/event/mocks.cc b/test/mocks/event/mocks.cc index a8db4995abb3a..06d39c6e020a0 100644 --- a/test/mocks/event/mocks.cc +++ b/test/mocks/event/mocks.cc @@ -31,6 +31,7 @@ MockDispatcher::MockDispatcher(const std::string& name) : name_(name) { std::function above_overflow) -> Buffer::Instance* { return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); })); + ON_CALL(*this, isThreadSafe()).WillByDefault(Return(true)); } MockDispatcher::~MockDispatcher() = default; diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index 8e29e84c3b326..87627c6d5ce9d 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -5,6 +5,7 @@ #include #include +#include "envoy/common/scope_tracker.h" #include "envoy/common/time.h" #include "envoy/event/deferred_deletable.h" #include "envoy/event/dispatcher.h" @@ -129,13 +130,16 @@ class MockDispatcher : public Dispatcher { MOCK_METHOD(void, exit, ()); MOCK_METHOD(SignalEvent*, listenForSignal_, (signal_t signal_num, SignalCb cb)); MOCK_METHOD(void, post, (std::function callback)); + MOCK_METHOD(void, deleteInDispatcherThread, (DispatcherThreadDeletableConstPtr deletable)); MOCK_METHOD(void, run, (RunType type)); - MOCK_METHOD(const ScopeTrackedObject*, setTrackedObject, (const ScopeTrackedObject* object)); + MOCK_METHOD(void, pushTrackedObject, (const ScopeTrackedObject* object)); + MOCK_METHOD(void, popTrackedObject, (const ScopeTrackedObject* expected_object)); MOCK_METHOD(bool, isThreadSafe, (), (const)); Buffer::WatermarkFactory& getWatermarkFactory() override { return buffer_factory_; } MOCK_METHOD(Thread::ThreadId, getCurrentThreadId, ()); MOCK_METHOD(MonotonicTime, approximateMonotonicTime, (), (const)); MOCK_METHOD(void, updateApproximateMonotonicTime, ()); + MOCK_METHOD(void, shutdown, ()); GlobalTimeSystem time_system_; std::list to_delete_; diff --git a/test/mocks/event/wrapped_dispatcher.h b/test/mocks/event/wrapped_dispatcher.h index 974d61b39be27..5036a55143514 100644 --- a/test/mocks/event/wrapped_dispatcher.h +++ b/test/mocks/event/wrapped_dispatcher.h @@ -94,11 +94,19 @@ class WrappedDispatcher : public Dispatcher { void post(std::function callback) override { impl_.post(std::move(callback)); } + void deleteInDispatcherThread(DispatcherThreadDeletableConstPtr deletable) override { + impl_.deleteInDispatcherThread(std::move(deletable)); + } + void run(RunType type) override { impl_.run(type); } Buffer::WatermarkFactory& getWatermarkFactory() override { return impl_.getWatermarkFactory(); } - const ScopeTrackedObject* setTrackedObject(const ScopeTrackedObject* object) override { - return impl_.setTrackedObject(object); + void pushTrackedObject(const ScopeTrackedObject* object) override { + return impl_.pushTrackedObject(object); + } + + void popTrackedObject(const ScopeTrackedObject* expected_object) override { + return impl_.popTrackedObject(expected_object); } MonotonicTime approximateMonotonicTime() const override { @@ -109,6 +117,8 @@ class WrappedDispatcher : public Dispatcher { bool isThreadSafe() const override { return impl_.isThreadSafe(); } + void shutdown() override { impl_.shutdown(); } + protected: Dispatcher& impl_; }; diff --git a/test/mocks/router/router_filter_interface.cc b/test/mocks/router/router_filter_interface.cc index f1fb2ab0518c4..9d175ee6cb7a0 100644 --- a/test/mocks/router/router_filter_interface.cc +++ b/test/mocks/router/router_filter_interface.cc @@ -18,7 +18,8 @@ MockRouterFilterInterface::MockRouterFilterInterface() ON_CALL(*this, config()).WillByDefault(ReturnRef(config_)); ON_CALL(*this, cluster()).WillByDefault(Return(cluster_info_)); ON_CALL(*this, upstreamRequests()).WillByDefault(ReturnRef(requests_)); - EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(callbacks_.dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(callbacks_.dispatcher_, popTrackedObject(_)).Times(AnyNumber()); ON_CALL(*this, routeEntry()).WillByDefault(Return(&route_entry_)); ON_CALL(callbacks_, connection()).WillByDefault(Return(&client_connection_)); route_entry_.connect_config_.emplace(RouteEntry::ConnectConfig()); diff --git a/test/server/server_test.cc b/test/server/server_test.cc index 10cc3a0192914..087f6d0b78488 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -1363,7 +1363,7 @@ TEST_P(ServerInstanceImplTest, WithUnknownBootstrapExtensions) { #ifndef WIN32 class SafeFatalAction : public Configuration::FatalAction { public: - void run(const ScopeTrackedObject* /*current_object*/) override { + void run(absl::Span /*tracked_objects*/) override { std::cerr << "Called SafeFatalAction" << std::endl; } @@ -1372,7 +1372,7 @@ class SafeFatalAction : public Configuration::FatalAction { class UnsafeFatalAction : public Configuration::FatalAction { public: - void run(const ScopeTrackedObject* /*current_object*/) override { + void run(absl::Span /*tracked_objects*/) override { std::cerr << "Called UnsafeFatalAction" << std::endl; } From da69a5e5967a50d73d753afa58c0e750523bb041 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Mon, 15 Mar 2021 15:10:19 +0900 Subject: [PATCH 13/28] ci: Fix macosx install flake (#15310) (#15435) Commit Message: ci: Fix macosx install flake Signed-off-by: Ryan Northey Signed-off-by: Shikugawa --- ci/mac_ci_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/mac_ci_setup.sh b/ci/mac_ci_setup.sh index 88f8c406dd655..f6cd976b59e20 100755 --- a/ci/mac_ci_setup.sh +++ b/ci/mac_ci_setup.sh @@ -47,7 +47,7 @@ if ! retry brew update; then echo "Failed to update homebrew" fi -DEPS="automake cmake coreutils go libtool wget ninja" +DEPS="automake cmake coreutils libtool wget ninja" for DEP in ${DEPS} do is_installed "${DEP}" || install "${DEP}" From c1eeffeae68189a5cbbb388634e27db0b3c63674 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Mon, 15 Mar 2021 22:52:19 +0900 Subject: [PATCH 14/28] wasm: fix getHeaderMapValue() in case of early client disconnect. (#15224) (#15395) Previously, getHeaderMapValuei() would incorrectly return BadArgument when called in the access log phase after an early client disconnect, because the code assumed that a HeaderMap can point to a nullptr only when called from a callback in which given map is not available. Fixes proxy-wasm/proxy-wasm-rust-sdk#82. Signed-off-by: Piotr Sikora Signed-off-by: Shikugawa Co-authored-by: Piotr Sikora --- source/extensions/common/wasm/context.cc | 21 +++++++++++++--- source/extensions/common/wasm/context.h | 1 + .../http/wasm/test_data/headers_rust.rs | 10 +++++--- .../filters/http/wasm/test_data/test_cpp.cc | 4 +++- .../filters/http/wasm/wasm_filter_test.cc | 24 +++++++++++++++---- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index aab2d7a7d76a4..ac2d6f9daf897 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -666,19 +666,22 @@ Http::HeaderMap* Context::getMap(WasmHeaderMapType type) { const Http::HeaderMap* Context::getConstMap(WasmHeaderMapType type) { switch (type) { case WasmHeaderMapType::RequestHeaders: - if (access_log_request_headers_) { + if (access_log_phase_) { return access_log_request_headers_; } return request_headers_; case WasmHeaderMapType::RequestTrailers: + if (access_log_phase_) { + return nullptr; + } return request_trailers_; case WasmHeaderMapType::ResponseHeaders: - if (access_log_response_headers_) { + if (access_log_phase_) { return access_log_response_headers_; } return response_headers_; case WasmHeaderMapType::ResponseTrailers: - if (access_log_response_trailers_) { + if (access_log_phase_) { return access_log_response_trailers_; } return response_trailers_; @@ -722,6 +725,16 @@ WasmResult Context::getHeaderMapValue(WasmHeaderMapType type, absl::string_view absl::string_view* value) { auto map = getConstMap(type); if (!map) { + if (access_log_phase_) { + // Maps might point to nullptr in the access log phase. + if (wasm()->abiVersion() == proxy_wasm::AbiVersion::ProxyWasm_0_1_0) { + *value = ""; + return WasmResult::Ok; + } else { + return WasmResult::NotFound; + } + } + // Requested map type is not currently available. return WasmResult::BadArgument; } const Http::LowerCaseString lower_key{std::string(key)}; @@ -1479,6 +1492,7 @@ void Context::log(const Http::RequestHeaderMap* request_headers, onCreate(); } + access_log_phase_ = true; access_log_request_headers_ = request_headers; // ? request_trailers ? access_log_response_headers_ = response_headers; @@ -1487,6 +1501,7 @@ void Context::log(const Http::RequestHeaderMap* request_headers, onLog(); + access_log_phase_ = false; access_log_request_headers_ = nullptr; // ? request_trailers ? access_log_response_headers_ = nullptr; diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 216b7eaed3aac..9eb53ab78a823 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -450,6 +450,7 @@ class Context : public proxy_wasm::ContextBase, Http::RequestHeaderMapPtr grpc_initial_metadata_; // Access log state. + bool access_log_phase_ = false; const StreamInfo::StreamInfo* access_log_stream_info_{}; const Http::RequestHeaderMap* access_log_request_headers_{}; const Http::ResponseHeaderMap* access_log_response_headers_{}; diff --git a/test/extensions/filters/http/wasm/test_data/headers_rust.rs b/test/extensions/filters/http/wasm/test_data/headers_rust.rs index dbdaff1368780..71fea4fb9573e 100644 --- a/test/extensions/filters/http/wasm/test_data/headers_rust.rs +++ b/test/extensions/filters/http/wasm/test_data/headers_rust.rs @@ -46,9 +46,13 @@ impl HttpContext for TestStream { } fn on_log(&mut self) { - if let Some(path) = self.get_http_request_header(":path") { - warn!("onLog {} {}", self.context_id, path); - } + let path = self + .get_http_request_header(":path") + .unwrap_or(String::from("")); + let status = self + .get_http_response_header(":status") + .unwrap_or(String::from("")); + warn!("onLog {} {} {}", self.context_id, path, status); } } diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index f09b6198f835e..6d3bb261b72e7 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -329,7 +329,9 @@ void TestContext::onLog() { auto test = root()->test_; if (test == "headers") { auto path = getRequestHeader(":path"); - logWarn("onLog " + std::to_string(id()) + " " + std::string(path->view())); + auto status = getResponseHeader(":status"); + logWarn("onLog " + std::to_string(id()) + " " + std::string(path->view()) + " " + + std::string(status->view())); auto response_header = getResponseHeader("bogus-header"); if (response_header && response_header->view() != "") { logWarn("response bogus-header found"); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 35018c56ff6ee..28b5949f6a46f 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -548,11 +548,11 @@ TEST_P(WasmHttpFilterTest, AccessLog) { EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / 200")))); EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::TestResponseHeaderMapImpl response_headers{}; + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; Http::TestResponseTrailerMapImpl response_trailers{}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); filter().continueStream(proxy_wasm::WasmStreamType::Response); @@ -562,15 +562,31 @@ TEST_P(WasmHttpFilterTest, AccessLog) { filter().onDestroy(); } +TEST_P(WasmHttpFilterTest, AccessLogClientDisconnected) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / ")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + StreamInfo::MockStreamInfo log_stream_info; + filter().log(&request_headers, nullptr, nullptr, log_stream_info); + filter().onDestroy(); +} + TEST_P(WasmHttpFilterTest, AccessLogCreate) { setupTest("", "headers"); setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / 200")))); EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); StreamInfo::MockStreamInfo log_stream_info; Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::TestResponseHeaderMapImpl response_headers{}; + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; Http::TestResponseTrailerMapImpl response_trailers{}; filter().log(&request_headers, &response_headers, &response_trailers, log_stream_info); filter().onDestroy(); From b44d1766e24bc8cbd30a28223d38047d808fd2a8 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Tue, 16 Mar 2021 01:48:09 +0900 Subject: [PATCH 15/28] container: Only drop privs if user is root (#15115) (#15397) Signed-off-by: Ryan Northey ryan@synca.io Signed-off-by: Shikugawa rei@tetrate.io For an explanation of how to fill out the fields, please see the relevant section in PULL_REQUESTS.md Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Deprecated:] [Optional API Considerations:] Signed-off-by: Ryan Northey Signed-off-by: Shikugawa Co-authored-by: phlax --- ci/docker-entrypoint.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/docker-entrypoint.sh b/ci/docker-entrypoint.sh index 6e584d37e3d70..b778560db0f35 100755 --- a/ci/docker-entrypoint.sh +++ b/ci/docker-entrypoint.sh @@ -2,6 +2,8 @@ set -e loglevel="${loglevel:-}" +USERID=$(id -u) + # if the first argument look like a parameter (i.e. start with '-'), run Envoy if [ "${1#-}" != "$1" ]; then @@ -15,7 +17,7 @@ if [ "$1" = 'envoy' ]; then fi fi -if [ "$ENVOY_UID" != "0" ]; then +if [ "$ENVOY_UID" != "0" ] && [ "$USERID" = 0 ]; then if [ -n "$ENVOY_UID" ]; then usermod -u "$ENVOY_UID" envoy fi From 8a8d1a3913e9b70c3a3ea96fae785c1baac336b5 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Tue, 16 Mar 2021 02:10:53 +0900 Subject: [PATCH 16/28] wasm: fix crash on HTTP callout to a bad cluster. (#15311) (#15396) Fixes envoyproxy/envoy#14878, proxy-wasm/proxy-wasm-cpp-sdk#81. Signed-off-by: Piotr Sikora Signed-off-by: Shikugawa Co-authored-by: Piotr Sikora --- source/extensions/common/wasm/context.cc | 16 +++++-- .../http/wasm/test_data/async_call_rust.rs | 9 ++-- .../wasm/test_data/test_async_call_cpp.cc | 42 ++++++++++--------- .../filters/http/wasm/wasm_filter_test.cc | 35 +++++++++++++--- 4 files changed, 68 insertions(+), 34 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index ac2d6f9daf897..bbf2803128149 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -951,6 +951,8 @@ WasmResult Context::httpCall(absl::string_view cluster, const Pairs& request_hea uint32_t token = nextHttpCallToken(); auto& handler = http_request_[token]; + handler.context_ = this; + handler.token_ = token; // set default hash policy to be based on :authority to enable consistent hash Http::AsyncClient::RequestOptions options; @@ -964,8 +966,6 @@ WasmResult Context::httpCall(absl::string_view cluster, const Pairs& request_hea http_request_.erase(token); return WasmResult::InternalFailure; } - handler.context_ = this; - handler.token_ = token; handler.request_ = http_request; *token_ptr = token; return WasmResult::Ok; @@ -1752,12 +1752,16 @@ void Context::onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr& }); return; } + auto handler = http_request_.find(token); + if (handler == http_request_.end()) { + return; + } http_call_response_ = &response; uint32_t body_size = response->body().length(); onHttpCallResponse(token, response->headers().size(), body_size, headerSize(response->trailers())); http_call_response_ = nullptr; - http_request_.erase(token); + http_request_.erase(handler); } void Context::onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason reason) { @@ -1766,13 +1770,17 @@ void Context::onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason wasm()->addAfterVmCallAction([this, token, reason] { onHttpCallFailure(token, reason); }); return; } + auto handler = http_request_.find(token); + if (handler == http_request_.end()) { + return; + } status_code_ = static_cast(WasmResult::BrokenConnection); // This is the only value currently. ASSERT(reason == Http::AsyncClient::FailureReason::Reset); status_message_ = "reset"; onHttpCallResponse(token, 0, 0, 0); status_message_ = ""; - http_request_.erase(token); + http_request_.erase(handler); } void Context::onGrpcReceiveWrapper(uint32_t token, ::Envoy::Buffer::InstancePtr response) { diff --git a/test/extensions/filters/http/wasm/test_data/async_call_rust.rs b/test/extensions/filters/http/wasm/test_data/async_call_rust.rs index 0cb7833c4437d..314a599f8b665 100644 --- a/test/extensions/filters/http/wasm/test_data/async_call_rust.rs +++ b/test/extensions/filters/http/wasm/test_data/async_call_rust.rs @@ -13,15 +13,16 @@ struct TestStream; impl HttpContext for TestStream { fn on_http_request_headers(&mut self, _: usize) -> Action { - self.dispatch_http_call( + match self.dispatch_http_call( "cluster", vec![(":method", "POST"), (":path", "/"), (":authority", "foo")], Some(b"hello world"), vec![("trail", "cow")], Duration::from_secs(5), - ) - .unwrap(); - info!("onRequestHeaders"); + ) { + Ok(_) => info!("onRequestHeaders"), + Err(_) => info!("async_call rejected"), + }; Action::Pause } } diff --git a/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc index 8075ef63b5785..bdaf73f7f5654 100644 --- a/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc @@ -27,7 +27,7 @@ static RegisterContextFactory register_AsyncCallContext(CONTEXT_FACTORY(AsyncCal ROOT_FACTORY(AsyncCallRootContext), "async_call"); -FilterHeadersStatus AsyncCallContext::onRequestHeaders(uint32_t, bool end_of_stream) { +FilterHeadersStatus AsyncCallContext::onRequestHeaders(uint32_t, bool) { auto context_id = id(); auto callback = [context_id](uint32_t, size_t body_size, uint32_t) { if (body_size == 0) { @@ -47,29 +47,31 @@ FilterHeadersStatus AsyncCallContext::onRequestHeaders(uint32_t, bool end_of_str logWarn(std::string(p.first) + std::string(" -> ") + std::string(p.second)); } }; - if (end_of_stream) { + auto path = getRequestHeader(":path"); + if (path->view() == "/bad") { if (root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, + "hello world", {{"trail", "cow"}}, 1000, callback) != WasmResult::Ok) { + logInfo("async_call rejected"); + } + } else { + if (root()->httpCall("bogus cluster", + {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, "hello world", {{"trail", "cow"}}, 1000, callback) == WasmResult::Ok) { - logError("expected failure did not"); + logError("bogus cluster found error"); } - return FilterHeadersStatus::Continue; - } - if (root()->httpCall("bogus cluster", - {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, "hello world", - {{"trail", "cow"}}, 1000, callback) == WasmResult::Ok) { - logError("bogus cluster found error"); - } - if (root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, - "hello world", {{"trail", "cow"}}, 0xFFFFFFFF, callback) == WasmResult::Ok) { - logError("bogus timeout accepted error"); - } - if (root()->httpCall("cluster", {{":method", "POST"}, {":authority", "foo"}}, "hello world", - {{"trail", "cow"}}, 1000, callback) == WasmResult::Ok) { - logError("emissing path accepted error"); + if (root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, + "hello world", {{"trail", "cow"}}, 0xFFFFFFFF, + callback) == WasmResult::Ok) { + logError("bogus timeout accepted error"); + } + if (root()->httpCall("cluster", {{":method", "POST"}, {":authority", "foo"}}, "hello world", + {{"trail", "cow"}}, 1000, callback) == WasmResult::Ok) { + logError("emissing path accepted error"); + } + root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, + "hello world", {{"trail", "cow"}}, 1000, callback); + logInfo("onRequestHeaders"); } - root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, - "hello world", {{"trail", "cow"}}, 1000, callback); - logInfo("onRequestHeaders"); return FilterHeadersStatus::StopIteration; } diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 28b5949f6a46f..13e08e09d6ad3 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -684,14 +684,10 @@ TEST_P(WasmHttpFilterTest, StopAndResumeViaAsyncCall) { } TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): The Rust SDK does not support end_of_stream in on_http_request_headers. - return; - } setupTest("async_call"); setupFilter(); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::TestRequestHeaderMapImpl request_headers{{":path", "/bad"}}; Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); cluster_manager_.initializeThreadLocalClusters({"cluster"}); EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); @@ -703,7 +699,34 @@ TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { return nullptr; })); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call rejected"))); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); +} + +TEST_P(WasmHttpFilterTest, AsyncCallBadCluster) { + setupTest("async_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/bad"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response( + new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "503"}}})); + // Simulate code path for "no healthy host for HTTP connection pool" inline callback. + cb.onSuccess(request, std::move(response)); + return nullptr; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call rejected"))); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); } TEST_P(WasmHttpFilterTest, AsyncCallFailure) { From e2c5f20c29e9ffd2253e6650c699cd4a32c7dbe6 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 13 Apr 2021 15:16:56 -0700 Subject: [PATCH 17/28] wasm: update V8 to v9.0.257.17 (security fix). (#15942) Signed-off-by: Piotr Sikora --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 56d531f499dde..3c7213ccb672e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -665,14 +665,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "V8", project_desc = "Google’s open source high-performance JavaScript and WebAssembly engine, written in C++", project_url = "https://v8.dev", - version = "8.8.278.8", + version = "9.0.257.17", # This archive was created using https://storage.googleapis.com/envoyproxy-wee8/wee8-archive.sh # and contains complete checkout of V8 with all dependencies necessary to build wee8. - sha256 = "0c5c7b534a619d3f6077dd3583b7976a2cfe7f8ea71ca1e2d81a9de1d40131f9", + sha256 = "0eaf060eae4907f7d961fc31b0692175003f56cee320c7e4da4d19b47c2557f3", urls = ["https://storage.googleapis.com/envoyproxy-wee8/wee8-{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.v8"], - release_date = "2020-12-04", + release_date = "2021-04-12", cpe = "cpe:2.3:a:google:v8:*", ), com_googlesource_quiche = dict( From 72c16ed32b0ba3ac1b8e9d571d3a8cbe4a9e9f40 Mon Sep 17 00:00:00 2001 From: Tony Allen Date: Thu, 25 Mar 2021 12:23:55 -0400 Subject: [PATCH 18/28] backport to 1.17: http: Fixing empty metadata map handling Commit Message: Fixing a crash when the decoder receives an empty metadata map. Additional Description: Upon receiving an empty metadata map and trying to decode it an assertion is triggered in debug mode, and a seg-fault occurs in release mode. The proposed fix ignores the empty metadata maps and updates a stats if one is received. Risk Level: Medium for Envoy's running with Metadata support. Testing: Added integration tests. Docs Changes: Added a codec stats counter description. Release Notes: Added bug fix description. Platform Specific Features: N/A. Fixes a fuzz bug: 25303 Signed-off-by: Adi Suissa-Peleg Signed-off-by: Tony Allen --- .../http/http_conn_man/stats.rst | 1 + source/common/http/http2/codec_impl.cc | 8 +- source/common/http/http2/codec_stats.h | 1 + test/common/http/http2/http2_frame.cc | 2 +- test/common/http/http2/http2_frame.h | 2 +- test/integration/http2_integration_test.cc | 93 +++++++++++++++++++ .../integration/integration_stream_decoder.cc | 1 + test/integration/integration_stream_decoder.h | 2 + 8 files changed, 107 insertions(+), 3 deletions(-) diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index c6aa07f284d49..9c7d441b7fff2 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -135,6 +135,7 @@ On the upstream side all http2 statistics are rooted at *cluster..http2.* inbound_empty_frames_flood, Counter, Total number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting `. inbound_priority_frames_flood, Counter, Total number of connections terminated for exceeding the limit on inbound frames of type PRIORITY. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting `. inbound_window_update_frames_flood, Counter, Total number of connections terminated for exceeding the limit on inbound frames of type WINDOW_UPDATE. The limit is configured by setting the :ref:`max_inbound_window_updateframes_per_data_frame_sent config setting `. + metadata_empty_frames, Counter, Total number of metadata frames that were received and contained empty maps. outbound_flood, Counter, Total number of connections terminated for exceeding the limit on outbound frames of all types. The limit is configured by setting the :ref:`max_outbound_frames config setting `. outbound_control_flood, Counter, "Total number of connections terminated for exceeding the limit on outbound frames of types PING, SETTINGS and RST_STREAM. The limit is configured by setting the :ref:`max_outbound_control_frames config setting `." requests_rejected_with_underscores_in_headers, Counter, Total numbers of rejected requests due to header names containing underscores. This action is configured by setting the :ref:`headers_with_underscores_action config setting `. diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 74a565b279164..00231ea364c0c 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -553,7 +553,13 @@ MetadataDecoder& ConnectionImpl::StreamImpl::getMetadataDecoder() { } void ConnectionImpl::StreamImpl::onMetadataDecoded(MetadataMapPtr&& metadata_map_ptr) { - decoder().decodeMetadata(std::move(metadata_map_ptr)); + // Empty metadata maps should not be decoded. + if (metadata_map_ptr->empty()) { + ENVOY_CONN_LOG(debug, "decode metadata called with empty map, skipping", parent_.connection_); + parent_.stats_.metadata_empty_frames_.inc(); + } else { + decoder().decodeMetadata(std::move(metadata_map_ptr)); + } } ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stats, diff --git a/source/common/http/http2/codec_stats.h b/source/common/http/http2/codec_stats.h index ba9d1592095bb..eb11291f82f4b 100644 --- a/source/common/http/http2/codec_stats.h +++ b/source/common/http/http2/codec_stats.h @@ -19,6 +19,7 @@ namespace Http2 { COUNTER(inbound_empty_frames_flood) \ COUNTER(inbound_priority_frames_flood) \ COUNTER(inbound_window_update_frames_flood) \ + COUNTER(metadata_empty_frames) \ COUNTER(outbound_control_flood) \ COUNTER(outbound_flood) \ COUNTER(requests_rejected_with_underscores_in_headers) \ diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index 6717ec49b3b85..266f7680dc45c 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -248,7 +248,7 @@ Http2Frame Http2Frame::makeWindowUpdateFrame(uint32_t stream_index, uint32_t inc // Note: encoder in codebase persists multiple maps, with each map representing an individual frame. Http2Frame Http2Frame::makeMetadataFrameFromMetadataMap(uint32_t stream_index, - MetadataMap& metadata_map, + const MetadataMap& metadata_map, MetadataFlags flags) { const int numberOfNameValuePairs = metadata_map.size(); absl::FixedArray nameValues(numberOfNameValuePairs); diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 53465a6f9248a..370316fc787cf 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -149,7 +149,7 @@ class Http2Frame { static Http2Frame makeWindowUpdateFrame(uint32_t stream_index, uint32_t increment); static Http2Frame makeMetadataFrameFromMetadataMap(uint32_t stream_index, - MetadataMap& metadata_map, + const MetadataMap& metadata_map, MetadataFlags flags); static Http2Frame makeMalformedRequest(uint32_t stream_index); diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc index a0e73bdbde68c..9a9251c0a262f 100644 --- a/test/integration/http2_integration_test.cc +++ b/test/integration/http2_integration_test.cc @@ -180,6 +180,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { response->waitForEndStream(); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the second request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -199,6 +200,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { response->waitForEndStream(); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the third request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -218,6 +220,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { response->waitForEndStream(); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the fourth request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -238,6 +241,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { response->waitForEndStream(); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the fifth request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -258,6 +262,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { response->waitForEndStream(); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the sixth request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -308,6 +313,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { // Verifies multiple metadata are received by the client. response->waitForEndStream(); ASSERT_TRUE(response->complete()); + EXPECT_EQ(4, response->metadataMapsDecodedCount()); for (int i = 0; i < size; i++) { for (const auto& metadata : *multiple_vecs[i][0]) { EXPECT_EQ(response->metadataMap().find(metadata.first)->second, metadata.second); @@ -341,6 +347,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { // Verifies metadata is not received by the client. response->waitForEndStream(); ASSERT_TRUE(response->complete()); + EXPECT_EQ(0, response->metadataMapsDecodedCount()); EXPECT_EQ(response->metadataMap().size(), 0); } @@ -382,6 +389,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("data"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(2, response->metadataMapsDecodedCount()); // Upstream responds with headers, data and trailers. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -396,6 +404,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("trailers"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 3); + EXPECT_EQ(4, response->metadataMapsDecodedCount()); // Upstream responds with headers, 100-continue and data. response = @@ -418,6 +427,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("100-continue"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 4); + EXPECT_EQ(4, response->metadataMapsDecodedCount()); // Upstream responds with headers and metadata that will not be consumed. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -436,6 +446,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("aaa"); expected_metadata_keys.insert("keep"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); + EXPECT_EQ(2, response->metadataMapsDecodedCount()); // Upstream responds with headers, data and metadata that will be consumed. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -455,6 +466,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("replace"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(3, response->metadataMapsDecodedCount()); } TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) { @@ -1568,6 +1580,87 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FrameIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +// Tests sending an empty metadata map from downstream. +TEST_P(Http2FrameIntegrationTest, DownstreamSendingEmptyMetadata) { + // Allow metadata usage. + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + cluster->mutable_http2_protocol_options()->set_allow_metadata(true); + }); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_http2_protocol_options()->set_allow_metadata(true); }); + + // This test uses an Http2Frame and not the encoder's encodeMetadata method, + // because encodeMetadata fails when an empty metadata map is sent. + beginSession(); + FakeHttpConnectionPtr fake_upstream_connection; + FakeStreamPtr fake_upstream_request; + + const uint32_t client_stream_idx = 1; + // Send request. + const Http2Frame request = + Http2Frame::makePostRequest(client_stream_idx, "host", "/path/to/long/url"); + sendFrame(request); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForNewStream(*dispatcher_, fake_upstream_request)); + + // Send metadata frame with empty metadata map. + const Http::MetadataMap empty_metadata_map; + const Http2Frame empty_metadata_map_frame = Http2Frame::makeMetadataFrameFromMetadataMap( + client_stream_idx, empty_metadata_map, Http2Frame::MetadataFlags::EndMetadata); + sendFrame(empty_metadata_map_frame); + + // Send an empty data frame to close the stream. + const Http2Frame empty_data_frame = + Http2Frame::makeEmptyDataFrame(client_stream_idx, Http2Frame::DataFlags::EndStream); + sendFrame(empty_data_frame); + + // Upstream sends a reply. + ASSERT_TRUE(fake_upstream_request->waitForEndStream(*dispatcher_)); + const Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + fake_upstream_request->encodeHeaders(response_headers, true); + + // Make sure that a response from upstream is received by the client, and + // close the connection. + const auto response = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, response.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, response.responseStatus()); + EXPECT_EQ(1, test_server_->counter("http2.metadata_empty_frames")->value()); + + // Cleanup. + tcp_client_->close(); +} + +// Tests that an empty metadata map from upstream is ignored. +TEST_P(Http2MetadataIntegrationTest, UpstreamSendingEmptyMetadata) { + initialize(); + + // Send a request and make sure an upstream connection is established. + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + auto* upstream = fake_upstreams_.front().get(); + + // Send response headers. + upstream_request_->encodeHeaders(default_response_headers_, false); + // Send an empty metadata map back from upstream. + const Http::MetadataMap empty_metadata_map; + const Http2Frame empty_metadata_frame = Http2Frame::makeMetadataFrameFromMetadataMap( + 1, empty_metadata_map, Http2Frame::MetadataFlags::EndMetadata); + ASSERT_TRUE(upstream->rawWriteConnection( + 0, std::string(empty_metadata_frame.begin(), empty_metadata_frame.end()))); + // Send an empty data frame after the metadata frame to end the stream. + upstream_request_->encodeData(0, true); + + // Verifies that no metadata was received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(0, response->metadataMapsDecodedCount()); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.metadata_empty_frames")->value()); +} + // Tests upstream sending a metadata frame after ending a stream. TEST_P(Http2MetadataIntegrationTest, UpstreamMetadataAfterEndStream) { initialize(); diff --git a/test/integration/integration_stream_decoder.cc b/test/integration/integration_stream_decoder.cc index 6ac864cdabbb1..0b7566675317a 100644 --- a/test/integration/integration_stream_decoder.cc +++ b/test/integration/integration_stream_decoder.cc @@ -97,6 +97,7 @@ void IntegrationStreamDecoder::decodeTrailers(Http::ResponseTrailerMapPtr&& trai } void IntegrationStreamDecoder::decodeMetadata(Http::MetadataMapPtr&& metadata_map) { + metadata_maps_decoded_count_++; // Combines newly received metadata with the existing metadata. for (const auto& metadata : *metadata_map) { duplicated_metadata_key_count_[metadata.first]++; diff --git a/test/integration/integration_stream_decoder.h b/test/integration/integration_stream_decoder.h index 888a4ad266000..99486c78b7ce0 100644 --- a/test/integration/integration_stream_decoder.h +++ b/test/integration/integration_stream_decoder.h @@ -29,6 +29,7 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre const Http::ResponseTrailerMapPtr& trailers() { return trailers_; } const Http::MetadataMap& metadataMap() { return *metadata_map_; } uint64_t keyCount(std::string key) { return duplicated_metadata_key_count_[key]; } + uint32_t metadataMapsDecodedCount() const { return metadata_maps_decoded_count_; } void waitForContinueHeaders(); void waitForHeaders(); // This function waits until body_ has at least size bytes in it (it might have more). clearBody() @@ -70,6 +71,7 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre bool waiting_for_headers_{}; bool saw_reset_{}; Http::StreamResetReason reset_reason_{}; + uint32_t metadata_maps_decoded_count_{}; }; using IntegrationStreamDecoderPtr = std::unique_ptr; From e93a77b8fb5640863c616d0390d42e23bc16c7b7 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Fri, 26 Mar 2021 02:03:05 +0900 Subject: [PATCH 19/28] ssl: fix crash when peer sends an SSL Alert with an unknown code Fix for CVE-2021-28683 (crash when peer sends an SSL Alert with an unknown code) Signed-off-by: Greg Greenway Co-authored-by: Christoph Pakulski Signed-off-by: Tony Allen --- .../transport_sockets/tls/context_impl.cc | 7 +++-- .../transport_sockets/tls/ssl_socket.cc | 7 +++-- .../tls/integration/ssl_integration_test.cc | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index ee84dec354b98..b0782b9a71cb6 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -296,9 +296,10 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c if (ctx.cert_chain_ == nullptr || !SSL_CTX_use_certificate(ctx.ssl_ctx_.get(), ctx.cert_chain_.get())) { while (uint64_t err = ERR_get_error()) { - ENVOY_LOG_MISC(debug, "SSL error: {}:{}:{}:{}", err, ERR_lib_error_string(err), - ERR_func_error_string(err), ERR_GET_REASON(err), - ERR_reason_error_string(err)); + ENVOY_LOG_MISC(debug, "SSL error: {}:{}:{}:{}", err, + absl::NullSafeStringView(ERR_lib_error_string(err)), + absl::NullSafeStringView(ERR_func_error_string(err)), ERR_GET_REASON(err), + absl::NullSafeStringView(ERR_reason_error_string(err))); } throw EnvoyException( absl::StrCat("Failed to load certificate chain from ", ctx.cert_chain_file_path_)); diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index ba408c2d417fa..ab77f6c8fe22d 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -217,9 +217,10 @@ void SslSocket::drainErrorQueue() { if (failure_reason_.empty()) { failure_reason_ = "TLS error:"; } - failure_reason_.append(absl::StrCat(" ", err, ":", ERR_lib_error_string(err), ":", - ERR_func_error_string(err), ":", - ERR_reason_error_string(err))); + failure_reason_.append(absl::StrCat(" ", err, ":", + absl::NullSafeStringView(ERR_lib_error_string(err)), ":", + absl::NullSafeStringView(ERR_func_error_string(err)), ":", + absl::NullSafeStringView(ERR_reason_error_string(err)))); } ENVOY_CONN_LOG(debug, "{}", callbacks_->connection(), failure_reason_); if (saw_error && !saw_counted_error) { diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index 16896443a6a89..1047566c12187 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -18,6 +18,7 @@ #include "extensions/transport_sockets/tls/context_config_impl.h" #include "extensions/transport_sockets/tls/context_manager_impl.h" +#include "extensions/transport_sockets/tls/ssl_handshaker.h" #include "test/extensions/common/tap/common.h" #include "test/integration/autonomous_upstream.h" @@ -90,6 +91,36 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, SslIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +// Test that Envoy behaves correctly when receiving an SSLAlert for an unspecified code. The codes +// are defined in the standard, and assigned codes have a string associated with them in BoringSSL, +// which is included in logs. For an unknown code, verify that no crash occurs. +TEST_P(SslIntegrationTest, UnknownSslAlert) { + initialize(); + Network::ClientConnectionPtr connection = makeSslClientConnection({}); + ConnectionStatusCallbacks callbacks; + connection->addConnectionCallbacks(callbacks); + connection->connect(); + while (!callbacks.connected()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + Ssl::ConnectionInfoConstSharedPtr ssl_info = connection->ssl(); + SSL* ssl = + dynamic_cast(ssl_info.get()) + ->ssl(); + ASSERT_EQ(connection->state(), Network::Connection::State::Open); + ASSERT_NE(ssl, nullptr); + SSL_send_fatal_alert(ssl, 255); + while (!callbacks.closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + const std::string counter_name = listenerStatPrefix("ssl.connection_error"); + Stats::CounterSharedPtr counter = test_server_->counter(counter_name); + test_server_->waitForCounterGe(counter_name, 1); + connection->close(Network::ConnectionCloseType::NoFlush); +} + TEST_P(SslIntegrationTest, RouterRequestAndResponseWithGiantBodyBuffer) { ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection({}); From 5c3c65651743714dfd0edaf65ba806961db2c54c Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Fri, 26 Mar 2021 02:14:57 +0900 Subject: [PATCH 20/28] grpc: fix grpc-timeout integer overflow Fixes CVE-2021-28682, a remotely exploitable integer overflow. Signed-off-by: Asra Ali Co-authored-by: Tony Allen Co-authored-by: Christoph Pakulski Signed-off-by: Tony Allen --- source/common/grpc/common.cc | 18 ++++++++++++------ source/common/grpc/common.h | 2 ++ test/common/grpc/common_test.cc | 18 ++++++++++++++++-- test/common/http/conn_manager_impl_test.cc | 13 +++++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/source/common/grpc/common.cc b/source/common/grpc/common.cc index 8b7551d8bea29..542b204753dbe 100644 --- a/source/common/grpc/common.cc +++ b/source/common/grpc/common.cc @@ -167,12 +167,18 @@ Common::getGrpcTimeout(const Http::RequestHeaderMap& request_headers) { const Http::HeaderEntry* header_grpc_timeout_entry = request_headers.GrpcTimeout(); std::chrono::milliseconds timeout; if (header_grpc_timeout_entry) { - uint64_t grpc_timeout; - // TODO(dnoe): Migrate to pure string_view (#6580) - std::string grpc_timeout_string(header_grpc_timeout_entry->value().getStringView()); - const char* unit = StringUtil::strtoull(grpc_timeout_string.c_str(), grpc_timeout); - if (unit != nullptr && *unit != '\0') { - switch (*unit) { + int64_t grpc_timeout; + absl::string_view timeout_entry = header_grpc_timeout_entry->value().getStringView(); + if (timeout_entry.empty()) { + // Must be of the form TimeoutValue TimeoutUnit. See + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests. + return absl::nullopt; + } + // TimeoutValue must be a positive integer of at most 8 digits. + if (absl::SimpleAtoi(timeout_entry.substr(0, timeout_entry.size() - 1), &grpc_timeout) && + grpc_timeout >= 0 && static_cast(grpc_timeout) <= MAX_GRPC_TIMEOUT_VALUE) { + const char unit = timeout_entry[timeout_entry.size() - 1]; + switch (unit) { case 'H': return std::chrono::hours(grpc_timeout); case 'M': diff --git a/source/common/grpc/common.h b/source/common/grpc/common.h index f76082610f317..dcade2ad8015a 100644 --- a/source/common/grpc/common.h +++ b/source/common/grpc/common.h @@ -178,6 +178,8 @@ class Common { private: static void checkForHeaderOnlyError(Http::ResponseMessage& http_response); + + static constexpr size_t MAX_GRPC_TIMEOUT_VALUE = 99999999; }; } // namespace Grpc diff --git a/test/common/grpc/common_test.cc b/test/common/grpc/common_test.cc index 3f6d88dd00963..4a54ee4b6bbbe 100644 --- a/test/common/grpc/common_test.cc +++ b/test/common/grpc/common_test.cc @@ -78,6 +78,9 @@ TEST(GrpcContextTest, GetGrpcTimeout) { Http::TestRequestHeaderMapImpl missing_unit{{"grpc-timeout", "123"}}; EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(missing_unit)); + Http::TestRequestHeaderMapImpl small_missing_unit{{"grpc-timeout", "1"}}; + EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(small_missing_unit)); + Http::TestRequestHeaderMapImpl illegal_unit{{"grpc-timeout", "123F"}}; EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(illegal_unit)); @@ -102,8 +105,19 @@ TEST(GrpcContextTest, GetGrpcTimeout) { Http::TestRequestHeaderMapImpl unit_nanoseconds{{"grpc-timeout", "12345678n"}}; EXPECT_EQ(std::chrono::milliseconds(13), Common::getGrpcTimeout(unit_nanoseconds)); - // Max 8 digits and no leading whitespace or +- signs are not enforced on decode, - // so we don't test for them. + // Test max 8 digits to prevent millisecond overflow. + Http::TestRequestHeaderMapImpl value_overflow{{"grpc-timeout", "6666666666666H"}}; + EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(value_overflow)); + + // Reject negative values. + Http::TestRequestHeaderMapImpl value_negative{{"grpc-timeout", "-1S"}}; + EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(value_negative)); + + // Allow positive values marked with +. + Http::TestRequestHeaderMapImpl value_positive{{"grpc-timeout", "+1S"}}; + EXPECT_EQ(std::chrono::milliseconds(1000), Common::getGrpcTimeout(value_positive)); + + // No leading whitespace are not enforced on decode so we don't test for them. } TEST(GrpcCommonTest, GrpcStatusDetailsBin) { diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 04df5b2070e64..ccd3836948063 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -2284,6 +2284,19 @@ TEST_F(HttpConnectionManagerImplTest, DurationTimeout) { decoder_filters_[0]->callbacks_->clusterInfo(); } + // With an invalid gRPC timeout, refreshing cached route will not use header and use stream + // duration. + latched_headers->setGrpcTimeout("6666666666666H"); + { + // 25ms used already from previous case so timer is set to be 5ms. + EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(5), _)); + EXPECT_CALL(route_config_provider_.route_config_->route_->route_entry_, maxStreamDuration()) + .Times(2) + .WillRepeatedly(Return(std::chrono::milliseconds(30))); + decoder_filters_[0]->callbacks_->clearRouteCache(); + decoder_filters_[0]->callbacks_->clusterInfo(); + } + // Cleanup. EXPECT_CALL(*timer, disableTimer()); EXPECT_CALL(*decoder_filters_[0], onStreamComplete()); From 21fe4496ca0c7798d6a9a747fdbf1ec1071e7f4f Mon Sep 17 00:00:00 2001 From: Tony Allen Date: Thu, 15 Apr 2021 09:58:37 -0600 Subject: [PATCH 21/28] v1.17.2 release Signed-off-by: Tony Allen --- VERSION | 2 +- docs/root/version_history/current.rst | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 3fcc8df681aae..06fb41b6322f3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.2-dev +1.17.2 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3ae7afe1afa8e..24023eae3510d 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,5 +1,5 @@ -1.17.2 (Pending) -========================== +1.17.2 (April 15, 2021) +======================= Incompatible Behavior Changes ----------------------------- @@ -13,7 +13,10 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. * http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* tls: fix a crash when peer sends a TLS Alert with an unknown code. Removed Config or Runtime ------------------------- From d1314d0a3d600e64e38190b029184c6649b08dde Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 21 Apr 2021 23:11:45 +0000 Subject: [PATCH 22/28] Kick-off release 1.17.3 (#16044) Signed-off-by: Yan Avlasov --- VERSION | 2 +- docs/root/version_history/current.rst | 8 +---- docs/root/version_history/v1.17.2.rst | 30 +++++++++++++++++++ docs/root/version_history/version_history.rst | 1 + 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 docs/root/version_history/v1.17.2.rst diff --git a/VERSION b/VERSION index 06fb41b6322f3..a4d7fa595723c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.2 +1.17.3-dev diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 24023eae3510d..711dc25ab04a3 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,4 +1,4 @@ -1.17.2 (April 15, 2021) +1.17.3 (Pending) ======================= Incompatible Behavior Changes @@ -13,18 +13,12 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. -* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. -* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. -* tls: fix a crash when peer sends a TLS Alert with an unknown code. - Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` New Features ------------ -* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. Deprecated ---------- diff --git a/docs/root/version_history/v1.17.2.rst b/docs/root/version_history/v1.17.2.rst new file mode 100644 index 0000000000000..24023eae3510d --- /dev/null +++ b/docs/root/version_history/v1.17.2.rst @@ -0,0 +1,30 @@ +1.17.2 (April 15, 2021) +======================= + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* tls: fix a crash when peer sends a TLS Alert with an unknown code. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ +* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. + +Deprecated +---------- diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index afb1363d28840..74b1f422229d7 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.17.2 v1.17.1 v1.17.0 v1.16.2 From a1f761ae608aafd016bc6bba93abc927726e19e8 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 5 May 2021 23:35:39 +0000 Subject: [PATCH 23/28] Fix compile_time_options Http2FrameIntegrationTest.DownstreamSendingEmptyMetadata test (#16343) Signed-off-by: Yan Avlasov --- test/integration/http2_integration_test.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc index 9a9251c0a262f..9878f85eeebd1 100644 --- a/test/integration/http2_integration_test.cc +++ b/test/integration/http2_integration_test.cc @@ -1585,8 +1585,12 @@ TEST_P(Http2FrameIntegrationTest, DownstreamSendingEmptyMetadata) { // Allow metadata usage. config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); - auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); - cluster->mutable_http2_protocol_options()->set_allow_metadata(true); + ConfigHelper::HttpProtocolOptions protocol_options; + protocol_options.mutable_explicit_http_config() + ->mutable_http2_protocol_options() + ->set_allow_metadata(true); + ConfigHelper::setProtocolOptions(*bootstrap.mutable_static_resources()->mutable_clusters(0), + protocol_options); }); config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& From f77a357cd2f3d5e6d71c9ba7bb259296f88d2e58 Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Thu, 1 Apr 2021 12:34:21 -0400 Subject: [PATCH 24/28] Implement handling of escaped slash characters in URL path Fixes: CVE-2021-29492 Signed-off-by: Yan Avlasov --- .../v3/http_connection_manager.proto | 39 +++- .../v4alpha/http_connection_manager.proto | 39 +++- .../best_practices/_include/edge.yaml | 3 + .../configuration/best_practices/edge.rst | 12 ++ .../http/http_conn_man/runtime.rst | 19 ++ .../http/http_conn_man/stats.rst | 2 + docs/root/version_history/current.rst | 1 + .../v3/http_connection_manager.proto | 39 +++- .../v4alpha/http_connection_manager.proto | 39 +++- source/common/http/conn_manager_config.h | 10 + source/common/http/conn_manager_impl.cc | 21 +- source/common/http/conn_manager_utility.cc | 42 +++- source/common/http/conn_manager_utility.h | 13 +- source/common/http/path_utility.cc | 35 ++++ source/common/http/path_utility.h | 11 ++ .../network/http_connection_manager/config.cc | 48 ++++- .../network/http_connection_manager/config.h | 7 + source/server/admin/admin.h | 6 + .../http/conn_manager_impl_fuzz_test.cc | 6 + test/common/http/conn_manager_impl_test.cc | 96 +++++++++ .../http/conn_manager_impl_test_base.cc | 25 +++ .../common/http/conn_manager_impl_test_base.h | 11 ++ test/common/http/conn_manager_utility_test.cc | 177 ++++++++++++++++- test/common/http/path_utility_test.cc | 55 ++++++ .../http_connection_manager/config_test.cc | 182 +++++++++++++++++- test/integration/header_integration_test.cc | 169 +++++++++++++++- test/integration/protocol_integration_test.cc | 19 ++ test/mocks/http/mocks.h | 3 + 28 files changed, 1099 insertions(+), 30 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 395c6b67f0329..f590f2425b5a7 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -35,7 +35,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 43] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -98,6 +98,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -528,6 +558,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. // diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index 6afb11505ceeb..2f42153cf081a 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -34,7 +34,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 43] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -97,6 +97,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -527,6 +557,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. // diff --git a/docs/root/configuration/best_practices/_include/edge.yaml b/docs/root/configuration/best_practices/_include/edge.yaml index 21a6b7e7a5c14..f3db4288b7bfa 100644 --- a/docs/root/configuration/best_practices/_include/edge.yaml +++ b/docs/root/configuration/best_practices/_include/edge.yaml @@ -56,6 +56,9 @@ static_resources: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http use_remote_address: true + normalize_path: true + merge_slashes: true + path_with_escaped_slashes_action: UNESCAPE_AND_REDIRECT common_http_protocol_options: idle_timeout: 3600s # 1 hour headers_with_underscores_action: REJECT_REQUEST diff --git a/docs/root/configuration/best_practices/edge.rst b/docs/root/configuration/best_practices/edge.rst index d61c4684c71ab..cb72577e1a2a2 100644 --- a/docs/root/configuration/best_practices/edge.rst +++ b/docs/root/configuration/best_practices/edge.rst @@ -27,6 +27,18 @@ HTTP proxies should additionally configure: * :ref:`Listener connection limits. ` * :ref:`Global downstream connection limits `. +If Envoy is configured with RBAC filter or makes route selection based on URL path it is recommended to enable the following path normalization options to minimize probability of path confusion vulnerabilities. Path confusion vulnerabilities occur when parties participating in request use different path representations. + +* Enable :ref:`normalize_path setting `. +* Enable :ref:`merge_slashes setting `. + +Additionally the :ref:`path_with_escaped_slashes_action setting ` should be set according to following recommendations: + +* REJECT_REQUEST if dowstream clients are expected to use rfc3986 compliant normalized paths (i.e. gRPC clients). +* UNESCAPE_AND_REDIRECT if downstream client supports HTTP redirect (i.e. a browser). This option minimizes possibility of path confusion by forcing request to be re-issued with the same path across all parties: downstream client, Envoy and upstream server. Note that gRPC requests will still be rejected with the INTERNAL (13) error code, as gRPC clients do not support redirect. +* KEEP_UNCHANGED for servers that are not rfc3986 compliant and require encoded slashes. +* UNESCAPE_AND_FORWARD for servers that are known to treat escaped and unescaped slashes equivalently. Choosing this option may increase probablity of path confusion vulnerabilities if intermediaries perform path based access control. + The following is a YAML example of the above recommendation (taken from the :ref:`Google VRP ` edge server configuration): diff --git a/docs/root/configuration/http/http_conn_man/runtime.rst b/docs/root/configuration/http/http_conn_man/runtime.rst index 2c104c8065080..e19d3d89acf70 100644 --- a/docs/root/configuration/http/http_conn_man/runtime.rst +++ b/docs/root/configuration/http/http_conn_man/runtime.rst @@ -31,3 +31,22 @@ tracing.random_sampling % of requests that will be randomly traced. See :ref:`here ` for more information. This runtime control is specified in the range 0-10000 and defaults to 10000. Thus, trace sampling can be specified in 0.01% increments. + +.. _config_http_conn_man_runtime_path_with_escaped_slashes_action: + +http_connection_manager.path_with_escaped_slashes_action + Overrides Envoy's default action taken when the + :ref:`path_with_escaped_slashes_action `. + was not specified or set to the IMPLEMENTATION_SPECIFIC_DEFAULT value. Possible values: + + - 2 sets action to the REJECT_REQUEST. + - 3 sets action to the UNESCAPE_AND_REDIRECT. + - 4 sets action to the UNESCAPE_AND_FORWARD. + - all other values set the action to KEEP_UNCHANGED. + +.. _config_http_conn_man_runtime_path_with_escaped_slashes_action_enabled: + +http_connection_manager.path_with_escaped_slashes_action_enabled + % of requests that will be subject to the + :ref:`path_with_escaped_slashes_action `. + action. For all other requests the KEEP_UNCHANGED action will be applied. Defaults to 100. diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index 9c7d441b7fff2..a2495b6b698c2 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -48,6 +48,7 @@ statistics: downstream_rq_non_relative_path, Counter, Total requests with a non-relative HTTP path downstream_rq_too_large, Counter, Total requests resulting in a 413 due to buffering an overly large body downstream_rq_completed, Counter, Total requests that resulted in a response (e.g. does not include aborted requests) + downstream_rq_failed_path_normalization, Counter, Total requests redirected due to different original and normalized URL paths or when path normalization failed. This action is configured by setting the :ref:`path_with_escaped_slashes_action ` config option. downstream_rq_1xx, Counter, Total 1xx responses downstream_rq_2xx, Counter, Total 2xx responses downstream_rq_3xx, Counter, Total 3xx responses @@ -59,6 +60,7 @@ statistics: downstream_rq_max_duration_reached, Counter, Total requests closed due to max duration reached downstream_rq_timeout, Counter, Total requests closed due to a timeout on the request path downstream_rq_overload_close, Counter, Total requests closed due to Envoy overload + downstream_rq_redirected_with_normalized_path, Counter, Total requests redirected due to different original and normalized URL paths. This action is configured by setting the :ref:`path_with_escaped_slashes_action ` config option. rs_too_large, Counter, Total response errors due to buffering an overly large body Per user agent statistics diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 711dc25ab04a3..cff3be24280ff 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -19,6 +19,7 @@ Removed Config or Runtime New Features ------------ +* http: added the ability to :ref:`unescape slash sequences` in the path. Requests with unescaped slashes can be proxied, rejected or redirected to the new unescaped path. By default this feature is disabled. The default behavior can be overridden through :ref:`http_connection_manager.path_with_escaped_slashes_action` runtime variable. This action can be selectively enabled for a portion of requests by setting the :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime variable. Deprecated ---------- diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 844313e1ef972..56bb02d3315ae 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -35,7 +35,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 43] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -98,6 +98,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -530,6 +560,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. // diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index 6afb11505ceeb..2f42153cf081a 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -34,7 +34,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 43] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -97,6 +97,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -527,6 +557,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. // diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 026cb2b5171a9..a72f07462e21c 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -49,12 +49,14 @@ namespace Http { COUNTER(downstream_rq_4xx) \ COUNTER(downstream_rq_5xx) \ COUNTER(downstream_rq_completed) \ + COUNTER(downstream_rq_failed_path_normalization) \ COUNTER(downstream_rq_http1_total) \ COUNTER(downstream_rq_http2_total) \ COUNTER(downstream_rq_http3_total) \ COUNTER(downstream_rq_idle_timeout) \ COUNTER(downstream_rq_non_relative_path) \ COUNTER(downstream_rq_overload_close) \ + COUNTER(downstream_rq_redirected_with_normalized_path) \ COUNTER(downstream_rq_response_before_rq_complete) \ COUNTER(downstream_rq_rx_reset) \ COUNTER(downstream_rq_timeout) \ @@ -466,6 +468,14 @@ class ConnectionManagerConfig { * @return LocalReply configuration which supplies mapping for local reply generated by Envoy. */ virtual const LocalReply::LocalReply& localReply() const PURE; + + /** + * @return the action HttpConnectionManager should take when receiving client request + * with URI path containing %2F, %2f, %5c or %5C sequences. + */ + virtual envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const PURE; }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index d6f501985486e..9fea3af026ef4 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -977,14 +977,31 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he } // Path sanitization should happen before any path access other than the above sanity check. - if (!ConnectionManagerUtility::maybeNormalizePath(*request_headers_, - connection_manager_.config_)) { + const auto action = + ConnectionManagerUtility::maybeNormalizePath(*request_headers_, connection_manager_.config_); + // gRPC requests are rejected if Envoy is configured to redirect post-normalization. This is + // because gRPC clients do not support redirect. + if (action == ConnectionManagerUtility::NormalizePathAction::Reject || + (action == ConnectionManagerUtility::NormalizePathAction::Redirect && + Grpc::Common::hasGrpcContentType(*request_headers_))) { + connection_manager_.stats_.named_.downstream_rq_failed_path_normalization_.inc(); sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::BadRequest, "", nullptr, absl::nullopt, StreamInfo::ResponseCodeDetails::get().PathNormalizationFailed); return; + } else if (action == ConnectionManagerUtility::NormalizePathAction::Redirect) { + connection_manager_.stats_.named_.downstream_rq_redirected_with_normalized_path_.inc(); + sendLocalReply( + false, Code::TemporaryRedirect, "", + [new_path = request_headers_->Path()->value().getStringView()]( + Http::ResponseHeaderMap& response_headers) -> void { + response_headers.addReferenceKey(Http::Headers::get().Location, new_path); + }, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().PathNormalizationFailed); + return; } + ASSERT(action == ConnectionManagerUtility::NormalizePathAction::Continue); ConnectionManagerUtility::maybeNormalizeHost(*request_headers_, connection_manager_.config_, localPort()); diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 265079582e18f..4fa84c3edcad3 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -409,20 +409,46 @@ void ConnectionManagerUtility::mutateResponseHeaders(ResponseHeaderMap& response } } -bool ConnectionManagerUtility::maybeNormalizePath(RequestHeaderMap& request_headers, - const ConnectionManagerConfig& config) { +ConnectionManagerUtility::NormalizePathAction +ConnectionManagerUtility::maybeNormalizePath(RequestHeaderMap& request_headers, + const ConnectionManagerConfig& config) { if (!request_headers.Path()) { - return true; // It's as valid as it is going to get. + return NormalizePathAction::Continue; // It's as valid as it is going to get. } - bool is_valid_path = true; - if (config.shouldNormalizePath()) { - is_valid_path = PathUtil::canonicalPath(request_headers); + + NormalizePathAction final_action = NormalizePathAction::Continue; + const auto escaped_slashes_action = config.pathWithEscapedSlashesAction(); + ASSERT(escaped_slashes_action != envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT); + if (escaped_slashes_action != envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED) { + auto escaped_slashes_result = PathUtil::unescapeSlashes(request_headers); + if (escaped_slashes_result == PathUtil::UnescapeSlashesResult::FoundAndUnescaped) { + if (escaped_slashes_action == envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::REJECT_REQUEST) { + return NormalizePathAction::Reject; + } else if (escaped_slashes_action == + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT) { + final_action = NormalizePathAction::Redirect; + } else { + ASSERT(escaped_slashes_action == + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD); + } + } } + + if (config.shouldNormalizePath() && !PathUtil::canonicalPath(request_headers)) { + return NormalizePathAction::Reject; + } + // Merge slashes after path normalization to catch potential edge cases with percent encoding. - if (is_valid_path && config.shouldMergeSlashes()) { + if (config.shouldMergeSlashes()) { PathUtil::mergeSlashes(request_headers); } - return is_valid_path; + + return final_action; } void ConnectionManagerUtility::maybeNormalizeHost(RequestHeaderMap& request_headers, diff --git a/source/common/http/conn_manager_utility.h b/source/common/http/conn_manager_utility.h index 768971ffac2d3..22a3c3db3e7a7 100644 --- a/source/common/http/conn_manager_utility.h +++ b/source/common/http/conn_manager_utility.h @@ -66,12 +66,17 @@ class ConnectionManagerUtility { const RequestHeaderMap* request_headers, ConnectionManagerConfig& config, const std::string& via); + enum class NormalizePathAction { + Continue = 0, + Reject = 1, + Redirect = 2, + }; + // Sanitize the path in the header map if the path exists and it is forced by config. // Side affect: the string view of Path header is invalidated. - // Return false if error happens during the sanitization. - // Returns true if there is no path. - static bool maybeNormalizePath(RequestHeaderMap& request_headers, - const ConnectionManagerConfig& config); + // Returns the action that should taken based on the results of path normalization. + static NormalizePathAction maybeNormalizePath(RequestHeaderMap& request_headers, + const ConnectionManagerConfig& config); static void maybeNormalizeHost(RequestHeaderMap& request_headers, const ConnectionManagerConfig& config, uint32_t port); diff --git a/source/common/http/path_utility.cc b/source/common/http/path_utility.cc index f12790b411033..30e9d8bad7779 100644 --- a/source/common/http/path_utility.cc +++ b/source/common/http/path_utility.cc @@ -25,6 +25,16 @@ absl::optional canonicalizePath(absl::string_view original_path) { return absl::make_optional(std::move(canonical_path)); } } + +void unescapeInPath(std::string& path, absl::string_view escape_sequence, + absl::string_view substitution) { + std::vector split = absl::StrSplit(path, escape_sequence); + if (split.size() == 1) { + return; + } + path = absl::StrJoin(split, substitution); +} + } // namespace /* static */ @@ -71,6 +81,31 @@ void PathUtil::mergeSlashes(RequestHeaderMap& headers) { path_suffix, query)); } +PathUtil::UnescapeSlashesResult PathUtil::unescapeSlashes(RequestHeaderMap& headers) { + ASSERT(headers.Path()); + const auto original_path = headers.getPathValue(); + const auto original_length = original_path.length(); + // Only operate on path component in URL. + const absl::string_view::size_type query_start = original_path.find('?'); + const absl::string_view path = original_path.substr(0, query_start); + if (path.find('%') == absl::string_view::npos) { + return UnescapeSlashesResult::NotFound; + } + const absl::string_view query = absl::ClippedSubstr(original_path, query_start); + + // TODO(yanavlasov): optimize this by adding case insensitive matcher + std::string decoded_path{path}; + unescapeInPath(decoded_path, "%2F", "/"); + unescapeInPath(decoded_path, "%2f", "/"); + unescapeInPath(decoded_path, "%5C", "\\"); + unescapeInPath(decoded_path, "%5c", "\\"); + headers.setPath(absl::StrCat(decoded_path, query)); + // Path length will not match if there were unescaped %2f or %5c + return headers.getPathValue().length() != original_length + ? UnescapeSlashesResult::FoundAndUnescaped + : UnescapeSlashesResult::NotFound; +} + absl::string_view PathUtil::removeQueryAndFragment(const absl::string_view path) { absl::string_view ret = path; // Trim query parameters and/or fragment if present. diff --git a/source/common/http/path_utility.h b/source/common/http/path_utility.h index a6a99aaef78d7..1771ccaaa63fd 100644 --- a/source/common/http/path_utility.h +++ b/source/common/http/path_utility.h @@ -19,6 +19,17 @@ class PathUtil { // Merges two or more adjacent slashes in path part of URI into one. // Requires the Path header be present. static void mergeSlashes(RequestHeaderMap& headers); + + enum class UnescapeSlashesResult { + // No escaped slash sequences were found and URL path has not been modified. + NotFound = 0, + // Escaped slash sequences were found and URL path has been modified. + FoundAndUnescaped = 1, + }; + // Unescape %2F, %2f, %5C and %5c sequences. + // Requires the Path header be present. + // Returns the result of unescaping slashes. + static UnescapeSlashesResult unescapeSlashes(RequestHeaderMap& headers); // Removes the query and/or fragment string (if present) from the input path. // For example, this function returns "/data" for the input path "/data?param=value#fragment". static absl::string_view removeQueryAndFragment(const absl::string_view path); diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 0429e15561453..0e186ed978b02 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -88,6 +88,51 @@ class MissingConfigFilter : public Http::PassThroughDecoderFilter { } }; +envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + getPathWithEscapedSlashesActionRuntimeOverride(Server::Configuration::FactoryContext& context) { + // The default behavior is to leave escaped slashes unchanged. + uint64_t runtime_override = context.runtime().snapshot().getInteger( + "http_connection_manager.path_with_escaped_slashes_action", 0); + switch (runtime_override) { + default: + // Also includes runtime override values of 0 and 1 + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; + case 2: + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + REJECT_REQUEST; + case 3: + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + UNESCAPE_AND_REDIRECT; + case 4: + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + UNESCAPE_AND_FORWARD; + } +} + +envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + getPathWithEscapedSlashesAction(const envoy::extensions::filters::network:: + http_connection_manager::v3::HttpConnectionManager& config, + Server::Configuration::FactoryContext& context) { + envoy::type::v3::FractionalPercent default_fraction; + default_fraction.set_numerator(100); + default_fraction.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED); + if (context.runtime().snapshot().featureEnabled( + "http_connection_manager.path_with_escaped_slashes_action_enabled", default_fraction)) { + return config.path_with_escaped_slashes_action() == + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT + ? getPathWithEscapedSlashesActionRuntimeOverride(context) + : config.path_with_escaped_slashes_action(); + } + + // When action is disabled through runtime the behavior is to keep escaped slashes unchanged. + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; +} + } // namespace // Singleton registration via macro defined in envoy/singleton/manager.h @@ -251,7 +296,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( merge_slashes_(config.merge_slashes()), headers_with_underscores_action_( config.common_http_protocol_options().headers_with_underscores_action()), - local_reply_(LocalReply::Factory::create(config.local_reply_config(), context)) { + local_reply_(LocalReply::Factory::create(config.local_reply_config(), context)), + path_with_escaped_slashes_action_(getPathWithEscapedSlashesAction(config, context)) { // If idle_timeout_ was not configured in common_http_protocol_options, use value in deprecated // idle_timeout field. // TODO(asraa): Remove when idle_timeout is removed. diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index c3103efda53fe..344d3fdfd687c 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -177,6 +177,11 @@ class HttpConnectionManagerConfig : Logger::Loggable, } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return path_with_escaped_slashes_action_; + } private: enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; @@ -262,6 +267,8 @@ class HttpConnectionManagerConfig : Logger::Loggable, static const uint64_t RequestTimeoutMs = 0; // request header timeout is disabled by default static const uint64_t RequestHeaderTimeoutMs = 0; + const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction path_with_escaped_slashes_action_; }; /** diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index c766978c9638f..8692cae1fa5f8 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -180,6 +180,12 @@ class AdminImpl : public Admin, return envoy::config::core::v3::HttpProtocolOptions::ALLOW; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; + } Http::Code request(absl::string_view path_and_query, absl::string_view method, Http::ResponseHeaderMap& response_headers, std::string& body) override; void closeSocket(); diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 132307e4be15b..4a666ce2b8157 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -206,6 +206,12 @@ class FuzzConfig : public ConnectionManagerConfig { return envoy::config::core::v3::HttpProtocolOptions::ALLOW; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; + } const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager config_; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index ccd3836948063..7d301b59eb7a4 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -519,6 +519,102 @@ TEST_F(HttpConnectionManagerImplTest, RouteShouldUseSantizedPath) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +// Paths with escaped slashes rejected with 400 when configured. +TEST_F(HttpConnectionManagerImplTest, PathWithEscapedSlashesRejected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::REJECT_REQUEST; + testPathNormalization( + TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/abc%5c../"}, {":method", "GET"}}, + TestResponseHeaderMapImpl{{":status", "400"}, {"connection", "close"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_failed_path_normalization_.value()); +} + +// Paths with escaped slashes redirected when configured. +TEST_F(HttpConnectionManagerImplTest, PathWithEscapedSlashesRedirected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + testPathNormalization( + TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/abc%2f../"}, {":method", "GET"}}, + TestResponseHeaderMapImpl{{":status", "307"}, {"location", "/abc/../"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_redirected_with_normalized_path_.value()); +} + +// Paths with escaped slashes rejected with 400 instead of redirected for gRPC request. +TEST_F(HttpConnectionManagerImplTest, PathWithEscapedSlashesRejectedIfGRPC) { + // This test is slightly weird as it sends gRPC "request" over H/1 client of the + // HttpConnectionManagerImplTest. However it is sufficient to test the behavior of path + // normalization as it is determined by the content type only. + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + testPathNormalization(TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/abc%2fdef"}, + {":method", "GET"}, + {"content-type", "application/grpc"}}, + TestResponseHeaderMapImpl{{":status", "200"}, + {"connection", "close"}, + {"grpc-status", "13"}, + {"content-type", "application/grpc"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_failed_path_normalization_.value()); +} + +// Test that requests with escaped slashes are redirected when configured. Redirection +// occurs after Chromium URL normalization or merge slashes operations. +TEST_F(HttpConnectionManagerImplTest, EscapedSlashesRedirectedAfterOtherNormalizations) { + normalize_path_ = true; + merge_slashes_ = true; + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + // Both Chromium URL normalization and merge slashes should happen if request is redirected + // due to escaped slash sequences. + testPathNormalization(TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/abc%2f../%5cdef//"}, + {":method", "GET"}}, + TestResponseHeaderMapImpl{{":status", "307"}, {"location", "/def/"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_redirected_with_normalized_path_.value()); +} + +TEST_F(HttpConnectionManagerImplTest, AllNormalizationsWithEscapedSlashesForwarded) { + setup(false, ""); + // Enable path sanitizer + normalize_path_ = true; + merge_slashes_ = true; + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_FORWARD; + const std::string original_path = "/x/%2E%2e/z%2f%2Fabc%5C../def"; + const std::string normalized_path = "/z/def"; + + auto* filter = new MockStreamFilter(); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(StreamDecoderFilterSharedPtr{filter}); + })); + + EXPECT_CALL(*filter, decodeHeaders(_, true)) + .WillRepeatedly(Invoke([&](RequestHeaderMap& header_map, bool) -> FilterHeadersStatus { + EXPECT_EQ(normalized_path, header_map.getPathValue()); + return FilterHeadersStatus::StopIteration; + })); + + EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { + decoder_ = &conn_manager_->newStream(response_encoder_); + RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ + {":authority", "host"}, {":path", original_path}, {":method", "GET"}}}; + decoder_->decodeHeaders(std::move(headers), true); + return Http::okStatus(); + })); + + // Kick off the incoming data. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_CALL(*filter, onStreamComplete()); + EXPECT_CALL(*filter, onDestroy()); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); +} + TEST_F(HttpConnectionManagerImplTest, RouteOverride) { setup(false, ""); diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index 973e67bf59498..b74180c0b0999 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -258,5 +258,30 @@ void HttpConnectionManagerImplTest::doRemoteClose(bool deferred) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +void HttpConnectionManagerImplTest::testPathNormalization( + const RequestHeaderMap& request_headers, const ResponseHeaderMap& expected_response) { + InSequence s; + setup(false, ""); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { + decoder_ = &conn_manager_->newStream(response_encoder_); + RequestHeaderMapPtr headers{std::make_unique(request_headers)}; + decoder_->decodeHeaders(std::move(headers), true); + data.drain(4); + return Http::okStatus(); + })); + + EXPECT_CALL(response_encoder_, encodeHeaders(_, true)) + .WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void { + TestResponseHeaderMapImpl copy{headers}; + copy.remove(Envoy::Http::LowerCaseString{"date"}); + copy.remove(Envoy::Http::LowerCaseString{"server"}); + EXPECT_THAT(©, HeaderMapEqualIgnoreOrder(&expected_response)); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index ca36ece9ede64..6fb5496812189 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -61,6 +61,8 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan ResponseHeaderMap* sendResponseHeaders(ResponseHeaderMapPtr&& response_headers); void expectOnDestroy(bool deferred = true); void doRemoteClose(bool deferred = true); + void testPathNormalization(const RequestHeaderMap& request_headers, + const ResponseHeaderMap& expected_response); // Http::ConnectionManagerConfig const std::list& accessLogs() override { return access_logs_; } @@ -140,6 +142,11 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan return headers_with_underscores_action_; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return path_with_escaped_slashes_action_; + } Envoy::Event::SimulatedTimeSystem test_time_; NiceMock route_config_provider_; @@ -212,6 +219,10 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan std::vector decoder_filters_; std::vector encoder_filters_; std::shared_ptr log_handler_; + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction path_with_escaped_slashes_action_{ + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED}; }; } // namespace Http diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index d97ded1d529fe..be67be1f42167 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -100,6 +100,9 @@ class ConnectionManagerUtilityTest : public testing::Test { ON_CALL(config_, via()).WillByDefault(ReturnRef(via_)); ON_CALL(config_, requestIDExtension()).WillByDefault(Return(request_id_extension_)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED)); } struct MutateRequestRet { @@ -1376,7 +1379,8 @@ TEST_F(ConnectionManagerUtilityTest, SanitizeEmptyPath) { TestRequestHeaderMapImpl original_headers; TestRequestHeaderMapImpl header_map(original_headers); - EXPECT_TRUE(ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); EXPECT_EQ(original_headers, header_map); } @@ -1476,6 +1480,177 @@ TEST_F(ConnectionManagerUtilityTest, RemovePort) { EXPECT_EQ(header_map_none.getHostValue(), "host:9999"); } +// maybeNormalizePath() does not touch escaped slashes when configured to KEEP_UNCHANGED. +TEST_F(ConnectionManagerUtilityTest, KeepEscapedSlashesWhenConfigured) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2fabc%5Cqrt"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz%2fabc%5Cqrt"); +} + +// maybeNormalizePath() returns REJECT if %2F or %5C was detected and configured to REJECT. +TEST_F(ConnectionManagerUtilityTest, RejectIfEscapedSlashesPresentAndConfiguredToReject) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2F..//abc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Reject, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + + original_headers.setPath("/xyz%5c..//abc"); + header_map = original_headers; + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Reject, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); +} + +// maybeNormalizePath() returns CONTINUE if escaped slashes were NOT present and configured to +// REJECT. +TEST_F(ConnectionManagerUtilityTest, RejectIfEscapedSlashesNotPresentAndConfiguredToReject) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%EA/abc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz%EA/abc"); +} + +// maybeNormalizePath() returns REDIRECT if escaped slashes were detected and configured to +// REDIRECT. +TEST_F(ConnectionManagerUtilityTest, RedirectIfEscapedSlashesPresentAndConfiguredToRedirect) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2F../%5cabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Redirect, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz/../\\abc"); +} + +// maybeNormalizePath() returns CONTINUE if escaped slashes were NOT present and configured to +// REDIRECT. +TEST_F(ConnectionManagerUtilityTest, ContinueIfEscapedSlashesNotFoundAndConfiguredToRedirect) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%30..//abc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz%30..//abc"); +} + +// maybeNormalizePath() returns CONTINUE if escaped slashes were detected and configured to +// UNESCAPE_AND_FORWARD. +TEST_F(ConnectionManagerUtilityTest, ContinueIfEscapedSlashesPresentAndConfiguredToUnescape) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2F../%5Cabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz/../\\abc"); +} + +// maybeNormalizePath() performs both slash unescaping and Chromium URL normalization. +TEST_F(ConnectionManagerUtilityTest, UnescapeSlashesAndChromiumNormalization) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f../%5Cabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + // Chromium URL path normalization converts \ to / + EXPECT_EQ(header_map.getPathValue(), "//abc"); +} + +// maybeNormalizePath() rejects request when chromium normalization fails after unescaping slashes. +TEST_F(ConnectionManagerUtilityTest, UnescapeSlashesRedirectAndChromiumNormalizationFailure) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + // %00 is an invalid sequence in URL path and causes path normalization to fail. + original_headers.setPath("/xyz%2f../%5Cabc%00"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Reject, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); +} + +// maybeNormalizePath() performs both unescaping and merging slashes when configured. +TEST_F(ConnectionManagerUtilityTest, UnescapeAndMergeSlashes) { + ON_CALL(config_, shouldMergeSlashes()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f/..//abc%5C%5c"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Redirect, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + // Envoy does not merge back slashes + EXPECT_EQ(header_map.getPathValue(), "/xyz/../abc\\\\"); +} + +// maybeNormalizePath() performs all path transformations. +TEST_F(ConnectionManagerUtilityTest, AllNormalizations) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, shouldMergeSlashes()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f..%5c/%2Fabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/abc"); +} + +// maybeNormalizePath() redirects because of escaped slashes after all other transformations. +TEST_F(ConnectionManagerUtilityTest, RedirectAfterAllOtherNormalizations) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, shouldMergeSlashes()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f..%5c/%2Fabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Redirect, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/abc"); +} + // test preserve_external_request_id true does not reset the passed requestId if passed TEST_F(ConnectionManagerUtilityTest, PreserveExternalRequestId) { connection_.stream_info_.downstream_address_provider_->setRemoteAddress( diff --git a/test/common/http/path_utility_test.cc b/test/common/http/path_utility_test.cc index d7c6399341362..3934b1469ed5d 100644 --- a/test/common/http/path_utility_test.cc +++ b/test/common/http/path_utility_test.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -133,5 +134,59 @@ TEST_F(PathUtilityTest, RemoveQueryAndFragment) { EXPECT_EQ("/abc", PathUtil::removeQueryAndFragment("/abc?param=value#fragment")); } +TEST_F(PathUtilityTest, UnescapeSlashes) { + using UnescapeResult = std::tuple; + auto unescapeSlashes = [this](const std::string& path_value) { + auto& path_header = pathHeaderEntry(path_value); + auto result = PathUtil::unescapeSlashes(headers_); + auto sanitized_path_value = path_header.value().getStringView(); + return UnescapeResult(std::string(sanitized_path_value), result); + }; + EXPECT_EQ(UnescapeResult("", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("")); // empty + EXPECT_EQ(UnescapeResult("//", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2f%2F")); // case-insensitive + EXPECT_EQ(UnescapeResult("/a/b/c/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%2Fb%2fc/")); // between other characters + EXPECT_EQ(UnescapeResult("%2b", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("%2b")); // not %2f + EXPECT_EQ(UnescapeResult("/a/b/c", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("/a/b/c")); // not %2f + EXPECT_EQ(UnescapeResult("%2", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("%2")); // incomplete + EXPECT_EQ(UnescapeResult("%", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("%")); // incomplete + EXPECT_EQ(UnescapeResult("/abc%2", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("/abc%2")); // incomplete + EXPECT_EQ(UnescapeResult("foo%", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("foo%")); // incomplete + EXPECT_EQ(UnescapeResult("/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%2F")); // prefixed + EXPECT_EQ(UnescapeResult("/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2fa/")); // suffixed + EXPECT_EQ(UnescapeResult("%/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%%2fa/")); // double escape + EXPECT_EQ(UnescapeResult("%2/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2%2fa/")); // incomplete escape + + EXPECT_EQ(UnescapeResult("\\\\", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%5c%5C")); // case-insensitive + EXPECT_EQ(UnescapeResult("/a\\b\\c/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%5Cb%5cc/")); // between other characters + EXPECT_EQ(UnescapeResult("/a\\", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%5C")); // prefixed + EXPECT_EQ(UnescapeResult("\\a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%5ca/")); // suffixed + EXPECT_EQ(UnescapeResult("/x/%2E%2e/z//abc\\../def", + PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/x/%2E%2e/z%2f%2Fabc%5C../def")); + + EXPECT_EQ(UnescapeResult("/a\\b/c\\", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2fa%5Cb%2fc%5c")); // %5c and %2f together + EXPECT_EQ(UnescapeResult("/a\\b/c\\?%2fabcd%5C%%2f%", + PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2fa%5Cb%2fc%5c?%2fabcd%5C%%2f%")); // query is untouched +} + } // namespace Http } // namespace Envoy diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 5149c2e55c99e..bc0645c541ac2 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -31,6 +31,7 @@ using testing::_; using testing::An; +using testing::AnyNumber; using testing::Eq; using testing::NotNull; using testing::Pointee; @@ -835,8 +836,8 @@ TEST_F(HttpConnectionManagerConfigTest, ServerOverwrite) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -856,8 +857,8 @@ TEST_F(HttpConnectionManagerConfigTest, ServerAppendIfAbsent) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -877,8 +878,8 @@ TEST_F(HttpConnectionManagerConfigTest, ServerPassThrough) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -899,8 +900,8 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathDefault) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -922,6 +923,9 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathRuntime) { - name: envoy.filters.http.router )EOF"; + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled("http_connection_manager.normalize_path", An())) .WillOnce(Return(true)); @@ -943,6 +947,9 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathTrue) { - name: envoy.filters.http.router )EOF"; + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled("http_connection_manager.normalize_path", An())) .Times(0); @@ -964,6 +971,9 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathFalse) { - name: envoy.filters.http.router )EOF"; + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled("http_connection_manager.normalize_path", An())) .Times(0); @@ -2210,6 +2220,162 @@ TEST_F(FilterChainTest, InvalidConfig) { EnvoyException, "Error: multiple upgrade configs with the same name: 'websocket'"); } +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionDefault) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled(_, An())) + .WillOnce(Return(true)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .WillOnce(Return(0)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED, + config.pathWithEscapedSlashesAction()); +} + +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionDefaultOverriden) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + path_with_escaped_slashes_action: IMPLEMENTATION_SPECIFIC_DEFAULT + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled(_, An())) + .WillRepeatedly(Return(true)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .WillOnce(Return(3)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT, + config.pathWithEscapedSlashesAction()); + + // Check the UNESCAPE_AND_FORWARD override to mollify coverage check + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .WillOnce(Return(4)); + HttpConnectionManagerConfig config1(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD, + config1.pathWithEscapedSlashesAction()); +} + +// Verify that runtime override does not affect non default configuration value. +TEST_F(HttpConnectionManagerConfigTest, + PathWithEscapedSlashesActionRuntimeOverrideDoesNotChangeNonDefaultConfigValue) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + path_with_escaped_slashes_action: REJECT_REQUEST + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled(_, An())) + .WillOnce(Return(true)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + // When configuration value is not the IMPLEMENTATION_SPECIFIC_DEFAULT the runtime override should + // not even be considered. + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .Times(0); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST, + config.pathWithEscapedSlashesAction()); +} + +// Verify that disabling unescaping slashes results in the KEEP_UNCHANGED action when config is +// value is not set. +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionDefaultOverridenAndDisabled) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled("http_connection_manager.path_with_escaped_slashes_action_enabled", + An())) + .WillOnce(Return(false)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .Times(0); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED, + config.pathWithEscapedSlashesAction()); +} + +// Verify that disabling unescaping slashes results in the KEEP_UNCHANGED action when config is +// value is set. +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionSetAndDisabled) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + path_with_escaped_slashes_action: UNESCAPE_AND_REDIRECT + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled("http_connection_manager.path_with_escaped_slashes_action_enabled", + An())) + .WillOnce(Return(false)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .Times(0); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED, + config.pathWithEscapedSlashesAction()); +} + class HcmUtilityTest : public testing::Test { public: HcmUtilityTest() { diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index 9cdf7e0f72039..4a1c53f0eb467 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -329,6 +329,7 @@ class HeaderIntegrationTest } hcm.mutable_normalize_path()->set_value(normalize_path_); + hcm.set_path_with_escaped_slashes_action(path_with_escaped_slashes_action_); if (append) { // The config specifies append by default: no modifications needed. @@ -433,7 +434,7 @@ class HeaderIntegrationTest } template - void compareHeaders(Headers&& headers, ExpectedHeaders& expected_headers) { + void compareHeaders(Headers&& headers, const ExpectedHeaders& expected_headers) { headers.remove(Envoy::Http::LowerCaseString{"content-length"}); headers.remove(Envoy::Http::LowerCaseString{"date"}); if (!routerSuppressEnvoyHeaders()) { @@ -444,11 +445,15 @@ class HeaderIntegrationTest headers.remove(Envoy::Http::LowerCaseString{"x-request-id"}); headers.remove(Envoy::Http::LowerCaseString{"x-envoy-internal"}); - EXPECT_EQ(expected_headers, headers); + EXPECT_THAT(&headers, HeaderMapEqualIgnoreOrder(&expected_headers)); } bool use_eds_{false}; bool normalize_path_{false}; + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction path_with_escaped_slashes_action_{ + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED}; FakeHttpConnectionPtr eds_connection_; FakeStreamPtr eds_stream_; }; @@ -1085,6 +1090,166 @@ TEST_P(HeaderIntegrationTest, TestPathAndRouteOnNormalizedPath) { }); } +// Validates that Envoy by default does not modify escaped slashes. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesByDefaultUnchanghed) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT; + normalize_path_ = true; + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private/..%2Fpublic%5c"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }, + Http::TestRequestHeaderMapImpl{{":authority", "path-sanitization.com"}, + {":path", "/private/..%2Fpublic%5c"}, + {":method", "GET"}, + {"x-site", "private"}}, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + +// Validates that default action can be overridden through runtime. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesDefaultOverriden) { + // Override the default action to REJECT + config_helper_.addRuntimeOverride("http_connection_manager.path_with_escaped_slashes_action", + "2"); + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT; + initializeFilter(HeaderMode::Append, false); + registerTestServerPorts({"http"}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private%2f../public"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }); + response->waitForEndStream(); + Http::TestResponseHeaderMapImpl response_headers{response->headers()}; + compareHeaders(response_headers, Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"connection", "close"}, + {":status", "400"}, + }); +} + +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesRejected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::REJECT_REQUEST; + initializeFilter(HeaderMode::Append, false); + registerTestServerPorts({"http"}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private%2f../public"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }); + response->waitForEndStream(); + Http::TestResponseHeaderMapImpl response_headers{response->headers()}; + compareHeaders(response_headers, Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"connection", "close"}, + {":status", "400"}, + }); +} + +// Validates that Envoy does not modify escaped slashes when configured. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesUnmodified) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::KEEP_UNCHANGED; + normalize_path_ = true; + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private/..%2Fpublic%5c"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }, + Http::TestRequestHeaderMapImpl{{":authority", "path-sanitization.com"}, + {":path", "/private/..%2Fpublic%5c"}, + {":method", "GET"}, + {"x-site", "private"}}, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + +// Validates that Envoy forwards unescaped slashes when configured. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesAndNormalizationForwarded) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_FORWARD; + normalize_path_ = true; + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private/..%2Fpublic%5c%2e%2Fabc"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }, + Http::TestRequestHeaderMapImpl{{":authority", "path-sanitization.com"}, + {":path", "/public/abc"}, + {":method", "GET"}, + {"x-site", "public"}}, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesRedirected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + initializeFilter(HeaderMode::Append, false); + registerTestServerPorts({"http"}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private%2f../%2e%5Cpublic"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }); + response->waitForEndStream(); + Http::TestResponseHeaderMapImpl response_headers{response->headers()}; + compareHeaders(response_headers, Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"location", "/private/../%2e\\public"}, + {":status", "307"}, + }); +} + // Validates TE header is forwarded if it contains a supported value TEST_P(HeaderIntegrationTest, TestTeHeaderPassthrough) { initializeFilter(HeaderMode::Append, false); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 5c152222db558..5bddf3b6746f5 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -2191,6 +2191,25 @@ TEST_P(DownstreamProtocolIntegrationTest, Test100AndDisconnectLegacy) { } } +TEST_P(DownstreamProtocolIntegrationTest, HeaderNormalizationRejection) { + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + hcm.set_path_with_escaped_slashes_action( + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST); + }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + default_request_headers_.setPath("/test/long%2Furl"); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); +} + // For tests which focus on downstream-to-Envoy behavior, and don't need to be // run with both HTTP/1 and HTTP/2 upstreams. INSTANTIATE_TEST_SUITE_P(Protocols, DownstreamProtocolIntegrationTest, diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index d85065dc37bd1..6d97f6b041395 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -564,6 +564,9 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction, headersWithUnderscoresAction, (), (const)); MOCK_METHOD(const LocalReply::LocalReply&, localReply, (), (const)); + MOCK_METHOD(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::PathWithEscapedSlashesAction, + pathWithEscapedSlashesAction, (), (const)); std::unique_ptr internal_address_config_ = std::make_unique(); From 46bf743b97d0d3f01ff437b2f10cc0bd9cdfe6e4 Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Tue, 11 May 2021 09:49:56 -0400 Subject: [PATCH 25/28] v1.17.3 release Signed-off-by: Yan Avlasov --- VERSION | 2 +- docs/root/version_history/current.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index a4d7fa595723c..b9a05a6dc1d6b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.3-dev +1.17.3 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index cff3be24280ff..8cdc862a51f76 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,4 +1,4 @@ -1.17.3 (Pending) +1.17.3 (May 11, 2021) ======================= Incompatible Behavior Changes From 3d0a086b03fb75e68f832efadcecd26502fd54f5 Mon Sep 17 00:00:00 2001 From: Dmitri Dolguikh Date: Thu, 3 Jun 2021 10:37:47 -0700 Subject: [PATCH 26/28] Port of apple dns: fix interface issue with localhost lookup (#16369) (#16663) Commit Message: apple dns - fix interface issue with localhost lookup Additional Description: deleting ENVOY_BUG statement, as localhost dns resolution renders a valid non-zero interface index. Risk Level: low Testing: fixed previously existing test that did not have a run block. Signed-off-by: Jose Nino Signed-off-by: Dmitri Dolguikh Co-authored-by: Jose Ulises Nino Rivera --- source/common/network/apple_dns_impl.cc | 2 -- test/common/network/apple_dns_impl_test.cc | 32 +--------------------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc index f8994cb48e8c8..a21f260005c9f 100644 --- a/source/common/network/apple_dns_impl.cc +++ b/source/common/network/apple_dns_impl.cc @@ -300,8 +300,6 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( "error_code={}, hostname={}", dns_name_, flags, flags & kDNSServiceFlagsMoreComing ? "yes" : "no", flags & kDNSServiceFlagsAdd ? "yes" : "no", interface_index, error_code, hostname); - RELEASE_ASSERT(interface_index == 0, - fmt::format("unexpected interface_index={}", interface_index)); if (!pending_cb_) { pending_cb_ = {ResolutionStatus::Success, {}}; diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc index 9d9fbb534d127..5cc1f61059a9b 100644 --- a/test/common/network/apple_dns_impl_test.cc +++ b/test/common/network/apple_dns_impl_test.cc @@ -139,6 +139,7 @@ TEST_F(AppleDnsImplTest, DestructPending) { TEST_F(AppleDnsImplTest, LocalLookup) { EXPECT_NE(nullptr, resolveWithExpectations("localhost", DnsLookupFamily::Auto, DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); } TEST_F(AppleDnsImplTest, DnsIpAddressVersion) { @@ -400,37 +401,6 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { dns_callback_executed.WaitForNotification(); } -TEST_F(AppleDnsImplFakeApiTest, IncorrectInterfaceIndexReturned) { - createResolver(); - - const std::string hostname = "foo.com"; - sockaddr_in addr4; - addr4.sin_family = AF_INET; - EXPECT_EQ(1, inet_pton(AF_INET, "1.2.3.4", &addr4.sin_addr)); - addr4.sin_port = htons(6502); - - Network::Address::Ipv4Instance address(&addr4); - - EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(dns_service_, - dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, - kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, - StrEq(hostname.c_str()), _, _)) - .WillOnce(DoAll( - // Have the API call synchronously call the provided callback. Notice the incorrect - // interface_index "2". This will cause an assertion failure. - WithArgs<5, 6>(Invoke([&](DNSServiceGetAddrInfoReply callback, void* context) -> void { - EXPECT_DEATH(callback(nullptr, kDNSServiceFlagsAdd, 2, kDNSServiceErr_NoError, - hostname.c_str(), address.sockAddr(), 30, context), - "unexpected interface_index=2"); - })), - Return(kDNSServiceErr_NoError))); - - resolver_->resolve( - hostname, Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list &&) -> void { FAIL(); }); -} - TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithError) { createResolver(); From 35f45b6f3c66a6387812a4adaa5e05c971e68223 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 25 Jun 2021 20:17:48 +0000 Subject: [PATCH 27/28] Kick off release v1.17.4 (#17139) Signed-off-by: Yan Avlasov --- VERSION | 2 +- docs/root/version_history/current.rst | 3 +-- docs/root/version_history/v1.17.3.rst | 6 ++++++ docs/root/version_history/version_history.rst | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 docs/root/version_history/v1.17.3.rst diff --git a/VERSION b/VERSION index b9a05a6dc1d6b..cbb0156c82c66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.3 +1.17.4-dev diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 8cdc862a51f76..daa2c743dada5 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,4 +1,4 @@ -1.17.3 (May 11, 2021) +1.17.4 (Pending) ======================= Incompatible Behavior Changes @@ -19,7 +19,6 @@ Removed Config or Runtime New Features ------------ -* http: added the ability to :ref:`unescape slash sequences` in the path. Requests with unescaped slashes can be proxied, rejected or redirected to the new unescaped path. By default this feature is disabled. The default behavior can be overridden through :ref:`http_connection_manager.path_with_escaped_slashes_action` runtime variable. This action can be selectively enabled for a portion of requests by setting the :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime variable. Deprecated ---------- diff --git a/docs/root/version_history/v1.17.3.rst b/docs/root/version_history/v1.17.3.rst new file mode 100644 index 0000000000000..ed25bbc9b4a45 --- /dev/null +++ b/docs/root/version_history/v1.17.3.rst @@ -0,0 +1,6 @@ +1.17.3 (May 11, 2021) +======================= + +New Features +------------ +* http: added the ability to :ref:`unescape slash sequences` in the path. Requests with unescaped slashes can be proxied, rejected or redirected to the new unescaped path. By default this feature is disabled. The default behavior can be overridden through :ref:`http_connection_manager.path_with_escaped_slashes_action` runtime variable. This action can be selectively enabled for a portion of requests by setting the :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime variable. diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 74b1f422229d7..2f3e19e74377e 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.17.3 v1.17.2 v1.17.1 v1.17.0 From 93dc4d6d9e86eb90862dd46799ec1fa7ef7fb06b Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 22 Jun 2021 14:56:44 +0100 Subject: [PATCH 28/28] ci: Only publish the required docker image Signed-off-by: Ryan Northey --- ci/docker_ci.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ci/docker_ci.sh b/ci/docker_ci.sh index ab1283745037f..de7d702762cea 100755 --- a/ci/docker_ci.sh +++ b/ci/docker_ci.sh @@ -78,7 +78,6 @@ build_images() { else IMAGE_TAG="${BUILD_TAG}-${ARCH/linux\//}" fi - IMAGES_TO_SAVE+=("${IMAGE_TAG}") # docker buildx load cannot have multiple platform, load individually if ! is_windows; then @@ -138,13 +137,25 @@ fi # Test the docker build in all cases, but use a local tag that we will overwrite before push in the # cases where we do push. for BUILD_TYPE in "${BUILD_TYPES[@]}"; do - build_images "${BUILD_TYPE}" "${DOCKER_IMAGE_PREFIX}${BUILD_TYPE}${IMAGE_POSTFIX}:${IMAGE_NAME}" + image_tag="${DOCKER_IMAGE_PREFIX}${BUILD_TYPE}${IMAGE_POSTFIX}:${IMAGE_NAME}" + build_images "${BUILD_TYPE}" "$image_tag" + + if ! is_windows; then + if [[ "$BUILD_TYPE" == "" || "$BUILD_TYPE" == "-alpine" ]]; then + # verify_examples expects the base and alpine images, and for them to be named `-dev` + dev_image="envoyproxy/envoy${BUILD_TYPE}-dev:latest" + docker tag "$image_tag" "$dev_image" + IMAGES_TO_SAVE+=("$dev_image") + fi + fi done mkdir -p "${ENVOY_DOCKER_IMAGE_DIRECTORY}" -ENVOY_DOCKER_TAR="${ENVOY_DOCKER_IMAGE_DIRECTORY}/envoy-docker-images.tar.xz" -echo "Saving built images to ${ENVOY_DOCKER_TAR}." -docker save "${IMAGES_TO_SAVE[@]}" | xz -T0 -2 >"${ENVOY_DOCKER_TAR}" +if [[ ${#IMAGES_TO_SAVE[@]} -ne 0 ]]; then + ENVOY_DOCKER_TAR="${ENVOY_DOCKER_IMAGE_DIRECTORY}/envoy-docker-images.tar.xz" + echo "Saving built images to ${ENVOY_DOCKER_TAR}: ${IMAGES_TO_SAVE[*]}" + docker save "${IMAGES_TO_SAVE[@]}" | xz -T0 -2 >"${ENVOY_DOCKER_TAR}" +fi # Only push images for main builds, release branch builds, and tag builds. if [[ "${AZP_BRANCH}" != "${MAIN_BRANCH}" ]] &&