added support for tcp proxyProtocol v1&v2 to backend

This commit is contained in:
Matthias Schneider 2020-11-17 13:04:04 +01:00 committed by GitHub
parent 520fcf82ae
commit 84b125bdde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 388 additions and 83 deletions

View file

@ -183,6 +183,7 @@
- "traefik.tcp.routers.tcprouter1.tls.passthrough=true"
- "traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay=42"
- "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar"
- "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol.version=42"
- "traefik.udp.routers.udprouter0.entrypoints=foobar, foobar"
- "traefik.udp.routers.udprouter0.service=foobar"
- "traefik.udp.routers.udprouter1.entrypoints=foobar, foobar"

View file

@ -343,6 +343,8 @@
[tcp.services.TCPService01]
[tcp.services.TCPService01.loadBalancer]
terminationDelay = 42
[tcp.services.TCPService01.loadBalancer.proxyProtocol]
version = 42
[[tcp.services.TCPService01.loadBalancer.servers]]
address = "foobar"

View file

@ -387,6 +387,8 @@ tcp:
TCPService01:
loadBalancer:
terminationDelay: 42
proxyProtocol:
version: 42
servers:
- address: foobar
- address: foobar

View file

@ -247,6 +247,7 @@
| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/options` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/passthrough` | `true` |
| `traefik/tcp/services/TCPService01/loadBalancer/proxyProtocol/version` | `42` |
| `traefik/tcp/services/TCPService01/loadBalancer/servers/0/address` | `foobar` |
| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/address` | `foobar` |
| `traefik/tcp/services/TCPService01/loadBalancer/terminationDelay` | `42` |

View file

@ -177,6 +177,7 @@
"traefik.tcp.routers.tcprouter1.tls.options": "foobar",
"traefik.tcp.routers.tcprouter1.tls.passthrough": "true",
"traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay": "42",
"traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol.version": "42",
"traefik.tcp.services.tcpservice01.loadbalancer.server.port": "foobar",
"traefik.udp.routers.udprouter0.entrypoints": "foobar, foobar",
"traefik.udp.routers.udprouter0.service": "foobar",

View file

@ -381,6 +381,14 @@ You can declare TCP Routers and/or Services using tags.
traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
```yaml
traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1
```
### UDP
You can declare UDP Routers and/or Services using tags.

View file

@ -527,6 +527,14 @@ You can declare TCP Routers and/or Services using labels.
- "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100"
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1"
```
### UDP
You can declare UDP Routers and/or Services using labels.

View file

@ -388,6 +388,14 @@ You can declare TCP Routers and/or Services using labels.
traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
```yaml
traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1
```
### UDP
You can declare UDP Routers and/or Services using tags.

View file

@ -1090,40 +1090,44 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
port: 8080 # [6]
weight: 10 # [7]
terminationDelay: 400 # [8]
tls: # [9]
secretName: supersecret # [10]
options: # [11]
name: opt # [12]
namespace: default # [13]
certResolver: foo # [14]
domains: # [15]
- main: example.net # [16]
sans: # [17]
proxyProtocol: # [9]
version: 1 # [10]
tls: # [11]
secretName: supersecret # [12]
options: # [13]
name: opt # [14]
namespace: default # [15]
certResolver: foo # [16]
domains: # [17]
- main: example.net # [18]
sans: # [19]
- a.example.net
- b.example.net
passthrough: false # [18]
passthrough: false # [20]
```
| Ref | Attribute | Purpose |
|------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router |
| [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
| [5] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [7] | `services[n].weight` | Defines the weight to apply to the server load balancing |
| [8] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection.<br/>It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). |
| [9] | `tls` | Defines [TLS](../routers/index.md#tls_1) certificate configuration |
| [10] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [11] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [12] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [13] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [14] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver_1) |
| [15] | `tls.domains` | List of [domains](../routers/index.md#domains_1) |
| [16] | `domains[n].main` | Defines the main domain name |
| [17] | `domains[n].sans` | List of SANs (alternative domains) |
| [18] | `tls.passthrough` | If `true`, delegates the TLS termination to the backend |
| Ref | Attribute | Purpose |
|------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router |
| [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
| [5] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [7] | `services[n].weight` | Defines the weight to apply to the server load balancing |
| [8] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection. It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). |
| [9] | `proxyProtocol` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) configuration |
| [10] | `version` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) version |
| [11] | `tls` | Defines [TLS](../routers/index.md#tls_1) certificate configuration |
| [12] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [13] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [14] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [15] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [16] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver_1) |
| [17] | `tls.domains` | List of [domains](../routers/index.md#domains_1) |
| [18] | `domains[n].main` | Defines the main domain name |
| [19] | `domains[n].sans` | List of SANs (alternative domains) |
| [20] | `tls.passthrough` | If `true`, delegates the TLS termination to the backend |
??? example "Declaring an IngressRouteTCP"

View file

@ -384,6 +384,14 @@ You can declare TCP Routers and/or Services using KV.
| Key (Path) | Value |
|-------------------------------------------------------------------|-------|
| `traefik/tcp/services/mytcpservice/loadbalancer/terminationdelay` | `100` |
??? info "`traefik/tcp/services/<service_name>/loadbalancer/proxyprotocol/version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
| Key (Path) | Value |
|------------------------------------------------------------------------|-------|
| `traefik/tcp/services/mytcpservice/loadbalancer/proxyprotocol/version` | `1` |
??? info "`traefik/tcp/services/<service_name>/weighted/services/<n>/name`"

View file

@ -421,6 +421,14 @@ You can declare TCP Routers and/or Services using labels.
"traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay": "100"
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
```json
"traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version": "1"
```
### UDP
You can declare UDP Routers and/or Services using labels.

View file

@ -424,6 +424,14 @@ You can declare TCP Routers and/or Services using labels.
- "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100"
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1"
```
### UDP
You can declare UDP Routers and/or Services using labels.

View file

@ -991,6 +991,39 @@ The `address` option (IP:Port) point to a specific instance.
- address: "xx.xx.xx.xx:xx"
```
#### PROXY Protocol
Traefik supports [PROXY Protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2 on TCP Services.
It can be enabled by setting `proxyProtocol` on the load balancer.
Below are the available options for the PROXY protocol:
- `version` specifies the version of the protocol to be used. Either `1` or `2`.
!!! info "Version"
Specifying a version is optional. By default the version 2 will be used.
??? example "A Service with Proxy Protocol v1 -- Using the [File Provider](../../providers/file.md)"
```toml tab="TOML"
## Dynamic configuration
[tcp.services]
[tcp.services.my-service.loadBalancer]
[tcp.services.my-service.loadBalancer.proxyProtocol]
version = 1
```
```yaml tab="YAML"
## Dynamic configuration
tcp:
services:
my-service:
loadBalancer:
proxyProtocol:
version: 1
```
#### Termination Delay
As a proxy between a client and a server, it can happen that either side (e.g. client side) decides to terminate its writing capability on the connection (i.e. issuance of a FIN packet).

2
go.mod
View file

@ -14,7 +14,6 @@ require (
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd
github.com/aws/aws-sdk-go v1.30.20
github.com/c0va23/go-proxyprotocol v0.9.1
github.com/cenkalti/backoff/v4 v4.0.2
github.com/containerd/containerd v1.3.2 // indirect
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
@ -63,6 +62,7 @@ require (
github.com/openzipkin/zipkin-go v0.2.2
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/philhofer/fwd v1.0.0 // indirect
github.com/pires/go-proxyproto v0.3.1
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.3.0
github.com/prometheus/client_model v0.1.0

4
go.sum
View file

@ -141,8 +141,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34=
github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@ -680,6 +678,8 @@ github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0je
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pires/go-proxyproto v0.3.1 h1:eWb52zeDUbSUDBV+8aVCfOy0pnEG6DrDW3cJ/WKdQsk=
github.com/pires/go-proxyproto v0.3.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View file

@ -34,7 +34,7 @@ func (s *ProxyProtocolSuite) TestProxyProtocolTrusted(c *check.C) {
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://"+haproxyIP+"/whoami", 500*time.Millisecond,
err = try.GetRequest("http://"+haproxyIP+"/whoami", 1*time.Second,
try.StatusCodeIs(http.StatusOK),
try.BodyContains("X-Forwarded-For: "+gatewayIP))
c.Assert(err, checker.IsNil)
@ -57,7 +57,7 @@ func (s *ProxyProtocolSuite) TestProxyProtocolV2Trusted(c *check.C) {
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://"+haproxyIP+":81/whoami", 500*time.Millisecond,
err = try.GetRequest("http://"+haproxyIP+":81/whoami", 1*time.Second,
try.StatusCodeIs(http.StatusOK),
try.BodyContains("X-Forwarded-For: "+gatewayIP))
c.Assert(err, checker.IsNil)
@ -79,7 +79,7 @@ func (s *ProxyProtocolSuite) TestProxyProtocolNotTrusted(c *check.C) {
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://"+haproxyIP+"/whoami", 500*time.Millisecond,
err = try.GetRequest("http://"+haproxyIP+"/whoami", 1*time.Second,
try.StatusCodeIs(http.StatusOK),
try.BodyContains("X-Forwarded-For: "+haproxyIP))
c.Assert(err, checker.IsNil)
@ -101,7 +101,7 @@ func (s *ProxyProtocolSuite) TestProxyProtocolV2NotTrusted(c *check.C) {
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://"+haproxyIP+":81/whoami", 500*time.Millisecond,
err = try.GetRequest("http://"+haproxyIP+":81/whoami", 1*time.Second,
try.StatusCodeIs(http.StatusOK),
try.BodyContains("X-Forwarded-For: "+haproxyIP))
c.Assert(err, checker.IsNil)

View file

@ -72,8 +72,9 @@ type TCPServersLoadBalancer struct {
// connection, to close the reading capability as well, hence fully terminating the
// connection. It is a duration in milliseconds, defaulting to 100. A negative value
// means an infinite deadline (i.e. the reading capability is never closed).
TerminationDelay *int `json:"terminationDelay,omitempty" toml:"terminationDelay,omitempty" yaml:"terminationDelay,omitempty"`
Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server"`
TerminationDelay *int `json:"terminationDelay,omitempty" toml:"terminationDelay,omitempty" yaml:"terminationDelay,omitempty"`
ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty"`
Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server"`
}
// SetDefaults Default values for a TCPServersLoadBalancer.
@ -106,3 +107,15 @@ type TCPServer struct {
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty" label:"-"`
Port string `toml:"-" json:"-" yaml:"-"`
}
// +k8s:deepcopy-gen=true
// ProxyProtocol holds the ProxyProtocol configuration.
type ProxyProtocol struct {
Version int `json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty"`
}
// SetDefaults Default values for a ProxyProtocol.
func (p *ProxyProtocol) SetDefaults() {
p.Version = 2
}

View file

@ -880,6 +880,22 @@ func (in *PassTLSClientCert) DeepCopy() *PassTLSClientCert {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProxyProtocol) DeepCopyInto(out *ProxyProtocol) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyProtocol.
func (in *ProxyProtocol) DeepCopy() *ProxyProtocol {
if in == nil {
return nil
}
out := new(ProxyProtocol)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
*out = *in
@ -1373,6 +1389,11 @@ func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) {
*out = new(int)
**out = **in
}
if in.ProxyProtocol != nil {
in, out := &in.ProxyProtocol, &out.ProxyProtocol
*out = new(ProxyProtocol)
**out = **in
}
if in.Servers != nil {
in, out := &in.Servers, &out.Servers
*out = make([]TCPServer, len(*in))

View file

@ -182,8 +182,10 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.tcp.routers.Router1.tls.passthrough": "false",
"traefik.tcp.services.Service0.loadbalancer.server.Port": "42",
"traefik.tcp.services.Service0.loadbalancer.TerminationDelay": "42",
"traefik.tcp.services.Service0.loadbalancer.proxyProtocol.version": "42",
"traefik.tcp.services.Service1.loadbalancer.server.Port": "42",
"traefik.tcp.services.Service1.loadbalancer.TerminationDelay": "42",
"traefik.tcp.services.Service1.loadbalancer.proxyProtocol": "true",
"traefik.udp.routers.Router0.entrypoints": "foobar, fiibar",
"traefik.udp.routers.Router0.service": "foobar",
@ -233,6 +235,7 @@ func TestDecodeConfiguration(t *testing.T) {
},
},
TerminationDelay: func(i int) *int { return &i }(42),
ProxyProtocol: &dynamic.ProxyProtocol{Version: 42},
},
},
"Service1": {
@ -243,6 +246,7 @@ func TestDecodeConfiguration(t *testing.T) {
},
},
TerminationDelay: func(i int) *int { return &i }(42),
ProxyProtocol: &dynamic.ProxyProtocol{Version: 2},
},
},
},

View file

@ -0,0 +1,17 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
proxyProtocol:
version: 2

View file

@ -140,6 +140,15 @@ func createLoadBalancerServerTCP(client Client, namespace string, service v1alph
},
}
if service.ProxyProtocol != nil {
tcpService.LoadBalancer.ProxyProtocol = &dynamic.ProxyProtocol{}
tcpService.LoadBalancer.ProxyProtocol.SetDefaults()
if service.ProxyProtocol.Version != 0 {
tcpService.LoadBalancer.ProxyProtocol.Version = service.ProxyProtocol.Version
}
}
if service.TerminationDelay != nil {
tcpService.LoadBalancer.TerminationDelay = service.TerminationDelay
}

View file

@ -2878,6 +2878,49 @@ func TestLoadIngressRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP with proxyProtocol Version",
paths: []string{"tcp/services.yml", "tcp/with_proxyprotocol.yml"},
expected: &dynamic.Configuration{
TLS: &dynamic.TLSConfiguration{},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{
"default-test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
ProxyProtocol: &dynamic.ProxyProtocol{Version: 2},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
},
},
{
desc: "TLS with tls store",
paths: []string{"services.yml", "with_tls_store.yml"},

View file

@ -1,6 +1,7 @@
package v1alpha1
import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -53,11 +54,12 @@ type TLSStoreTCPRef struct {
// ServiceTCP defines an upstream to proxy traffic.
type ServiceTCP struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Port int32 `json:"port"`
Weight *int `json:"weight,omitempty"`
TerminationDelay *int `json:"terminationDelay,omitempty"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Port int32 `json:"port"`
Weight *int `json:"weight,omitempty"`
TerminationDelay *int `json:"terminationDelay,omitempty"`
ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"`
}
// +genclient

View file

@ -1011,6 +1011,11 @@ func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = new(int)
**out = **in
}
if in.ProxyProtocol != nil {
in, out := &in.ProxyProtocol, &out.ProxyProtocol
*out = new(dynamic.ProxyProtocol)
**out = **in
}
return
}

View file

@ -11,7 +11,7 @@ import (
"syscall"
"time"
proxyprotocol "github.com/c0va23/go-proxyprotocol"
"github.com/pires/go-proxyproto"
"github.com/sirupsen/logrus"
"github.com/traefik/traefik/v2/pkg/config/static"
"github.com/traefik/traefik/v2/pkg/ip"
@ -316,10 +316,10 @@ func (c *writeCloserWrapper) CloseWrite() error {
// implementation, if any was found within the underlying conn.
func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
switch typedConn := conn.(type) {
case *proxyprotocol.Conn:
underlying, err := writeCloser(typedConn.Conn)
if err != nil {
return nil, err
case *proxyproto.Conn:
underlying, ok := typedConn.TCPConn()
if !ok {
return nil, fmt.Errorf("underlying connection is not a tcp connection")
}
return &writeCloserWrapper{writeCloser: underlying, Conn: typedConn}, nil
case *net.TCPConn:
@ -356,42 +356,35 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
return tc, nil
}
type proxyProtocolLogger struct {
log.Logger
}
// Printf force log level to debug.
func (p proxyProtocolLogger) Printf(format string, v ...interface{}) {
p.Debugf(format, v...)
}
func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) {
var sourceCheck func(addr net.Addr) (bool, error)
proxyListener := &proxyproto.Listener{Listener: listener}
if entryPoint.ProxyProtocol.Insecure {
sourceCheck = func(_ net.Addr) (bool, error) {
return true, nil
}
} else {
checker, err := ip.NewChecker(entryPoint.ProxyProtocol.TrustedIPs)
if err != nil {
return nil, err
log.FromContext(ctx).Infof("Enabling ProxyProtocol without trusted IPs: Insecure")
return proxyListener, nil
}
checker, err := ip.NewChecker(entryPoint.ProxyProtocol.TrustedIPs)
if err != nil {
return nil, err
}
proxyListener.Policy = func(upstream net.Addr) (proxyproto.Policy, error) {
ipAddr, ok := upstream.(*net.TCPAddr)
if !ok {
return proxyproto.REJECT, fmt.Errorf("type error %v", upstream)
}
sourceCheck = func(addr net.Addr) (bool, error) {
ipAddr, ok := addr.(*net.TCPAddr)
if !ok {
return false, fmt.Errorf("type error %v", addr)
}
return checker.ContainsIP(ipAddr.IP), nil
if !checker.ContainsIP(ipAddr.IP) {
log.FromContext(ctx).Debugf("IP %s is not in trusted IPs list, ignoring ProxyProtocol Headers and bypass connection", ipAddr.IP)
return proxyproto.IGNORE, nil
}
return proxyproto.USE, nil
}
log.FromContext(ctx).Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
return proxyprotocol.NewDefaultListener(listener).
WithSourceChecker(sourceCheck).
WithLogger(proxyProtocolLogger{Logger: log.FromContext(ctx)}), nil
return proxyListener, nil
}
func buildListener(ctx context.Context, entryPoint *static.EntryPoint) (net.Listener, error) {

View file

@ -59,7 +59,7 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
continue
}
handler, err := tcp.NewProxy(server.Address, duration)
handler, err := tcp.NewProxy(server.Address, duration, conf.LoadBalancer.ProxyProtocol)
if err != nil {
logger.Errorf("In service %q server %q: %v", serviceQualifiedName, server.Address, err)
continue

View file

@ -1,10 +1,13 @@
package tcp
import (
"fmt"
"io"
"net"
"time"
"github.com/pires/go-proxyproto"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
)
@ -12,16 +15,21 @@ import (
type Proxy struct {
target *net.TCPAddr
terminationDelay time.Duration
proxyProtocol *dynamic.ProxyProtocol
}
// NewProxy creates a new Proxy.
func NewProxy(address string, terminationDelay time.Duration) (*Proxy, error) {
func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dynamic.ProxyProtocol) (*Proxy, error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return nil, err
}
return &Proxy{target: tcpAddr, terminationDelay: terminationDelay}, nil
if proxyProtocol != nil && (proxyProtocol.Version < 1 || proxyProtocol.Version > 2) {
return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version)
}
return &Proxy{target: tcpAddr, terminationDelay: terminationDelay, proxyProtocol: proxyProtocol}, nil
}
// ServeTCP forwards the connection to a service.
@ -39,8 +47,16 @@ func (p *Proxy) ServeTCP(conn WriteCloser) {
// maybe not needed, but just in case
defer connBackend.Close()
errChan := make(chan error)
if p.proxyProtocol != nil && p.proxyProtocol.Version > 0 && p.proxyProtocol.Version < 3 {
header := proxyproto.HeaderProxyFromAddrs(byte(p.proxyProtocol.Version), conn.RemoteAddr(), conn.LocalAddr())
if _, err := header.WriteTo(connBackend); err != nil {
log.WithoutContext().Errorf("Error while writing proxy protocol headers to backend connection: %v", err)
return
}
}
go p.connCopy(conn, connBackend, errChan)
go p.connCopy(connBackend, conn, errChan)

View file

@ -2,13 +2,17 @@ package tcp
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"testing"
"time"
"github.com/pires/go-proxyproto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
)
func fakeRedis(t *testing.T, listener net.Listener) {
@ -16,6 +20,7 @@ func fakeRedis(t *testing.T, listener net.Listener) {
conn, err := listener.Accept()
fmt.Println("Accept on server")
require.NoError(t, err)
for {
withErr := false
buf := make([]byte, 64)
@ -26,12 +31,13 @@ func fakeRedis(t *testing.T, listener net.Listener) {
if string(buf[:4]) == "ping" {
time.Sleep(1 * time.Millisecond)
if _, err := conn.Write([]byte("PONG")); err != nil {
conn.Close()
_ = conn.Close()
return
}
}
if withErr {
conn.Close()
_ = conn.Close()
return
}
}
@ -46,7 +52,7 @@ func TestCloseWrite(t *testing.T) {
_, port, err := net.SplitHostPort(backendListener.Addr().String())
require.NoError(t, err)
proxy, err := NewProxy(":"+port, 10*time.Millisecond)
proxy, err := NewProxy(":"+port, 10*time.Millisecond, nil)
require.NoError(t, err)
proxyListener, err := net.Listen("tcp", ":0")
@ -79,3 +85,87 @@ func TestCloseWrite(t *testing.T) {
require.Equal(t, int64(4), n)
require.Equal(t, "PONG", buffer.String())
}
func TestProxyProtocol(t *testing.T) {
testCases := []struct {
desc string
version int
}{
{
desc: "PROXY protocol v1",
version: 1,
},
{
desc: "PROXY protocol v2",
version: 2,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
backendListener, err := net.Listen("tcp", ":0")
require.NoError(t, err)
var version int
proxyBackendListener := proxyproto.Listener{
Listener: backendListener,
ValidateHeader: func(h *proxyproto.Header) error {
version = int(h.Version)
return nil
},
Policy: func(upstream net.Addr) (proxyproto.Policy, error) {
switch test.version {
case 1, 2:
return proxyproto.USE, nil
default:
return proxyproto.REQUIRE, errors.New("unsupported version")
}
},
}
defer proxyBackendListener.Close()
go fakeRedis(t, &proxyBackendListener)
_, port, err := net.SplitHostPort(proxyBackendListener.Addr().String())
require.NoError(t, err)
proxy, err := NewProxy(":"+port, 10*time.Millisecond, &dynamic.ProxyProtocol{Version: test.version})
require.NoError(t, err)
proxyListener, err := net.Listen("tcp", ":0")
require.NoError(t, err)
go func() {
for {
conn, err := proxyListener.Accept()
require.NoError(t, err)
proxy.ServeTCP(conn.(*net.TCPConn))
}
}()
_, port, err = net.SplitHostPort(proxyListener.Addr().String())
require.NoError(t, err)
conn, err := net.Dial("tcp", ":"+port)
require.NoError(t, err)
_, err = conn.Write([]byte("ping\n"))
require.NoError(t, err)
err = conn.(*net.TCPConn).CloseWrite()
require.NoError(t, err)
var buf []byte
buffer := bytes.NewBuffer(buf)
n, err := io.Copy(buffer, conn)
require.NoError(t, err)
assert.Equal(t, int64(4), n)
assert.Equal(t, "PONG", buffer.String())
assert.Equal(t, test.version, version)
})
}
}