Tunneling egress traffic through the gateway
Shared with Istio Community
Owner: jewertow@redhat.com Working Group: networking | Status: WIP | In Review | Approved | Obsolete Approvers: WG-lead-X [], WG-lead-Y [], ... |
Some users have to route outbound traffic to external services (outside the company network) via forward proxy, because of security policies. To do that, they use the HTTP CONNECT method, but it requires configuring their applications to use those proxies and they would like to make them transparent for apps.
The expected solution is to make it possible to enable TCP tunneling in egress gateway. Then apps would perform standard requests that would be routed to the egress gateway, which would be responsible for tunneling received requests.
The diagrams below show the current and expected solutions.
Envoy
- name: tcp
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
cluster: forward_proxy_cluster
tunneling_config:
hostname: example.com:443
The only limitation of tunneling_config is that it supports only static hostname for now.
This means that it wouldn’t make sense to enable tunneling when filter_chain_match.server_names contains:
because in these cases it’s not known which hostname to set in tunneling_config.
I already started a discussion with the Envoy community and maintainers are open to extending tunneling_config to automatically set the right hostname in TLS listeners. Then using multiple hostnames or hostnames with wildcards could be allowed, because Envoy would rely on SNI value provided in runtime by TLS inspector filter. But in case of plain TCP listeners, it would still be needed to set a static hostname.
Istio
Since Envoy allows tunneling by TcpPoxy filters, Istio could also support it in gateway servers that listen on TCP or TLS, because for these protocols TcpProxy filters are generated. On the other hand, support for tunneling in HTTP or HTTPS gateway servers shouldn’t be considered at all, because there is no practical use case for it.
To enable this solution in Istio, I suggest extending DestinationRule.TrafficPolicy, with tunneling configuration as follows:
message TrafficPolicy {
message TunnelingSettings {
enum TunnelingMethod {
CONNECT = 1;
POST = 2;
}
// specifies which HTTP method to use to request a tunnel
TunnelingMethod method = 1;
}
// optional field that enables tunneling traffic;
// important limitation:
// allowed to apply only to routes for TCP or TLS listeners
TunnelingSettings tunnel = 6;
}
The implementation of handling DestinationRule would apply proper tunneling_config based on specified TrafficPolicy.TunnelingSettings and Gateway.Server.Hosts.
Without extending Envoy with automatically set hostname in tunneling_config, this solution would have also the following limitations:
Then enabling tunneling for all routes with destination host external-forward-proxy.com would require to apply the following resource:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: tunnel-tls
spec:
host: external-forward-proxy.com
trafficPolicy:
tunnel:
method: CONNECT
In a case where only certain routes need tunneling, then subsets might be used to apply different rules for the same destination.
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: tunnel-tls
spec:
host: external-forward-proxy.com
subsets:
- name: google
trafficPolicy:
tunnel:
method: CONNECT
By default a port in the request-target of the CONNECT method would be 443 or a port of the listener. But there might be use cases where users need a custom port for a particular hostname to request a tunnel, so we could extend TunnelingSettings with a field port.
message TunnelingSettings {
enum TunnelingMethod {
CONNECT = 1;
POST = 2;
}
// specifies which HTTP method to use to tunnel traffic
TunnelingMethod method = 1;
// specifies a custom port used in the request-target of CONNECT request,
// i.e. CONNECT <hostname>:<port>;
// 0 means that port used by a listener will be used in the request-target
uint32 port = 2;
}
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: tunnel-tls
spec:
host: external-forward-proxy.com
subsets:
- name: tunnel-google
trafficPolicy:
tunnel:
method: CONNECT
- name: tunnel-wikipedia
trafficPolicy:
tunnel:
method: POST
port: 8443
Custom ports might be really useful in case of a gateway listening on terminated TLS. In such a case, TLS routes cannot be applied and TCP routes must be used instead, but TCP routes do not allow SNI-based routing. The workaround is to create as many gateway servers (on different ports) as many SNI routes need to be performed, but then server ports may not match the target ports, so setting them in a destination rule would fix the problem.
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: external-forward-proxy
spec:
hosts:
- external-forward-proxy.company.net
location: MESH_EXTERNAL
ports:
- number: 443
name: tls
protocol: TLS
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: example-com
spec:
hosts:
- www.example.com
location: MESH_EXTERNAL
ports:
- number: 443
name: tls
protocol: TLS
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: example-com-via-egress-gateway
spec:
hosts:
- www.example.com
gateways:
- istio-egressgateway
- mesh
tls:
- match:
- gateways:
- mesh
port: 443
sniHosts:
- www.example.com
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
port:
number: 443
- match:
- gateways:
- istio-egressgateway
port: 443
sniHosts:
- www.example.com
route:
- destination:
host: external-forward-proxy.company.net
port:
number: 443
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: tunnel-tls
spec:
host: external-forward-proxy.company.net
trafficPolicy:
tunnel:
method: CONNECT
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway
spec:
selector:
istio: egressgateway
servers:
- port:
number: 443
name: tls
protocol: TLS
hosts:
- www.example.com
tls:
mode: PASSTHROUGH
Shared with Istio Community