Skip to content

Commit

Permalink
http: add Kill Request HTTP filter (#14170)
Browse files Browse the repository at this point in the history
Add a KillRequest HTTP filter which can crash Envoy when receiving a Kill request. It will be used to fault inject kill request to Envoy and measure the blast radius.

Risk Level: Low, new feature.
Testing: Unit/integration tests.
Docs Changes: Added
Release Notes: Added
Issue: #13978

Signed-off-by: Qin Qin <qqin@google.com>
  • Loading branch information
qqustc authored Dec 1, 2020
1 parent 468d7c9 commit 237b29d
Show file tree
Hide file tree
Showing 31 changed files with 765 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,5 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp
# HTTP Local Rate Limit
/*/extensions/filters/http/local_ratelimit @rgs1 @mattklein123
/*/extensions/filters/common/local_ratelimit @mattklein123 @rgs1
# HTTP Kill Request
/*/extensions/filters/http/kill_request @qqustc @htuch
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ proto_library(
"//envoy/extensions/filters/http/health_check/v3:pkg",
"//envoy/extensions/filters/http/ip_tagging/v3:pkg",
"//envoy/extensions/filters/http/jwt_authn/v3:pkg",
"//envoy/extensions/filters/http/kill_request/v3:pkg",
"//envoy/extensions/filters/http/local_ratelimit/v3:pkg",
"//envoy/extensions/filters/http/lua/v3:pkg",
"//envoy/extensions/filters/http/oauth2/v3alpha:pkg",
Expand Down
12 changes: 12 additions & 0 deletions api/envoy/extensions/filters/http/kill_request/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

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

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/type/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";

package envoy.extensions.filters.http.kill_request.v3;

import "envoy/type/v3/percent.proto";

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.http.kill_request.v3";
option java_outer_classname = "KillRequestProto";
option java_multiple_files = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Kill Request]
// Kill Request :ref:`configuration overview <config_http_filters_kill_request>`.
// [#extension: envoy.filters.http.kill_request]

// Configuration for KillRequest filter.
message KillRequest {
// The probability that a Kill request will be triggered.
type.v3.FractionalPercent probability = 1;
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ proto_library(
"//envoy/extensions/filters/http/health_check/v3:pkg",
"//envoy/extensions/filters/http/ip_tagging/v3:pkg",
"//envoy/extensions/filters/http/jwt_authn/v3:pkg",
"//envoy/extensions/filters/http/kill_request/v3:pkg",
"//envoy/extensions/filters/http/local_ratelimit/v3:pkg",
"//envoy/extensions/filters/http/lua/v3:pkg",
"//envoy/extensions/filters/http/oauth2/v3alpha:pkg",
Expand Down
7 changes: 6 additions & 1 deletion docs/generate_extension_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ def GetExtensionMetadata(target):
if __name__ == '__main__':
output_path = sys.argv[1]
extension_db = {}
for extension, target in extensions_build_config.EXTENSIONS.items():
# Include all extensions from both EXTENSIONS and
# DISABLED_BY_DEFAULT_EXTENSIONS in source/extensions/extensions_build_config.bzl
all_extensions = {}
all_extensions.update(extensions_build_config.EXTENSIONS)
all_extensions.update(extensions_build_config.DISABLED_BY_DEFAULT_EXTENSIONS)
for extension, target in all_extensions.items():
extension_db[extension] = GetExtensionMetadata(target)
# The TLS and generic upstream extensions are hard-coded into the build, so
# not in source/extensions/extensions_build_config.bzl
Expand Down
1 change: 1 addition & 0 deletions docs/root/configuration/http/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ HTTP filters
header_to_metadata_filter
ip_tagging_filter
jwt_authn_filter
kill_request_filter
local_rate_limit_filter
lua_filter
oauth2_filter
Expand Down
39 changes: 39 additions & 0 deletions docs/root/configuration/http/http_filters/kill_request_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.. _config_http_filters_kill_request:

Kill Request
===============

The KillRequest filter can be used to crash Envoy when receiving a Kill request.
By default, KillRequest filter is not built into Envoy binary since it is included in *DISABLED_BY_DEFAULT_EXTENSIONS* in *extensions_build_config.bzl*. If you want to use this extension, please move it from *DISABLED_BY_DEFAULT_EXTENSIONS* to *EXTENSIONS*.

Configuration
-------------

* This filter should be configured with the name *envoy.filters.http.kill_request*.

.. _config_http_filters_kill_request_http_header:

Enable Kill Request via HTTP header
--------------------------------------------

The KillRequest filter requires the following header in the request:

x-envoy-kill-request
whether the request is a Kill request.
The header value must be one of (case-insensitive) ["true", "t", "yes", "y", "1"]
in order for the request to be a Kill request.

.. note::

If the headers appear multiple times only the first value is used.

The following is an example configuration:

.. code-block:: yaml
name: envoy.filters.http.kill_request
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.kill_request.v3.KillRequest
probability:
numerator: 100
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ New Features
* http: added HCM :ref:`timeout config field <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.request_headers_timeout>` 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.
* jwt_authn: added support for :ref:`per-route config <envoy_v3_api_msg_extensions.filters.http.jwt_authn.v3.PerRouteConfig>`.
* kill_request: added new :ref:`HTTP kill request filter <config_http_filters_kill_request>`.
* listener: added an optional :ref:`default filter chain <envoy_v3_api_field_config.listener.v3.Listener.default_filter_chain>`. If this field is supplied, and none of the :ref:`filter_chains <envoy_v3_api_field_config.listener.v3.Listener.filter_chains>` matches, this default filter chain is used to serve the connection.
* 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() <config_http_filters_lua_stream_info_wrapper>`.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions source/extensions/all_extensions.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@envoy_build_config//:extensions_build_config.bzl", "EXTENSIONS")
load("@envoy_build_config//:extensions_build_config.bzl", "DISABLED_BY_DEFAULT_EXTENSIONS", "EXTENSIONS")

GLOBAL_DENYLIST = DISABLED_BY_DEFAULT_EXTENSIONS.keys()

# These extensions are registered using the extension system but are required for the core Envoy build.
# The map may be overridden by extensions specified in envoy_build_config.
Expand All @@ -12,6 +14,8 @@ _required_extensions = {
def envoy_all_extensions(denylist = []):
all_extensions = dicts.add(_required_extensions, EXTENSIONS)

denylist = denylist + GLOBAL_DENYLIST

# These extensions can be removed on a site specific basis.
return [v for k, v in all_extensions.items() if not k in denylist]

Expand All @@ -37,7 +41,7 @@ _http_filter_prefix = "envoy.filters.http"
def envoy_all_http_filters():
all_extensions = dicts.add(_required_extensions, EXTENSIONS)

return [v for k, v in all_extensions.items() if k.startswith(_http_filter_prefix)]
return [v for k, v in all_extensions.items() if k.startswith(_http_filter_prefix) and k not in GLOBAL_DENYLIST]

# All network-layer filters are extensions with names that have the following prefix.
_network_filter_prefix = "envoy.filters.network"
Expand All @@ -49,4 +53,4 @@ _thrift_filter_prefix = "envoy.filters.thrift"
def envoy_all_network_filters():
all_extensions = dicts.add(_required_extensions, EXTENSIONS)

return [v for k, v in all_extensions.items() if k.startswith(_network_filter_prefix) or k.startswith(_thrift_filter_prefix)]
return [v for k, v in all_extensions.items() if (k.startswith(_network_filter_prefix) or k.startswith(_thrift_filter_prefix)) and k not in GLOBAL_DENYLIST]
5 changes: 5 additions & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ EXTENSIONS = {
"envoy.wasm.runtime.wasmtime": "//source/extensions/wasm_runtime/wasmtime:config",
}

# These filters will not be built into Envoy by default. To build Envoy with any of these filter, please move it to EXTENSIONS.
DISABLED_BY_DEFAULT_EXTENSIONS = {
"envoy.filters.http.kill_request": "//source/extensions/filters/http/kill_request:kill_request_config",
}

# These can be changed to ["//visibility:public"], for downstream builds which
# need to directly reference Envoy extensions.
EXTENSION_CONFIG_VISIBILITY = ["//:extension_config"]
Expand Down
40 changes: 40 additions & 0 deletions source/extensions/filters/http/kill_request/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_library(
name = "kill_request_filter_lib",
srcs = ["kill_request_filter.cc"],
hdrs = ["kill_request_filter.h"],
deps = [
"//include/envoy/common:random_generator_interface",
"//include/envoy/http:filter_interface",
"//include/envoy/http:header_map_interface",
"//source/common/http:header_map_lib",
"//source/common/http:header_utility_lib",
"//source/common/http:headers_lib",
"//source/common/protobuf:utility_lib",
"@envoy_api//envoy/extensions/filters/http/kill_request/v3:pkg_cc_proto",
],
)

envoy_cc_extension(
name = "kill_request_config",
srcs = ["kill_request_config.cc"],
hdrs = ["kill_request_config.h"],
security_posture = "robust_to_untrusted_downstream",
deps = [
"//include/envoy/registry",
"//source/extensions/filters/http:well_known_names",
"//source/extensions/filters/http/common:factory_base_lib",
"//source/extensions/filters/http/kill_request:kill_request_filter_lib",
"@envoy_api//envoy/extensions/filters/http/kill_request/v3:pkg_cc_proto",
],
)
31 changes: 31 additions & 0 deletions source/extensions/filters/http/kill_request/kill_request_config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "extensions/filters/http/kill_request/kill_request_config.h"

#include "envoy/extensions/filters/http/kill_request/v3/kill_request.pb.h"
#include "envoy/extensions/filters/http/kill_request/v3/kill_request.pb.validate.h"
#include "envoy/registry/registry.h"

#include "extensions/filters/http/kill_request/kill_request_filter.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace KillRequest {

Http::FilterFactoryCb KillRequestFilterFactory::createFilterFactoryFromProtoTyped(
const envoy::extensions::filters::http::kill_request::v3::KillRequest& proto_config,
const std::string&, Server::Configuration::FactoryContext& context) {
return [proto_config, &context](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(
std::make_shared<KillRequestFilter>(proto_config, context.api().randomGenerator()));
};
}

/**
* Static registration for the KillRequest filter. @see RegisterFactory.
*/
REGISTER_FACTORY(KillRequestFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory);

} // namespace KillRequest
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
31 changes: 31 additions & 0 deletions source/extensions/filters/http/kill_request/kill_request_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include "envoy/extensions/filters/http/kill_request/v3/kill_request.pb.h"
#include "envoy/extensions/filters/http/kill_request/v3/kill_request.pb.validate.h"

#include "extensions/filters/http/common/factory_base.h"
#include "extensions/filters/http/well_known_names.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace KillRequest {

/**
* Config registration for KillRequestFilter. @see NamedHttpFilterConfigFactory.
*/
class KillRequestFilterFactory
: public Common::FactoryBase<envoy::extensions::filters::http::kill_request::v3::KillRequest> {
public:
KillRequestFilterFactory() : FactoryBase(HttpFilterNames::get().KillRequest) {}

private:
Http::FilterFactoryCb createFilterFactoryFromProtoTyped(
const envoy::extensions::filters::http::kill_request::v3::KillRequest& proto_config,
const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override;
};

} // namespace KillRequest
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
38 changes: 38 additions & 0 deletions source/extensions/filters/http/kill_request/kill_request_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "extensions/filters/http/kill_request/kill_request_filter.h"

#include <csignal>

#include "common/protobuf/utility.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace KillRequest {

bool KillRequestFilter::isKillRequestEnabled() {
return ProtobufPercentHelper::evaluateFractionalPercent(kill_request_.probability(),
random_generator_.random());
}

Http::FilterHeadersStatus KillRequestFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) {
const auto kill_request_header = headers.get(KillRequestHeaders::get().KillRequest);
bool is_kill_request = false;
// This is an implicitly untrusted header, so per the API documentation only
// the first value is used.
if (kill_request_header.empty() ||
!absl::SimpleAtob(kill_request_header[0]->value().getStringView(), &is_kill_request)) {
return Http::FilterHeadersStatus::Continue;
}

if (is_kill_request && isKillRequestEnabled()) {
// Crash Envoy.
raise(SIGABRT);
}

return Http::FilterHeadersStatus::Continue;
}

} // namespace KillRequest
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Loading

0 comments on commit 237b29d

Please sign in to comment.