Skip to content

Commit

Permalink
http: add dynamic forward proxy filter and cluster (envoyproxy#7307)
Browse files Browse the repository at this point in the history
This allows using Envoy as a generic HTTP proxy without any
prior configuration of DNS targets. See the included documentation
for more information.

Part of envoyproxy#1606

Signed-off-by: Matt Klein <[email protected]>
  • Loading branch information
mattklein123 authored Jun 20, 2019
1 parent 2e03ef2 commit 79e53f2
Show file tree
Hide file tree
Showing 47 changed files with 2,534 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,9 @@ extensions/filters/common/original_src @snowp @klarose
/*/extensions/filters/network/zookeeper_proxy @rgs1 @snowp
# redis cluster extension
/*/extensions/clusters/redis @msukalski @henryyyang @mattklein123
# dynamic forward proxy
/*/extensions/clusters/dynamic_forward_proxy @mattklein123 @alyssawilk
/*/extensions/common/dynamic_forward_proxy @mattklein123 @alyssawilk
/*/extensions/filters/http/dynamic_forward_proxy @mattklein123 @alyssawilk
# omit_canary_hosts retry predicate
/*/extensions/retry/host/omit_canary_hosts @sriduth @snowp
3 changes: 3 additions & 0 deletions api/docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ proto_library(
"//envoy/config/accesslog/v2:als",
"//envoy/config/accesslog/v2:file",
"//envoy/config/bootstrap/v2:bootstrap",
"//envoy/config/cluster/dynamic_forward_proxy/v2alpha:cluster",
"//envoy/config/cluster/redis:redis_cluster",
"//envoy/config/common/dynamic_forward_proxy/v2alpha:dns_cache",
"//envoy/config/common/tap/v2alpha:common",
"//envoy/config/filter/accesslog/v2:accesslog",
"//envoy/config/filter/dubbo/router/v2alpha1:router",
"//envoy/config/filter/http/buffer/v2:buffer",
"//envoy/config/filter/http/csrf/v2:csrf",
"//envoy/config/filter/http/dynamic_forward_proxy/v2alpha:dynamic_forward_proxy",
"//envoy/config/filter/http/ext_authz/v2:ext_authz",
"//envoy/config/filter/http/fault/v2:fault",
"//envoy/config/filter/http/gzip/v2:gzip",
Expand Down
11 changes: 11 additions & 0 deletions api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal")

licenses(["notice"]) # Apache 2

api_proto_library_internal(
name = "cluster",
srcs = ["cluster.proto"],
deps = [
"//envoy/config/common/dynamic_forward_proxy/v2alpha:dns_cache",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

package envoy.config.cluster.dynamic_forward_proxy.v2alpha;

option java_outer_classname = "DynamicForwardProxyClusterProto";
option java_multiple_files = true;
option java_package = "io.envoyproxy.envoy.config.cluster.dynamic_forward_proxy.v2alpha";
option go_package = "v2alpha";

import "envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto";

import "validate/validate.proto";

// [#protodoc-title: Dynamic forward proxy cluster configuration]

// Configuration for the dynamic forward proxy cluster. See the :ref:`architecture overview
// <arch_overview_http_dynamic_forward_proxy>` for more information.
message ClusterConfig {
// The DNS cache configuration that the cluster will attach to. Note this configuration must
// match that of associated :ref:`dynamic forward proxy HTTP filter configuration
// <envoy_api_field_config.filter.http.dynamic_forward_proxy.v2alpha.FilterConfig.dns_cache_config>`.
common.dynamic_forward_proxy.v2alpha.DnsCacheConfig dns_cache_config = 1
[(validate.rules).message.required = true];
}
12 changes: 12 additions & 0 deletions api/envoy/config/common/dynamic_forward_proxy/v2alpha/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal")

licenses(["notice"]) # Apache 2

api_proto_library_internal(
name = "dns_cache",
srcs = ["dns_cache.proto"],
visibility = ["//visibility:public"],
deps = [
"//envoy/api/v2:cds",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
syntax = "proto3";

package envoy.config.common.dynamic_forward_proxy.v2alpha;

option java_outer_classname = "DnsCacheProto";
option java_multiple_files = true;
option java_package = "io.envoyproxy.envoy.config.common.dynamic_forward_proxy.v2alpha";

import "envoy/api/v2/cds.proto";

import "google/protobuf/duration.proto";

import "validate/validate.proto";

// [#protodoc-title: Dynamic forward proxy common configuration]

// Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview
// <arch_overview_http_dynamic_forward_proxy>` for more information.
message DnsCacheConfig {
// The name of the cache. Multiple named caches allow independent dynamic forward proxy
// configurations to operate within a single Envoy process using different configurations. All
// configurations with the same name *must* otherwise have the same settings when referenced
// from different configuration components. Configuration will fail to load if this is not
// the case.
string name = 1 [(validate.rules).string.min_bytes = 1];

// The DNS lookup family to use during resolution.
//
// [#comment:TODO(mattklein123): Figure out how to support IPv4/IPv6 "happy eyeballs" mode. The
// way this might work is a new lookup family which returns both IPv4 and IPv6 addresses, and
// then configures a host to have a primary and fall back address. With this, we could very
// likely build a "happy eyeballs" connection pool which would race the primary / fall back
// address and return the one that wins. This same method could potentially also be used for
// QUIC to TCP fall back.]
api.v2.Cluster.DnsLookupFamily dns_lookup_family = 2 [(validate.rules).enum.defined_only = true];

// The DNS refresh rate for currently cached DNS hosts. If not specified defaults to 60s.
//
// .. note:
//
// The returned DNS TTL is not currently used to alter the refresh rate. This feature will be
// added in a future change.
google.protobuf.Duration dns_refresh_rate = 3 [(validate.rules).duration.gt = {}];

// The TTL for hosts that are unused. Hosts that have not been used in the configured time
// interval will be purged. If not specified defaults to 5m.
//
// .. note:
//
// The TTL is only checked at the time of DNS refresh, as specified by *dns_refresh_rate*. This
// means that if the configured TTL is shorter than the refresh rate the host may not be removed
// immediately.
//
// .. note:
//
// The TTL has no relation to DNS TTL and is only used to control Envoy's resource usage.
google.protobuf.Duration host_ttl = 4 [(validate.rules).duration.gt = {}];
}
11 changes: 11 additions & 0 deletions api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal")

licenses(["notice"]) # Apache 2

api_proto_library_internal(
name = "dynamic_forward_proxy",
srcs = ["dynamic_forward_proxy.proto"],
deps = [
"//envoy/config/common/dynamic_forward_proxy/v2alpha:dns_cache",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

package envoy.config.filter.http.dynamic_forward_proxy.v2alpha;

option java_outer_classname = "DynamicForwardProxyProto";
option java_multiple_files = true;
option java_package = "io.envoyproxy.envoy.config.filter.http.dynamic_forward_proxy.v2alpha";
option go_package = "v2alpha";

import "envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto";

import "validate/validate.proto";

// [#protodoc-title: Dynamic forward proxy]

// Configuration for the dynamic forward proxy HTTP filter. See the :ref:`architecture overview
// <arch_overview_http_dynamic_forward_proxy>` for more information.
message FilterConfig {
// The DNS cache configuration that the filter will attach to. Note this configuration must
// match that of associated :ref:`dynamic forward proxy cluster configuration
// <envoy_api_field_config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig.dns_cache_config>`.
common.dynamic_forward_proxy.v2alpha.DnsCacheConfig dns_cache_config = 1
[(validate.rules).message.required = true];
}
3 changes: 3 additions & 0 deletions docs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ PROTO_RST="
/envoy/config/accesslog/v2/als/envoy/config/accesslog/v2/als.proto.rst
/envoy/config/accesslog/v2/file/envoy/config/accesslog/v2/file.proto.rst
/envoy/config/bootstrap/v2/bootstrap/envoy/config/bootstrap/v2/bootstrap.proto.rst
/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto.rst
/envoy/config/cluster/redis/redis_cluster/envoy/config/cluster/redis/redis_cluster.proto.rst
/envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache/envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto.rst
/envoy/config/common/tap/v2alpha/common/envoy/config/common/tap/v2alpha/common.proto.rst
/envoy/config/ratelimit/v2/rls/envoy/config/ratelimit/v2/rls.proto.rst
/envoy/config/metrics/v2/metrics_service/envoy/config/metrics/v2/metrics_service.proto.rst
Expand All @@ -95,6 +97,7 @@ PROTO_RST="
/envoy/config/filter/fault/v2/fault/envoy/config/filter/fault/v2/fault.proto.rst
/envoy/config/filter/http/buffer/v2/buffer/envoy/config/filter/http/buffer/v2/buffer.proto.rst
/envoy/config/filter/http/csrf/v2/csrf/envoy/config/filter/http/csrf/v2/csrf.proto.rst
/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto.rst
/envoy/config/filter/http/ext_authz/v2/ext_authz/envoy/config/filter/http/ext_authz/v2/ext_authz.proto.rst
/envoy/config/filter/http/fault/v2/fault/envoy/config/filter/http/fault/v2/fault.proto.rst
/envoy/config/filter/http/gzip/v2/gzip/envoy/config/filter/http/gzip/v2/gzip.proto.rst
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v2/config/cluster/cluster.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ Cluster
:glob:
:maxdepth: 1

dynamic_forward_proxy/v2alpha/*
redis/*
1 change: 1 addition & 0 deletions docs/root/api-v2/config/common/common.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ Common
:glob:
:maxdepth: 2

dynamic_forward_proxy/v2alpha/*
tap/v2alpha/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
.. _config_http_filters_dynamic_forward_proxy:

Dynamic forward proxy
=====================

.. attention::

HTTP dynamic forward proxy support should be considered alpha and not production ready. Stats
as well as circuit breakers are missing and will be added soon.

* HTTP dynamic forward proxy :ref:`architecture overview <arch_overview_http_dynamic_forward_proxy>`
* :ref:`v2 API reference <envoy_api_msg_config.filter.http.dynamic_forward_proxy.v2alpha.FilterConfig>`
* This filter should be configured with the name *envoy.filters.http.dynamic_forward_proxy*.

The following is a complete configuration that configures both the
:ref:`dynamic forward proxy HTTP filter
<envoy_api_msg_config.filter.http.dynamic_forward_proxy.v2alpha.FilterConfig>`
as well as the :ref:`dynamic forward proxy cluster
<envoy_api_msg_config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig>`. Both filter and cluster
must be configured together and point to the same DNS cache parameters for Envoy to operate as an
HTTP dynamic forward proxy.

.. note::

The HTTP connection manager :ref:`allow_absolute_url
<envoy_api_field_core.Http1ProtocolOptions.allow_absolute_url>` parameter has been set to true
to allow Envoy to proxy absolute HTTP URLs.

.. attention::

While configuring a :ref:`tls_context <envoy_api_field_Cluster.tls_Context>` on the cluster with
*trusted_ca* certificates instructs Envoy to use TLS when connecting to upstream hosts and verify
the certificate chain, currently it is not possible to configure per-host TLS configuration
parameters including SNI, subject alt name verification, etc. This will be added in a future
change. **This means that the following configuration will not fully validate TLS certificates**.
Use with care until full support for per-host validation is implemented.

.. code-block:: yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
protocol: TCP
address: 127.0.0.1
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
stat_prefix: ingress_http
http_protocol_options:
allow_absolute_url: true
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: dynamic_forward_proxy_cluster
http_filters:
- name: envoy.filters.http.dynamic_forward_proxy
config:
dns_cache_config:
name: dynamic_forward_proxy_cache_config
dns_lookup_family: V4_ONLY
- name: envoy.router
clusters:
- name: dynamic_forward_proxy_cluster
connect_timeout: 1s
lb_policy: CLUSTER_PROVIDED
cluster_type:
name: envoy.clusters.dynamic_forward_proxy
typed_config:
"@type": type.googleapis.com/envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig
dns_cache_config:
name: dynamic_forward_proxy_cache_config
dns_lookup_family: V4_ONLY
tls_context:
common_tls_context:
validation_context:
trusted_ca: {filename: /etc/ssl/certs/ca-certificates.crt}
1 change: 1 addition & 0 deletions docs/root/configuration/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ HTTP filters
buffer_filter
cors_filter
csrf_filter
dynamic_forward_proxy_filter
dynamodb_filter
ext_authz_filter
fault_filter
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/arch_overview/http/http.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ HTTP
http_filters
http_routing
websocket
http_proxy
54 changes: 54 additions & 0 deletions docs/root/intro/arch_overview/http/http_proxy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.. _arch_overview_http_dynamic_forward_proxy:

HTTP dynamic forward proxy
==========================

.. attention::

HTTP dynamic forward proxy support should be considered alpha and not production ready. Stats
as well as circuit breakers are missing and will be added soon.

Through the combination of both an :ref:`HTTP filter <config_http_filters_dynamic_forward_proxy>` and
:ref:`custom cluster <envoy_api_msg_config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig>`,
Envoy supports HTTP dynamic forward proxy. This means that Envoy can perform the role of an HTTP
proxy without prior knowledge of all configured DNS addresses, while still retaining the vast
majority of Envoy's benefits including asynchronous DNS resolution. The implementation works as
follows:

* The dynamic forward proxy HTTP filter is used to pause requests if the target DNS host is not
already in cache.
* Envoy will begin asynchronously resolving the DNS address, unblocking any requests waiting on
the response when the resolution completes.
* Any future requests will not be blocked as the DNS address is already in cache. The resolution
process works similarly to the :ref:`logical DNS
<arch_overview_service_discovery_types_logical_dns>` service discovery type with a single target
address being remembered at any given time.
* All known hosts are stored in the dynamic forward proxy cluster such that they can be displayed
in :ref:`admin output <operations_admin_interface>`.
* A special load balancer will select the right host to use based on the HTTP host/authority header
during forwarding.
* Hosts that have not been used for a period of time are subject to a TTL that will purge them.

The above implementation details mean that at steady state Envoy can forward a large volume of
HTTP proxy traffic while all DNS resolution happens asynchronously in the background. Additionally,
all other Envoy filters and extensions can be used in conjunction with dynamic forward proxy support
including authentication, RBAC, rate limiting, etc.

For further configuration information see the :ref:`HTTP filter configuration documentation
<config_http_filters_dynamic_forward_proxy>`.

Memory usage details
--------------------

.. attention::

HTTP dynamic forward proxy support currently can use unbounded memory and is not ready for
production use. Circuit breakers and other limits will be added soon.

Memory usage detail's for Envoy's dynamic forward proxy support are as follows:

* Each resolved host/port pair uses a fixed amount of memory global to the server and shared
amongst all workers.
* Address changes are performed inline using read/write locks and require no host reallocations.
* Hosts removed via TTL are purged once all active connections stop referring to them and all used
memory is regained.
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Version history
* http: mitigated a race condition with the :ref:`delayed_close_timeout<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.delayed_close_timeout>` where it could trigger while actively flushing a pending write buffer for a downstream connection.
* http: added support for :ref:`preserve_external_request_id<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.preserve_external_request_id>` that represents whether the x-request-id should not be reset on edge entry inside mesh
* http: changed `sendLocalReply` to send percent-encoded `GrpcMessage`.
* http: added :ref:`dynamic forward proxy <arch_overview_http_dynamic_forward_proxy>` support.
* jwt_authn: make filter's parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123``
* listener: added :ref:`source IP <envoy_api_field_listener.FilterChainMatch.source_prefix_ranges>`
and :ref:`source port <envoy_api_field_listener.FilterChainMatch.source_ports>` filter
Expand Down
1 change: 1 addition & 0 deletions source/common/common/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace Logger {
FUNCTION(dubbo) \
FUNCTION(file) \
FUNCTION(filter) \
FUNCTION(forward_proxy) \
FUNCTION(grpc) \
FUNCTION(hc) \
FUNCTION(health_checker) \
Expand Down
7 changes: 6 additions & 1 deletion source/common/upstream/upstream_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,12 @@ bool BaseDynamicClusterImpl::updateDynamicHostList(const HostVector& new_hosts,
}

Network::DnsLookupFamily getDnsLookupFamilyFromCluster(const envoy::api::v2::Cluster& cluster) {
switch (cluster.dns_lookup_family()) {
return getDnsLookupFamilyFromEnum(cluster.dns_lookup_family());
}

Network::DnsLookupFamily
getDnsLookupFamilyFromEnum(envoy::api::v2::Cluster::DnsLookupFamily family) {
switch (family) {
case envoy::api::v2::Cluster::V6_ONLY:
return Network::DnsLookupFamily::V6Only;
case envoy::api::v2::Cluster::V4_ONLY:
Expand Down
Loading

0 comments on commit 79e53f2

Please sign in to comment.