Skip to content
7 changes: 7 additions & 0 deletions docs/configuration/http_conn_man/headers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ the header value to *true*.

This is a convenience to avoid having to parse and understand XFF.

.. _config_http_conn_man_headers_x-envoy-ip-tags:

x-envoy-ip-tags
---------------

This header is populated by the :ref:`Ip Tagging Filter<config_http_filters_ip_tagging>`. Behavior is under development.

.. _config_http_conn_man_headers_x-forwarded-for:

x-forwarded-for
Expand Down
1 change: 1 addition & 0 deletions docs/configuration/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ HTTP filters
grpc_http1_bridge_filter
grpc_web_filter
health_check_filter
ip_tagging_filter
rate_limit_filter
router_filter
48 changes: 48 additions & 0 deletions docs/configuration/http_filters/ip_tagging_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.. _config_http_filters_ip_tagging:

Ip tagging filter
====================

This is an HTTP filter which enables Envoy to tag requests with extra information such as location, cloud source, and any
extra data. This is useful to prevent against DDoS.

**Note**: this filter is under active development, and currently does not perform any tagging on requests. In other
words, installing this filter is a no-op in the filter chain.

.. code-block:: json

{
"type": "decoder",
"name": "ip_tagging",
"config": {
"request_type": "...",
"ip_tags": []
}
}

request_type
*(optional, string)* The type of requests the filter should apply to. The supported
types are *internal*, *external* or *both*. A request is considered internal if
:ref:`x-envoy-internal<config_http_conn_man_headers_x-envoy-internal>` is set to true. If
:ref:`x-envoy-internal<config_http_conn_man_headers_x-envoy-internal>` is not set or false, a
request is considered external. The filter defaults to *both*, and it will apply to all request
types.

ip_tags:
*(optional, array)* Specifies the list of ip tags to set for a request.

Ip tags
-------
.. code-block:: json

{
"ip_tag_name": "...",
"ip_list": []
}

ip_tag_name:
*(required, string)* Specifies the ip tag name to apply.

ip_list:
*(required, list of strings)* A list of IP address and subnet masks that will be tagged with the ``ip_tag_name``. Both
IPv4 and IPv6 CIDR addresses are allowed here.
13 changes: 13 additions & 0 deletions source/common/http/filter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "ip_tagging_filter_lib",
srcs = ["ip_tagging_filter.cc"],
hdrs = ["ip_tagging_filter.h"],
deps = [
"//include/envoy/http:filter_interface",
"//include/envoy/json:json_object_interface",
"//source/common/common:assert_lib",
"//source/common/json:config_schemas_lib",
"//source/common/json:json_validator_lib",
],
)

envoy_cc_library(
name = "ratelimit_lib",
srcs = ["ratelimit.cc"],
Expand Down
29 changes: 29 additions & 0 deletions source/common/http/filter/ip_tagging_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "common/http/filter/ip_tagging_filter.h"

namespace Envoy {
namespace Http {

IpTaggingFilter::IpTaggingFilter(IpTaggingFilterConfigSharedPtr config) : config_(config) {}

IpTaggingFilter::~IpTaggingFilter() {}

void IpTaggingFilter::onDestroy() {}

FilterHeadersStatus IpTaggingFilter::decodeHeaders(HeaderMap&, bool) {
return FilterHeadersStatus::Continue;
}

FilterDataStatus IpTaggingFilter::decodeData(Buffer::Instance&, bool) {
return FilterDataStatus::Continue;
}

FilterTrailersStatus IpTaggingFilter::decodeTrailers(HeaderMap&) {
return FilterTrailersStatus::Continue;
}

void IpTaggingFilter::setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) {
callbacks_ = &callbacks;
}

} // Http
} // Envoy
75 changes: 75 additions & 0 deletions source/common/http/filter/ip_tagging_filter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#pragma once

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "envoy/http/filter.h"
#include "envoy/json/json_object.h"

#include "common/common/assert.h"
#include "common/json/config_schemas.h"
#include "common/json/json_validator.h"

namespace Envoy {
namespace Http {

/**
* Type of requests the filter should apply to.
*/
enum class FilterRequestType { Internal, External, Both };

/**
* Configuration for the ip tagging filter.
*/
class IpTaggingFilterConfig : Json::Validator {
public:
IpTaggingFilterConfig(const Json::Object& json_config)
: Json::Validator(json_config, Json::Schema::IP_TAGGING_HTTP_FILTER_SCHEMA),
request_type_(stringToType(json_config.getString("request_type", "both"))) {}

FilterRequestType requestType() const { return request_type_; }

private:
static FilterRequestType stringToType(const std::string& request_type) {
if (request_type == "internal") {
return FilterRequestType::Internal;
} else if (request_type == "external") {
return FilterRequestType::External;
} else {
ASSERT(request_type == "both");
return FilterRequestType::Both;
}
}

const FilterRequestType request_type_;
};

typedef std::shared_ptr<IpTaggingFilterConfig> IpTaggingFilterConfigSharedPtr;

/**
* A filter that tags requests via the x-envoy-ip-tags header based on the request's trusted XFF
* address.
*/
class IpTaggingFilter : public StreamDecoderFilter {
public:
IpTaggingFilter(IpTaggingFilterConfigSharedPtr config);
~IpTaggingFilter();

// Http::StreamFilterBase
void onDestroy() override;

// Http::StreamDecoderFilter
FilterHeadersStatus decodeHeaders(HeaderMap& headers, bool end_stream) override;
FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override;
FilterTrailersStatus decodeTrailers(HeaderMap& trailers) override;
void setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) override;

private:
IpTaggingFilterConfigSharedPtr config_;
StreamDecoderFilterCallbacks* callbacks_{};
};

} // Http
} // Envoy
33 changes: 33 additions & 0 deletions source/common/json/config_schemas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,39 @@ const std::string Json::Schema::FAULT_HTTP_FILTER_SCHEMA(R"EOF(
}
)EOF");

const std::string Json::Schema::IP_TAGGING_HTTP_FILTER_SCHEMA(R"EOF(
{
"$schema": "http://json-schema.org/schema#",
"type" : "object",
"properties" : {
"request_type" : {
"type" : "string",
"enum" : ["internal", "external", "both"]
},
"ip_tags" : {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ip_tags is supposed to be an array of ip_tag_name and ip_lists.

"type" : "array",
"minItems" : 1,
"uniqueItems" : true,
"items" : {
"type" : "object",
"properties" : {
"ip_tag_name" : { "type" : "string" },
"ip_list" : {
"type" : "array",
"minItems" : 1,
"uniqueItems" : true,
"items" : { "type" : "string" }
}
},
"required" : ["ip_tag_name", "ip_list"],
"additionalProperties" : false
}
}
},
"additionalProperties" : false
}
)EOF");

const std::string Json::Schema::HEALTH_CHECK_HTTP_FILTER_SCHEMA(R"EOF(
{
"$schema": "http://json-schema.org/schema#",
Expand Down
1 change: 1 addition & 0 deletions source/common/json/config_schemas.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Schema {
static const std::string BUFFER_HTTP_FILTER_SCHEMA;
static const std::string FAULT_HTTP_FILTER_SCHEMA;
static const std::string HEALTH_CHECK_HTTP_FILTER_SCHEMA;
static const std::string IP_TAGGING_HTTP_FILTER_SCHEMA;
static const std::string RATE_LIMIT_HTTP_FILTER_SCHEMA;
static const std::string ROUTER_HTTP_FILTER_SCHEMA;

Expand Down
12 changes: 12 additions & 0 deletions source/server/config/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "ip_tagging_lib",
srcs = ["ip_tagging.cc"],
hdrs = ["ip_tagging.h"],
deps = [
"//include/envoy/server:instance_interface",
"//source/common/http/filter:ip_tagging_filter_lib",
"//source/common/json:config_schemas_lib",
"//source/server/config/network:http_connection_manager_lib",
],
)

envoy_cc_library(
name = "lightstep_lib",
srcs = ["lightstep_http_tracer.cc"],
Expand Down
37 changes: 37 additions & 0 deletions source/server/config/http/ip_tagging.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "server/config/http/ip_tagging.h"

#include <string>

#include "common/http/filter/ip_tagging_filter.h"
#include "common/json/config_schemas.h"

namespace Envoy {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

namespace Server {
namespace Configuration {

HttpFilterFactoryCb IpTaggingFilterConfig::createFilterFactory(HttpFilterType type,
const Json::Object& json_config,
const std::string&,
Server::Instance&) {
if (type != HttpFilterType::Decoder) {
throw EnvoyException(
fmt::format("{} ip tagging filter must be configured as a decoder filter.", name()));
}

Http::IpTaggingFilterConfigSharedPtr config(new Http::IpTaggingFilterConfig(json_config));
return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamDecoderFilter(
Http::StreamDecoderFilterSharedPtr{new Http::IpTaggingFilter(config)});
};
}

std::string IpTaggingFilterConfig::name() { return "ip_tagging"; }

/**
* Static registration for the ip tagging filter. @see RegisterNamedHttpFilterConfigFactory.
*/
static RegisterNamedHttpFilterConfigFactory<IpTaggingFilterConfig> register_;

} // Configuration
} // Server
} // Envoy
27 changes: 27 additions & 0 deletions source/server/config/http/ip_tagging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <string>

#include "envoy/server/instance.h"

#include "server/config/network/http_connection_manager.h"

namespace Envoy {
namespace Server {
namespace Configuration {

/**
* Config registration for the router filter. @see NamedHttpFilterConfigFactory.
*/
class IpTaggingFilterConfig : public NamedHttpFilterConfigFactory {
public:
HttpFilterFactoryCb createFilterFactory(HttpFilterType type, const Json::Object& json_config,
const std::string& stat_prefix,
Server::Instance& server) override;

std::string name() override;
};

} // Configuration
} // Server
} // Envoy
1 change: 1 addition & 0 deletions test/server/config/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ envoy_cc_test(
"//source/server/config/http:fault_lib",
"//source/server/config/http:grpc_http1_bridge_lib",
"//source/server/config/http:grpc_web_lib",
"//source/server/config/http:ip_tagging_lib",
"//source/server/config/http:lightstep_lib",
"//source/server/config/http:ratelimit_lib",
"//source/server/config/http:router_lib",
Expand Down
41 changes: 41 additions & 0 deletions test/server/config/http/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "server/config/http/fault.h"
#include "server/config/http/grpc_http1_bridge.h"
#include "server/config/http/grpc_web.h"
#include "server/config/http/ip_tagging.h"
#include "server/config/http/lightstep_http_tracer.h"
#include "server/config/http/ratelimit.h"
#include "server/config/http/router.h"
Expand Down Expand Up @@ -193,6 +194,46 @@ TEST(HttpFilterConfigTest, BadRouterFilterConfig) {
Json::Exception);
}

TEST(HttpFilterConfigTest, IpTaggingFilter) {
std::string json_string = R"EOF(
{
"request_type" : "internal",
"ip_tags" : [
{ "ip_tag_name" : "example_tag",
"ip_list" : ["0.0.0.0"]
}
]
}
)EOF";

Json::ObjectSharedPtr json_config = Json::Factory::loadFromString(json_string);
NiceMock<MockInstance> server;
IpTaggingFilterConfig factory;
HttpFilterFactoryCb cb =
factory.createFilterFactory(HttpFilterType::Decoder, *json_config, "stats", server);
Http::MockFilterChainFactoryCallbacks filter_callback;
EXPECT_CALL(filter_callback, addStreamDecoderFilter(_));
cb(filter_callback);
}

TEST(HttpFilterConfigTest, BadIpTaggingFilterConfig) {
std::string json_string = R"EOF(
{
"request_type" : "internal",
"ip_tags" : [
{ "ip_tag_name" : "example_tag"
}
]
}
)EOF";

Json::ObjectSharedPtr json_config = Json::Factory::loadFromString(json_string);
NiceMock<MockInstance> server;
IpTaggingFilterConfig factory;
EXPECT_THROW(factory.createFilterFactory(HttpFilterType::Decoder, *json_config, "stats", server),
Json::Exception);
}

TEST(HttpFilterConfigTest, DoubleRegistrationTest) {
EXPECT_THROW_WITH_MESSAGE(
RegisterNamedHttpFilterConfigFactory<RouterFilterConfig>(), EnvoyException,
Expand Down