Merge branch 'v2.1' into master
This commit is contained in:
commit
da3d814c8b
56 changed files with 2162 additions and 558 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -1,3 +1,35 @@
|
||||||
|
## [v2.1.2](https://github.com/containous/traefik/tree/v2.1.2) (2020-01-07)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v2.1.1...v2.1.2)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[authentication,middleware,tracing]** fix(tracing): makes sure tracing headers are being propagated when using forwardAuth ([#6072](https://github.com/containous/traefik/pull/6072) by [jcchavezs](https://github.com/jcchavezs))
|
||||||
|
- **[cli]** fix: invalid label/flag parsing. ([#6028](https://github.com/containous/traefik/pull/6028) by [ldez](https://github.com/ldez))
|
||||||
|
- **[consulcatalog]** Query consul catalog for service health separately ([#6046](https://github.com/containous/traefik/pull/6046) by [SantoDE](https://github.com/SantoDE))
|
||||||
|
- **[k8s,k8s/crd]** Restore ExternalName https support for Kubernetes CRD ([#6037](https://github.com/containous/traefik/pull/6037) by [kpeiruza](https://github.com/kpeiruza))
|
||||||
|
- **[k8s,k8s/crd]** Log the ignored namespace only when needed ([#6087](https://github.com/containous/traefik/pull/6087) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||||
|
- **[k8s,k8s/ingress]** k8s Ingress: fix crash on rules with nil http ([#6121](https://github.com/containous/traefik/pull/6121) by [grimmy](https://github.com/grimmy))
|
||||||
|
- **[logs]** Improves error message when a configuration file is empty. ([#6135](https://github.com/containous/traefik/pull/6135) by [ldez](https://github.com/ldez))
|
||||||
|
- **[server]** Handle respondingTimeout and better shutdown tests. ([#6115](https://github.com/containous/traefik/pull/6115) by [juliens](https://github.com/juliens))
|
||||||
|
- **[server]** Don't set user-agent to Go-http-client/1.1 ([#6030](https://github.com/containous/traefik/pull/6030) by [sh7dm](https://github.com/sh7dm))
|
||||||
|
- **[tracing]** fix: Malformed x-b3-traceid Header ([#6079](https://github.com/containous/traefik/pull/6079) by [ldez](https://github.com/ldez))
|
||||||
|
- **[webui]** fix: dashboard redirect loop ([#6078](https://github.com/containous/traefik/pull/6078) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme]** Use consistent name in ACME documentation ([#6019](https://github.com/containous/traefik/pull/6019) by [ldez](https://github.com/ldez))
|
||||||
|
- **[api,k8s/crd]** Add a documentation example for dashboard and api for kubernetes CRD ([#6022](https://github.com/containous/traefik/pull/6022) by [dduportal](https://github.com/dduportal))
|
||||||
|
- **[cli]** Fix examples for the use of websecure via CLI ([#6116](https://github.com/containous/traefik/pull/6116) by [tiagoboeing](https://github.com/tiagoboeing))
|
||||||
|
- **[k8s,k8s/crd]** Improve documentation about Kubernetes IngressRoute ([#6058](https://github.com/containous/traefik/pull/6058) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||||
|
- **[middleware]** Improve sourceRange explanation for ipWhiteList ([#6070](https://github.com/containous/traefik/pull/6070) by [der-domi](https://github.com/der-domi))
|
||||||
|
|
||||||
|
## [v2.1.1](https://github.com/containous/traefik/tree/v2.1.1) (2019-12-12)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v2.1.0...v2.1.1)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[logs,middleware,metrics]** CloseNotifier: return pointer instead of value ([#6010](https://github.com/containous/traefik/pull/6010) by [mpl](https://github.com/mpl))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- Add Migration Guide for Traefik v2.1 ([#6017](https://github.com/containous/traefik/pull/6017) by [SantoDE](https://github.com/SantoDE))
|
||||||
|
|
||||||
## [v2.1.0](https://github.com/containous/traefik/tree/v2.1.0) (2019-12-10)
|
## [v2.1.0](https://github.com/containous/traefik/tree/v2.1.0) (2019-12-10)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0)
|
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0)
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
|
|
||||||
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
|
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
|
||||||
|
|
||||||
serverEntryPointsTCP, err := server.NewTCPEntryPoints(*staticConfiguration)
|
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ Requirements:
|
||||||
|
|
||||||
- `go` v1.13+
|
- `go` v1.13+
|
||||||
- environment variable `GO111MODULE=on`
|
- environment variable `GO111MODULE=on`
|
||||||
- go-bindata `GO111MODULE=off go get -u github.com/containous/go-bindata/...`
|
- [go-bindata](https://github.com/containous/go-bindata) `GO111MODULE=off go get -u github.com/containous/go-bindata/...`
|
||||||
|
|
||||||
!!! tip "Source Directory"
|
!!! tip "Source Directory"
|
||||||
|
|
||||||
|
@ -100,30 +100,32 @@ Requirements:
|
||||||
#### Build Traefik
|
#### Build Traefik
|
||||||
|
|
||||||
Once you've set up your go environment and cloned the source repository, you can build Traefik.
|
Once you've set up your go environment and cloned the source repository, you can build Traefik.
|
||||||
Beforehand, you need to get `go-bindata` (the first time) in order to be able to use the `go generate` command (which is part of the build process).
|
|
||||||
|
Beforehand, you need to get [go-bindata](https://github.com/containous/go-bindata) (the first time) in order to be able to use the `go generate` command (which is part of the build process).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/go/src/github.com/containous/traefik
|
cd ~/go/src/github.com/containous/traefik
|
||||||
|
|
||||||
# Get go-bindata. (Important: the ellipses are required.)
|
# Get go-bindata. (Important: the ellipses are required.)
|
||||||
GO111MODULE=off go get github.com/containous/go-bindata/...
|
GO111MODULE=off go get github.com/containous/go-bindata/...
|
||||||
|
```
|
||||||
|
|
||||||
# Let's build
|
```bash
|
||||||
|
# Generate UI static files
|
||||||
|
rm -rf static/ autogen/; make generate-webui
|
||||||
|
|
||||||
# generate
|
# required to merge non-code components into the final binary,
|
||||||
# (required to merge non-code components into the final binary, such as the web dashboard and the provider's templates)
|
# such as the web dashboard/UI
|
||||||
go generate
|
go generate
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
# Standard go build
|
# Standard go build
|
||||||
go build ./cmd/traefik
|
go build ./cmd/traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
You will find the Traefik executable (`traefik`) in the `~/go/src/github.com/containous/traefik` directory.
|
You will find the Traefik executable (`traefik`) in the `~/go/src/github.com/containous/traefik` directory.
|
||||||
|
|
||||||
### Updating the templates
|
|
||||||
|
|
||||||
If you happen to update the provider's templates (located in `/templates`), you must run `go generate` to update the `autogen` package.
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
### Method 1: `Docker` and `make`
|
### Method 1: `Docker` and `make`
|
||||||
|
|
|
@ -59,10 +59,10 @@ Please check the [configuration examples below](#configuration-examples) for mor
|
||||||
[entryPoints.web-secure]
|
[entryPoints.web-secure]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
|
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
email = "your-email@your-domain.org"
|
email = "your-email@your-domain.org"
|
||||||
storage = "acme.json"
|
storage = "acme.json"
|
||||||
[certificatesResolvers.sample.acme.httpChallenge]
|
[certificatesResolvers.le.acme.httpChallenge]
|
||||||
# used during the challenge
|
# used during the challenge
|
||||||
entryPoint = "web"
|
entryPoint = "web"
|
||||||
```
|
```
|
||||||
|
@ -89,10 +89,10 @@ Please check the [configuration examples below](#configuration-examples) for mor
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.websecure.address=:443
|
--entryPoints.websecure.address=:443
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.email=your-email@your-domain.org
|
--certificatesResolvers.le.acme.email=your-email@your-domain.org
|
||||||
--certificatesResolvers.sample.acme.storage=acme.json
|
--certificatesResolvers.le.acme.storage=acme.json
|
||||||
# used during the challenge
|
# used during the challenge
|
||||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
--certificatesResolvers.le.acme.httpChallenge.entryPoint=web
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! important "Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must [reference](../routing/routers/index.md#certresolver) it."
|
!!! important "Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must [reference](../routing/routers/index.md#certresolver) it."
|
||||||
|
@ -164,9 +164,9 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
|
||||||
??? example "Configuring the `tlsChallenge`"
|
??? example "Configuring the `tlsChallenge`"
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
# ...
|
# ...
|
||||||
[certificatesResolvers.sample.acme.tlsChallenge]
|
[certificatesResolvers.le.acme.tlsChallenge]
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
|
@ -179,7 +179,7 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.tlsChallenge=true
|
--certificatesResolvers.le.acme.tlsChallenge=true
|
||||||
```
|
```
|
||||||
|
|
||||||
### `httpChallenge`
|
### `httpChallenge`
|
||||||
|
@ -187,7 +187,7 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
|
||||||
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning an HTTP resource under a well-known URI.
|
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning an HTTP resource under a well-known URI.
|
||||||
|
|
||||||
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
||||||
when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
when using the `HTTP-01` challenge, `certificatesResolvers.le.acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
||||||
|
|
||||||
??? example "Using an EntryPoint Called http for the `httpChallenge`"
|
??? example "Using an EntryPoint Called http for the `httpChallenge`"
|
||||||
|
|
||||||
|
@ -199,9 +199,9 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall
|
||||||
[entryPoints.web-secure]
|
[entryPoints.web-secure]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
|
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
# ...
|
# ...
|
||||||
[certificatesResolvers.sample.acme.httpChallenge]
|
[certificatesResolvers.le.acme.httpChallenge]
|
||||||
entryPoint = "web"
|
entryPoint = "web"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.websecure.address=:443
|
--entryPoints.websecure.address=:443
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
--certificatesResolvers.le.acme.httpChallenge.entryPoint=web
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
|
@ -238,9 +238,9 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni
|
||||||
??? example "Configuring a `dnsChallenge` with the DigitalOcean Provider"
|
??? example "Configuring a `dnsChallenge` with the DigitalOcean Provider"
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
# ...
|
# ...
|
||||||
[certificatesResolvers.sample.acme.dnsChallenge]
|
[certificatesResolvers.le.acme.dnsChallenge]
|
||||||
provider = "digitalocean"
|
provider = "digitalocean"
|
||||||
delayBeforeCheck = 0
|
delayBeforeCheck = 0
|
||||||
# ...
|
# ...
|
||||||
|
@ -259,8 +259,8 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.provider=digitalocean
|
--certificatesResolvers.le.acme.dnsChallenge.provider=digitalocean
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.delayBeforeCheck=0
|
--certificatesResolvers.le.acme.dnsChallenge.delayBeforeCheck=0
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -357,9 +357,9 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
||||||
Use custom DNS servers to resolve the FQDN authority.
|
Use custom DNS servers to resolve the FQDN authority.
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
# ...
|
# ...
|
||||||
[certificatesResolvers.sample.acme.dnsChallenge]
|
[certificatesResolvers.le.acme.dnsChallenge]
|
||||||
# ...
|
# ...
|
||||||
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
|
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
|
||||||
```
|
```
|
||||||
|
@ -378,7 +378,7 @@ certificatesResolvers:
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.resolvers:=1.1.1.1:53,8.8.8.8:53
|
--certificatesResolvers.le.acme.dnsChallenge.resolvers:=1.1.1.1:53,8.8.8.8:53
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Wildcard Domains
|
#### Wildcard Domains
|
||||||
|
@ -393,7 +393,7 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
||||||
??? example "Using the Let's Encrypt staging server"
|
??? example "Using the Let's Encrypt staging server"
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
# ...
|
# ...
|
||||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
# ...
|
# ...
|
||||||
|
@ -410,7 +410,7 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
--certificatesResolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -419,7 +419,7 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
||||||
The `storage` option sets the location where your ACME certificates are saved to.
|
The `storage` option sets the location where your ACME certificates are saved to.
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[certificatesResolvers.sample.acme]
|
[certificatesResolvers.le.acme]
|
||||||
# ...
|
# ...
|
||||||
storage = "acme.json"
|
storage = "acme.json"
|
||||||
# ...
|
# ...
|
||||||
|
@ -436,7 +436,7 @@ certificatesResolvers:
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.storage=acme.json
|
--certificatesResolvers.le.acme.storage=acme.json
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ labels:
|
||||||
deploy:
|
deploy:
|
||||||
labels:
|
labels:
|
||||||
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||||
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
|
||||||
- traefik.http.routers.blog.tls=true
|
- traefik.http.routers.blog.tls=true
|
||||||
- traefik.http.routers.blog.tls.certresolver=le
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
```yaml tab="Kubernetes"
|
||||||
|
|
|
@ -12,9 +12,9 @@ labels:
|
||||||
deploy:
|
deploy:
|
||||||
labels:
|
labels:
|
||||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
|
||||||
- traefik.http.routers.blog.tls=true
|
- traefik.http.routers.blog.tls=true
|
||||||
- traefik.http.routers.blog.tls.certresolver=le
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
```yaml tab="Kubernetes"
|
||||||
|
|
|
@ -35,13 +35,13 @@
|
||||||
#
|
#
|
||||||
# Optional (but recommended)
|
# Optional (but recommended)
|
||||||
#
|
#
|
||||||
[certificatesResolvers.sample.acme.tlsChallenge]
|
[certificatesResolvers.le.acme.tlsChallenge]
|
||||||
|
|
||||||
# Use a HTTP-01 ACME challenge.
|
# Use a HTTP-01 ACME challenge.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# [certificatesResolvers.sample.acme.httpChallenge]
|
# [certificatesResolvers.le.acme.httpChallenge]
|
||||||
|
|
||||||
# EntryPoint to use for the HTTP-01 challenges.
|
# EntryPoint to use for the HTTP-01 challenges.
|
||||||
#
|
#
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# [certificatesResolvers.sample.acme.dnsChallenge]
|
# [certificatesResolvers.le.acme.dnsChallenge]
|
||||||
|
|
||||||
# DNS provider used.
|
# DNS provider used.
|
||||||
#
|
#
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.email=test@traefik.io
|
--certificatesResolvers.le.acme.email=test@traefik.io
|
||||||
|
|
||||||
# File or key used for certificates storage.
|
# File or key used for certificates storage.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.storage=acme.json
|
--certificatesResolvers.le.acme.storage=acme.json
|
||||||
|
|
||||||
# CA server to use.
|
# CA server to use.
|
||||||
# Uncomment the line to use Let's Encrypt's staging server,
|
# Uncomment the line to use Let's Encrypt's staging server,
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
# Optional
|
# Optional
|
||||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
--certificatesResolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
|
||||||
# KeyType to use.
|
# KeyType to use.
|
||||||
#
|
#
|
||||||
|
@ -28,38 +28,38 @@
|
||||||
#
|
#
|
||||||
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
|
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.keyType=RSA4096
|
--certificatesResolvers.le.acme.keyType=RSA4096
|
||||||
|
|
||||||
# Use a TLS-ALPN-01 ACME challenge.
|
# Use a TLS-ALPN-01 ACME challenge.
|
||||||
#
|
#
|
||||||
# Optional (but recommended)
|
# Optional (but recommended)
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.tlsChallenge=true
|
--certificatesResolvers.le.acme.tlsChallenge=true
|
||||||
|
|
||||||
# Use a HTTP-01 ACME challenge.
|
# Use a HTTP-01 ACME challenge.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.httpChallenge=true
|
--certificatesResolvers.le.acme.httpChallenge=true
|
||||||
|
|
||||||
# EntryPoint to use for the HTTP-01 challenges.
|
# EntryPoint to use for the HTTP-01 challenges.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
--certificatesResolvers.le.acme.httpChallenge.entryPoint=web
|
||||||
|
|
||||||
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
||||||
# Note: mandatory for wildcard certificate generation.
|
# Note: mandatory for wildcard certificate generation.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge=true
|
--certificatesResolvers.le.acme.dnsChallenge=true
|
||||||
|
|
||||||
# DNS provider used.
|
# DNS provider used.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.provider=digitalocean
|
--certificatesResolvers.le.acme.dnsChallenge.provider=digitalocean
|
||||||
|
|
||||||
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
|
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
|
||||||
|
@ -68,14 +68,14 @@
|
||||||
# Optional
|
# Optional
|
||||||
# Default: 0
|
# Default: 0
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.delayBeforeCheck=0
|
--certificatesResolvers.le.acme.dnsChallenge.delayBeforeCheck=0
|
||||||
|
|
||||||
# Use following DNS servers to resolve the FQDN authority.
|
# Use following DNS servers to resolve the FQDN authority.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: empty
|
# Default: empty
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
|
--certificatesResolvers.le.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
|
||||||
|
|
||||||
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
|
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
|
||||||
#
|
#
|
||||||
|
@ -85,4 +85,4 @@
|
||||||
# Optional
|
# Optional
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.disablePropagationCheck=true
|
--certificatesResolvers.le.acme.dnsChallenge.disablePropagationCheck=true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
certificatesResolvers:
|
certificatesResolvers:
|
||||||
sample:
|
le:
|
||||||
# Enable ACME (Let's Encrypt): automatic SSL.
|
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||||
acme:
|
acme:
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ tls:
|
||||||
|
|
||||||
In the above example, we've used the [file provider](../providers/file.md) to handle these definitions.
|
In the above example, we've used the [file provider](../providers/file.md) to handle these definitions.
|
||||||
It is the only available method to configure the certificates (as well as the options and the stores).
|
It is the only available method to configure the certificates (as well as the options and the stores).
|
||||||
However, in [Kubernetes](../providers/kubernetes-crd.md), the certificates can and must be provided by [secrets](../routing/providers/kubernetes-crd.md#tls).
|
However, in [Kubernetes](../providers/kubernetes-crd.md), the certificates can and must be provided by [secrets](https://kubernetes.io/docs/concepts/configuration/secret/).
|
||||||
|
|
||||||
## Certificates Stores
|
## Certificates Stores
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ http:
|
||||||
|
|
||||||
### `sourceRange`
|
### `sourceRange`
|
||||||
|
|
||||||
The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs).
|
The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation).
|
||||||
|
|
||||||
### `ipStrategy`
|
### `ipStrategy`
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ Then any router can refer to an instance of the wanted middleware.
|
||||||
|
|
||||||
```yaml tab="K8s IngressRoute"
|
```yaml tab="K8s IngressRoute"
|
||||||
# The definitions below require the definitions for the Middleware and IngressRoute kinds.
|
# The definitions below require the definitions for the Middleware and IngressRoute kinds.
|
||||||
# https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition
|
# https://docs.traefik.io/v2.1/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -278,7 +278,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o
|
||||||
|
|
||||||
```yaml tab="K8s IngressRoute"
|
```yaml tab="K8s IngressRoute"
|
||||||
# The definitions below require the definitions for the TLSOption and IngressRoute kinds.
|
# The definitions below require the definitions for the TLSOption and IngressRoute kinds.
|
||||||
# https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition
|
# https://docs.traefik.io/v2.1/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: TLSOption
|
kind: TLSOption
|
||||||
metadata:
|
metadata:
|
||||||
|
|
99
docs/content/migration/v2.md
Normal file
99
docs/content/migration/v2.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
# Migration: Steps needed between the versions
|
||||||
|
|
||||||
|
## v2.0 to v2.1
|
||||||
|
|
||||||
|
In v2.1, a new CRD called `TraefikService` was added. While updating an installation to v2.1,
|
||||||
|
it is required to apply that CRD before as well as enhance the existing `ClusterRole` definition to allow Traefik to use that CRD.
|
||||||
|
|
||||||
|
To add that CRD and enhance the permissions, following definitions need to be applied to the cluster.
|
||||||
|
|
||||||
|
```yaml tab="TraefikService"
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: traefikservices.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: TraefikService
|
||||||
|
plural: traefikservices
|
||||||
|
singular: traefikservice
|
||||||
|
scope: Namespaced
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="ClusterRole"
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- endpoints
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- ingresses/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- middlewares
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- ingressroutes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- ingressroutetcps
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- tlsoptions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- traefikservices
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
```
|
||||||
|
|
||||||
|
After having both resources applied, Traefik will work properly.
|
|
@ -19,6 +19,28 @@ deploy:
|
||||||
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes CRD"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: traefik-dashboard
|
||||||
|
spec:
|
||||||
|
routes:
|
||||||
|
- match: Host(`traefik.domain.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: api@internal
|
||||||
|
kind: TraefikService
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: auth
|
||||||
|
spec:
|
||||||
|
basicAuth:
|
||||||
|
secret: secretName # Kubernetes secret named "secretName"
|
||||||
|
```
|
||||||
|
|
||||||
```yaml tab="Consul Catalog"
|
```yaml tab="Consul Catalog"
|
||||||
# Dynamic Configuration
|
# Dynamic Configuration
|
||||||
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)"
|
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)"
|
||||||
|
|
|
@ -86,7 +86,7 @@ and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||||
## Routing Configuration
|
## Routing Configuration
|
||||||
|
|
||||||
When using Docker as a [provider](https://docs.traefik.io/providers/overview/),
|
When using Docker as a [provider](https://docs.traefik.io/providers/overview/),
|
||||||
Trafik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
||||||
|
|
||||||
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,43 @@ Traefik used to support Kubernetes only through the [Kubernetes Ingress provider
|
||||||
However, as the community expressed the need to benefit from Traefik features without resorting to (lots of) annotations,
|
However, as the community expressed the need to benefit from Traefik features without resorting to (lots of) annotations,
|
||||||
we ended up writing a [Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) (alias CRD in the following) for an IngressRoute type, defined below, in order to provide a better way to configure access to a Kubernetes cluster.
|
we ended up writing a [Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) (alias CRD in the following) for an IngressRoute type, defined below, in order to provide a better way to configure access to a Kubernetes cluster.
|
||||||
|
|
||||||
|
## Configuration Requirements
|
||||||
|
|
||||||
|
!!! tip "All Steps for a Successful Deployment"
|
||||||
|
|
||||||
|
* Add/update **all** the Traefik resources [definitions](../reference/dynamic-configuration/kubernetes-crd.md#definitions)
|
||||||
|
* Add/update the [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) for the Traefik custom resources
|
||||||
|
* Use [Helm Chart](../getting-started/install-traefik.md#use-the-helm-chart) or use a custom Traefik Deployment
|
||||||
|
* Enable the kubernetesCRD provider
|
||||||
|
* Apply the needed kubernetesCRD provider [configuration](#provider-configuration)
|
||||||
|
* Add all needed traefik custom [resources](../reference/dynamic-configuration/kubernetes-crd.md#resources)
|
||||||
|
|
||||||
|
??? example "Initializing Resource Definition and RBAC"
|
||||||
|
|
||||||
|
```yaml tab="Traefik Resource Definition"
|
||||||
|
# All resources definition must be declared
|
||||||
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="RBAC for Traefik CRD"
|
||||||
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml"
|
||||||
|
```
|
||||||
|
|
||||||
## Resource Configuration
|
## Resource Configuration
|
||||||
|
|
||||||
See the dedicated section in [routing](../routing/providers/kubernetes-crd.md).
|
When using KubernetesCRD as a provider,
|
||||||
|
Traefik uses [Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) to retrieve its routing configuration.
|
||||||
|
Traefik Custom Resource Definitions are a Kubernetes implementation of the Traefik concepts. The main particularities are:
|
||||||
|
|
||||||
|
* The usage of `name` **and** `namespace` to refer to another Kubernetes resource.
|
||||||
|
* The usage of [secret](https://kubernetes.io/docs/concepts/configuration/secret/) for sensible data like:
|
||||||
|
* TLS certificate.
|
||||||
|
* Authentication data.
|
||||||
|
* The structure of the configuration.
|
||||||
|
* The obligation to declare all the [definitions](../reference/dynamic-configuration/kubernetes-crd.md#definitions).
|
||||||
|
|
||||||
|
The Traefik CRD are building blocks which you can assemble according to your needs.
|
||||||
|
See the list of CRDs in the dedicated [routing section](../routing/providers/kubernetes-crd.md).
|
||||||
|
|
||||||
## LetsEncrypt Support with the Custom Resource Definition Provider
|
## LetsEncrypt Support with the Custom Resource Definition Provider
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: ingressroutes.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: IngressRoute
|
||||||
|
plural: ingressroutes
|
||||||
|
singular: ingressroute
|
||||||
|
scope: Namespaced
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: middlewares.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: Middleware
|
||||||
|
plural: middlewares
|
||||||
|
singular: middleware
|
||||||
|
scope: Namespaced
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: ingressroutetcps.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
plural: ingressroutetcps
|
||||||
|
singular: ingressroutetcp
|
||||||
|
scope: Namespaced
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: tlsoptions.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: TLSOption
|
||||||
|
plural: tlsoptions
|
||||||
|
singular: tlsoption
|
||||||
|
scope: Namespaced
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: traefikservices.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: TraefikService
|
||||||
|
plural: traefikservices
|
||||||
|
singular: traefikservice
|
||||||
|
scope: Namespaced
|
|
@ -0,0 +1,13 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: ingressroutetcps.traefik.containo.us
|
||||||
|
|
||||||
|
spec:
|
||||||
|
group: traefik.containo.us
|
||||||
|
version: v1alpha1
|
||||||
|
names:
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
plural: ingressroutetcps
|
||||||
|
singular: ingressroutetcp
|
||||||
|
scope: Namespaced
|
|
@ -0,0 +1,57 @@
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- endpoints
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- ingresses/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- middlewares
|
||||||
|
- ingressroutes
|
||||||
|
- traefikservices
|
||||||
|
- ingressroutetcps
|
||||||
|
- tlsoptions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: default
|
|
@ -0,0 +1,157 @@
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TraefikService
|
||||||
|
metadata:
|
||||||
|
name: wrr2
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
weighted:
|
||||||
|
services:
|
||||||
|
- name: s1
|
||||||
|
weight: 1
|
||||||
|
port: 80
|
||||||
|
# Optional, as it is the default value
|
||||||
|
kind: Service
|
||||||
|
- name: s3
|
||||||
|
weight: 1
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TraefikService
|
||||||
|
metadata:
|
||||||
|
name: wrr1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
weighted:
|
||||||
|
services:
|
||||||
|
- name: wrr2
|
||||||
|
kind: TraefikService
|
||||||
|
weight: 1
|
||||||
|
- name: s3
|
||||||
|
weight: 1
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TraefikService
|
||||||
|
metadata:
|
||||||
|
name: mirror1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
mirroring:
|
||||||
|
name: s1
|
||||||
|
port: 80
|
||||||
|
mirrors:
|
||||||
|
- name: s3
|
||||||
|
percent: 20
|
||||||
|
port: 80
|
||||||
|
- name: mirror2
|
||||||
|
kind: TraefikService
|
||||||
|
percent: 20
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TraefikService
|
||||||
|
metadata:
|
||||||
|
name: mirror2
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
mirroring:
|
||||||
|
name: wrr2
|
||||||
|
kind: TraefikService
|
||||||
|
mirrors:
|
||||||
|
- name: s2
|
||||||
|
# Optional, as it is the default value
|
||||||
|
kind: Service
|
||||||
|
percent: 20
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: ingressroute
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
- web-secure
|
||||||
|
routes:
|
||||||
|
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 12
|
||||||
|
# defining several services is possible and allowed, but for now the servers of
|
||||||
|
# all the services (for a given route) get merged altogether under the same
|
||||||
|
# load-balancing strategy.
|
||||||
|
services:
|
||||||
|
- name: s1
|
||||||
|
port: 80
|
||||||
|
healthCheck:
|
||||||
|
path: /health
|
||||||
|
host: baz.com
|
||||||
|
intervalSeconds: 7
|
||||||
|
timeoutSeconds: 60
|
||||||
|
# strategy defines the load balancing strategy between the servers. It defaults
|
||||||
|
# to Round Robin, and for now only Round Robin is supported anyway.
|
||||||
|
strategy: RoundRobin
|
||||||
|
- name: s2
|
||||||
|
port: 433
|
||||||
|
healthCheck:
|
||||||
|
path: /health
|
||||||
|
host: baz.com
|
||||||
|
intervalSeconds: 7
|
||||||
|
timeoutSeconds: 60
|
||||||
|
- match: PathPrefix(`/misc`)
|
||||||
|
services:
|
||||||
|
- name: s3
|
||||||
|
port: 80
|
||||||
|
middlewares:
|
||||||
|
- name: stripprefix
|
||||||
|
- name: addprefix
|
||||||
|
- match: PathPrefix(`/misc`)
|
||||||
|
services:
|
||||||
|
- name: s3
|
||||||
|
# Optional, as it is the default value
|
||||||
|
kind: Service
|
||||||
|
port: 8443
|
||||||
|
# scheme allow to override the scheme for the service. (ex: https or h2c)
|
||||||
|
scheme: https
|
||||||
|
- match: PathPrefix(`/lb`)
|
||||||
|
services:
|
||||||
|
- name: wrr1
|
||||||
|
kind: TraefikService
|
||||||
|
- match: PathPrefix(`/mirrored`)
|
||||||
|
services:
|
||||||
|
- name: mirror1
|
||||||
|
kind: TraefikService
|
||||||
|
# use an empty tls object for TLS with Let's Encrypt
|
||||||
|
tls:
|
||||||
|
secretName: supersecret
|
||||||
|
options:
|
||||||
|
name: myTLSOption
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: ingressroutetcp.crd
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- footcp
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`bar.com`)
|
||||||
|
services:
|
||||||
|
- name: whoamitcp
|
||||||
|
port: 8080
|
||||||
|
tls:
|
||||||
|
secretName: foosecret
|
||||||
|
passthrough: false
|
||||||
|
options:
|
||||||
|
name: myTLSOption
|
||||||
|
namespace: default
|
|
@ -3,6 +3,20 @@
|
||||||
Dynamic configuration with Kubernetes Custom Resource
|
Dynamic configuration with Kubernetes Custom Resource
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd.yml"
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-resource.yml"
|
||||||
|
```
|
||||||
|
|
||||||
|
## RBAC
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml"
|
||||||
```
|
```
|
||||||
|
|
|
@ -41,7 +41,7 @@ They define the port which will receive the requests (whether HTTP or TCP).
|
||||||
[entryPoints.web]
|
[entryPoints.web]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web-secure]
|
[entryPoints.websecure]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -51,18 +51,18 @@ They define the port which will receive the requests (whether HTTP or TCP).
|
||||||
web:
|
web:
|
||||||
address: ":80"
|
address: ":80"
|
||||||
|
|
||||||
web-secure:
|
websecure:
|
||||||
address: ":443"
|
address: ":443"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web-secure.address=:443
|
--entryPoints.websecure.address=:443
|
||||||
```
|
```
|
||||||
|
|
||||||
- Two entrypoints are defined: one called `web`, and the other called `web-secure`.
|
- Two entrypoints are defined: one called `web`, and the other called `websecure`.
|
||||||
- `web` listens on port `80`, and `web-secure` on port `443`.
|
- `web` listens on port `80`, and `websecure` on port `443`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|
|
@ -3,79 +3,325 @@
|
||||||
The Kubernetes Ingress Controller, The Custom Resource Way.
|
The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
## Resource Configuration
|
## Configuration Examples
|
||||||
|
|
||||||
If you're in a hurry, maybe you'd rather go through the [dynamic configuration](../../reference/dynamic-configuration/kubernetes-crd.md) reference.
|
??? example "Configuring KubernetesCRD and Deploying/Exposing Services"
|
||||||
|
|
||||||
### Traefik IngressRoute definition
|
```yaml tab="Resource Definition"
|
||||||
|
# All resources definition must be declared
|
||||||
```yaml
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
|
||||||
--8<-- "content/routing/providers/crd_ingress_route.yml"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
That `IngressRoute` kind can then be used to define an `IngressRoute` object, such as in:
|
```yaml tab="RBAC"
|
||||||
|
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Traefik"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
labels:
|
||||||
|
app: traefik
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: traefik
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: traefik
|
||||||
|
spec:
|
||||||
|
serviceAccountName: traefik-ingress-controller
|
||||||
|
containers:
|
||||||
|
- name: traefik
|
||||||
|
image: traefik:v2.1
|
||||||
|
args:
|
||||||
|
- --log.level=DEBUG
|
||||||
|
- --api
|
||||||
|
- --api.insecure
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --providers.kubernetescrd
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
containerPort: 80
|
||||||
|
- name: admin
|
||||||
|
containerPort: 8080
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
selector:
|
||||||
|
app: traefik
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
name: web
|
||||||
|
targetPort: 80
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
|
name: admin
|
||||||
|
targetPort: 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="IngressRoute"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: myingressroute
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- match: Host(`foo`) && PathPrefix(`/bar`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Whoami"
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app: containous
|
||||||
|
name: whoami
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: containous
|
||||||
|
task: whoami
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: containous
|
||||||
|
task: whoami
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: containouswhoami
|
||||||
|
image: containous/whoami
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routing Configuration
|
||||||
|
|
||||||
|
### Custom Resource Definition (CRD)
|
||||||
|
|
||||||
|
* You can find an exhaustive list, generated from Traefik's source code, of the custom resources and their attributes in [the reference page](../../reference/dynamic-configuration/kubernetes-crd.md).
|
||||||
|
* Validate that [the prerequisites](../../providers/kubernetes-crd.md#configuration-requirements) are fulfilled before using the Traefik custom resources.
|
||||||
|
* Traefik CRDs are building blocks that you can assemble according to your needs.
|
||||||
|
|
||||||
|
You can find an excerpt of the available custom resources in the table below:
|
||||||
|
|
||||||
|
| Kind | Purpose | Concept Behind |
|
||||||
|
|------------------------------------------|---------------------------------------------------------------|----------------------------------------------------------------|
|
||||||
|
| [IngressRoute](#kind-ingressroute) | HTTP Routing | [HTTP router](../routers/index.md#configuring-http-routers) |
|
||||||
|
| [Middleware](#kind-middleware) | Tweaks the HTTP requests before they are sent to your service | [HTTP Middlewares](../../middlewares/overview.md) |
|
||||||
|
| [TraefikService](#kind-traefikservice) | Abstraction for HTTP loadbalancing/mirroring | [HTTP service](../services/index.md#configuring-http-services) |
|
||||||
|
| [IngressRouteTCP](#kind-ingressroutetcp) | TCP Routing | [TCP router](../routers/index.md#configuring-tcp-routers) |
|
||||||
|
| [TLSOptions](#kind-tlsoption) | Allows to configure some parameters of the TLS connection | [TLSOptions](../../https/tls.md#tls-options) |
|
||||||
|
|
||||||
|
### Kind: `IngressRoute`
|
||||||
|
|
||||||
|
`IngressRoute` is the CRD implementation of a [Traefik HTTP router](../routers/index.md#configuring-http-routers).
|
||||||
|
|
||||||
|
Register the `IngressRoute` kind in the Kubernetes cluster before creating `IngressRoute` objects.
|
||||||
|
|
||||||
|
!!! info "IngressRoute Attributes"
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
metadata:
|
||||||
name: ingressroutefoo
|
name: foo
|
||||||
|
namespace: bar
|
||||||
|
spec:
|
||||||
|
entryPoints: # [1]
|
||||||
|
- foo
|
||||||
|
routes: # [2]
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`test.domain.com`) # [3]
|
||||||
|
priority: 10 # [4]
|
||||||
|
middlewares: # [5]
|
||||||
|
- name: middleware1 # [6]
|
||||||
|
namespace: default # [7]
|
||||||
|
services: # [8]
|
||||||
|
- kind: Service
|
||||||
|
name: foo
|
||||||
|
namespace: default
|
||||||
|
passHostHeader: true
|
||||||
|
port: 80
|
||||||
|
responseForwarding:
|
||||||
|
flushInterval: 1ms
|
||||||
|
scheme: https
|
||||||
|
sticky:
|
||||||
|
cookie:
|
||||||
|
httpOnly: true
|
||||||
|
name: cookie
|
||||||
|
secure: true
|
||||||
|
strategy: RoundRobin
|
||||||
|
weight: 10
|
||||||
|
tls: # [9]
|
||||||
|
secretName: supersecret # [10]
|
||||||
|
options: # [11]
|
||||||
|
name: opt # [12]
|
||||||
|
namespace: default # [13]
|
||||||
|
certResolver: foo # [14]
|
||||||
|
domains: # [15]
|
||||||
|
- main: foo.com # [16]
|
||||||
|
sans: # [17]
|
||||||
|
- a.foo.com
|
||||||
|
- b.foo.com
|
||||||
|
```
|
||||||
|
|
||||||
|
| Ref | Attribute | Purpose |
|
||||||
|
|------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) name |
|
||||||
|
| [2] | `routes` | List of route |
|
||||||
|
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
|
||||||
|
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
|
||||||
|
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
|
||||||
|
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
|
||||||
|
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
|
||||||
|
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
|
||||||
|
| [9] | `tls` | Defines [TLS](../routers/index.md#tls) 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.cetResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
|
||||||
|
| [15] | `tls.domains` | List of [domains](../routers/index.md#domains) |
|
||||||
|
| [16] | `domains[n].main` | Defines the main domain name |
|
||||||
|
| [17] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||||
|
|
||||||
|
??? example "Declaring an IngressRoute"
|
||||||
|
|
||||||
|
```yaml tab="IngressRoute"
|
||||||
|
# All resources definition must be declared
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: testName
|
||||||
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- web
|
||||||
routes:
|
routes:
|
||||||
# Match is the rule corresponding to an underlying router.
|
- kind: Rule
|
||||||
# Later on, match could be the simple form of a path prefix, e.g. just "/bar",
|
match: Host(`test.domain.com`)
|
||||||
# but for now we only support a traefik style matching rule.
|
middlewares:
|
||||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
- name: middleware1
|
||||||
# kind could eventually be one of "Rule", "Path", "Host", "Method", "Header",
|
namespace: default
|
||||||
# "Parameter", etc, to support simpler forms of rule matching, but for now we
|
priority: 10
|
||||||
# only support "Rule".
|
|
||||||
kind: Rule
|
|
||||||
# (optional) Priority disambiguates rules of the same length, for route matching.
|
|
||||||
priority: 12
|
|
||||||
services:
|
services:
|
||||||
- name: whoami
|
- kind: Service
|
||||||
port: 80
|
name: foo
|
||||||
# (default 1) A weight used by the weighted round-robin strategy (WRR).
|
namespace: default
|
||||||
weight: 1
|
|
||||||
# (default true) PassHostHeader controls whether to leave the request's Host
|
|
||||||
# Header as it was before it reached the proxy, or whether to let the proxy set it
|
|
||||||
# to the destination (backend) host.
|
|
||||||
passHostHeader: true
|
passHostHeader: true
|
||||||
|
port: 80
|
||||||
responseForwarding:
|
responseForwarding:
|
||||||
# (default 100ms) Interval between flushes of the buffered response body to the client.
|
flushInterval: 1ms
|
||||||
flushInterval: 100ms
|
scheme: https
|
||||||
|
sticky:
|
||||||
|
cookie:
|
||||||
|
httpOnly: true
|
||||||
|
name: cookie
|
||||||
|
secure: true
|
||||||
|
strategy: RoundRobin
|
||||||
|
weight: 10
|
||||||
|
tls:
|
||||||
|
certResolver: foo
|
||||||
|
domains:
|
||||||
|
- main: foo.com
|
||||||
|
sans:
|
||||||
|
- a.foo.com
|
||||||
|
- b.foo.com
|
||||||
|
options:
|
||||||
|
name: opt
|
||||||
|
namespace: default
|
||||||
|
secretName: supersecret
|
||||||
|
```
|
||||||
|
|
||||||
---
|
```yaml tab="Middlewares"
|
||||||
|
# All resources definition must be declared
|
||||||
|
# Prefixing with /foo
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: IngressRouteTCP
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
name: ingressroutetcpfoo.crd
|
name: middleware1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
addPrefix:
|
||||||
|
prefix: /foo
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="TLSOption"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TLSOption
|
||||||
|
metadata:
|
||||||
|
name: opt
|
||||||
|
namespace: default
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
entryPoints:
|
minVersion: VersionTLS12
|
||||||
- footcp
|
|
||||||
routes:
|
|
||||||
# Match is the rule corresponding to an underlying router.
|
|
||||||
- match: HostSNI(`*`)
|
|
||||||
services:
|
|
||||||
- name: whoamitcp
|
|
||||||
port: 8080
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Middleware
|
```yaml tab="Secret"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: supersecret
|
||||||
|
|
||||||
Additionally, to allow for the use of middlewares in an `IngressRoute`, we defined the CRD below for the `Middleware` kind.
|
data:
|
||||||
|
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
```yaml
|
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
--8<-- "content/routing/providers/crd_middlewares.yml"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Once the `Middleware` kind has been registered with the Kubernetes cluster, it can then be used in `IngressRoute` definitions, such as:
|
### Kind: `Middleware`
|
||||||
|
|
||||||
```yaml
|
`Middleware` is the CRD implementation of a [Traefik middleware](../../middlewares/overview.md).
|
||||||
|
|
||||||
|
Register the `Middleware` kind in the Kubernetes cluster before creating `Middleware` objects or referencing middlewares in the [`IngressRoute`](#kind-ingressroute) objects.
|
||||||
|
|
||||||
|
??? "Declaring and Referencing a Middleware"
|
||||||
|
|
||||||
|
```yaml tab="Middleware"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -86,8 +332,9 @@ spec:
|
||||||
stripPrefix:
|
stripPrefix:
|
||||||
prefixes:
|
prefixes:
|
||||||
- /stripit
|
- /stripit
|
||||||
|
```
|
||||||
|
|
||||||
---
|
```yaml tab="IngressRoute"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -111,28 +358,116 @@ spec:
|
||||||
|
|
||||||
As Kubernetes also has its own notion of namespace, one should not confuse the kubernetes namespace of a resource
|
As Kubernetes also has its own notion of namespace, one should not confuse the kubernetes namespace of a resource
|
||||||
(in the reference to the middleware) with the [provider namespace](../../middlewares/overview.md#provider-namespace),
|
(in the reference to the middleware) with the [provider namespace](../../middlewares/overview.md#provider-namespace),
|
||||||
when the definition of the middleware is from another provider.
|
when the definition of the middleware comes from another provider.
|
||||||
In this context, specifying a namespace when referring to the resource does not make any sense, and will be ignored.
|
In this context, specifying a namespace when referring to the resource does not make any sense, and will be ignored.
|
||||||
|
|
||||||
More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md).
|
More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md).
|
||||||
|
|
||||||
### Services
|
### Kind: `TraefikService`
|
||||||
|
|
||||||
If one needs a setup more sophisticated than a load-balancer of servers (which is a Kubernetes Service kind behind the scenes),
|
`TraefikService` is the CRD implementation of a ["Traefik Service"](../services/index.md).
|
||||||
one can define and use additional service objects specific to Traefik, based on the `TraefikService` kind defined in the CRD below.
|
|
||||||
|
|
||||||
```yaml
|
Register the `TraefikService` kind in the Kubernetes cluster before creating `TraefikService` objects,
|
||||||
--8<-- "content/routing/providers/crd_traefikservice.yml"
|
referencing services in the [`IngressRoute`](#kind-ingressroute)/[`IngressRouteTCP`](#kind-ingressroutetcp) objects or recursively in others `TraefikService` objects.
|
||||||
|
|
||||||
|
!!! info "Disambiguate Traefik and Kubernetes Services "
|
||||||
|
|
||||||
|
As the field `name` can reference different types of objects, use the field `kind` to avoid any ambiguity.
|
||||||
|
|
||||||
|
The field `kind` allows the following values:
|
||||||
|
|
||||||
|
* `Service` (default value): to reference a [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/)
|
||||||
|
* `TraefikService`: to reference another [Traefik Service](../services/index.md)
|
||||||
|
|
||||||
|
`TraefikService` object allows to use any (valid) combinations of:
|
||||||
|
|
||||||
|
* servers [load balancing](#server-load-balancing).
|
||||||
|
* services [Weighted Round Robin](#weighted-round-robin) load balancing.
|
||||||
|
* services [mirroring](#mirroring).
|
||||||
|
|
||||||
|
|
||||||
|
#### Server Load Balancing
|
||||||
|
|
||||||
|
More information in the dedicated server [load balancing](../services/index.md#load-balancing) section.
|
||||||
|
|
||||||
|
??? "Declaring and Using Server Load Balancing"
|
||||||
|
|
||||||
|
```yaml tab="IngressRoute"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: ingressroutebar
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
routes:
|
||||||
|
- match: Host(`bar.com`) && PathPrefix(`/foo`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: svc1
|
||||||
|
namespace: default
|
||||||
|
- name: svc2
|
||||||
|
namespace: default
|
||||||
```
|
```
|
||||||
|
|
||||||
Once the `TraefikService` kind has been registered with the Kubernetes cluster, it can then be used in `IngressRoute` definitions
|
```yaml tab="K8s Service"
|
||||||
(as well as recursively in other Traefik Services), such as below.
|
apiVersion: v1
|
||||||
Note how the `name` field in the IngressRoute definition now refers to a TraefikService instead of a (Kubernetes) Service.
|
kind: Service
|
||||||
The reason this is allowed, and why a `name` can refer either to a TraefikService or a Service,
|
metadata:
|
||||||
is because the `kind` field is used to break the ambiguity. The allowed values for this field are `TraefikService`, or `Service`
|
name: svc1
|
||||||
(which is the default value).
|
namespace: default
|
||||||
|
|
||||||
```yaml
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc2
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Weighted Round Robin
|
||||||
|
|
||||||
|
More information in the dedicated [Weighted Round Robin](../services/index.md#weighted-round-robin-service) service load balancing section.
|
||||||
|
|
||||||
|
??? "Declaring and Using Weighted Round Robin"
|
||||||
|
|
||||||
|
```yaml tab="IngressRoute"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: ingressroutebar
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
routes:
|
||||||
|
- match: Host(`bar.com`) && PathPrefix(`/foo`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: wrr1
|
||||||
|
namespace: default
|
||||||
|
kind: TraefikService
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Weighted Round Robin"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -142,38 +477,91 @@ metadata:
|
||||||
spec:
|
spec:
|
||||||
weighted:
|
weighted:
|
||||||
services:
|
services:
|
||||||
- name: s2
|
- name: svc1
|
||||||
kind: Service
|
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
- name: s3
|
- name: wrr2
|
||||||
|
kind: TraefikService
|
||||||
|
weight: 1
|
||||||
|
- name: mirror1
|
||||||
|
kind: TraefikService
|
||||||
weight: 1
|
weight: 1
|
||||||
port: 80
|
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
metadata:
|
metadata:
|
||||||
name: mirror1
|
name: wrr2
|
||||||
namespace: default
|
namespace: default
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
mirroring:
|
weighted:
|
||||||
name: wrr1
|
services:
|
||||||
kind: TraefikService
|
- name: svc2
|
||||||
mirrors:
|
|
||||||
- name: s1
|
|
||||||
percent: 20
|
|
||||||
port: 80
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
- name: svc3
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="K8s Service"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app1
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc2
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app2
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc3
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app3
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mirroring
|
||||||
|
|
||||||
|
More information in the dedicated [mirroring](../services/index.md#mirroring-service) service section.
|
||||||
|
|
||||||
|
??? "Declaring and Using Mirroring"
|
||||||
|
|
||||||
|
```yaml tab="IngressRoute"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
metadata:
|
||||||
name: ingressroutebar
|
name: ingressroutebar
|
||||||
namespace: default
|
namespace: default
|
||||||
|
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- web
|
||||||
|
@ -186,26 +574,252 @@ spec:
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```yaml tab="Mirroring k8s Service"
|
||||||
|
# Mirroring from a k8s Service
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TraefikService
|
||||||
|
metadata:
|
||||||
|
name: mirror1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
mirroring:
|
||||||
|
name: svc1
|
||||||
|
port: 80
|
||||||
|
mirrors:
|
||||||
|
- name: svc2
|
||||||
|
port: 80
|
||||||
|
percent: 20
|
||||||
|
- name: svc3
|
||||||
|
kind: TraefikService
|
||||||
|
percent: 20
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Mirroring Traefik Service"
|
||||||
|
# Mirroring from a Traefik Service
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TraefikService
|
||||||
|
metadata:
|
||||||
|
name: mirror1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
mirroring:
|
||||||
|
name: wrr1
|
||||||
|
kind: TraefikService
|
||||||
|
mirrors:
|
||||||
|
- name: svc2
|
||||||
|
port: 80
|
||||||
|
percent: 20
|
||||||
|
- name: svc3
|
||||||
|
kind: TraefikService
|
||||||
|
percent: 20
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="K8s Service"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc2
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: containous
|
||||||
|
task: app2
|
||||||
|
```
|
||||||
|
|
||||||
!!! important "References and namespaces"
|
!!! important "References and namespaces"
|
||||||
|
|
||||||
If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the current resource.
|
If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the current resource.
|
||||||
|
|
||||||
Additionally, when the definition of the `TraefikService` is from another provider,
|
Additionally, when the definition of the `TraefikService` is from another provider,
|
||||||
the cross-provider syntax (service@provider) should be used to refer to the `TraefikService`, just as in the middleware case.
|
the cross-provider syntax (`service@provider`) should be used to refer to the `TraefikService`, just as in the middleware case.
|
||||||
|
|
||||||
Specifying a namespace attribute in this case would not make any sense, and will be ignored (except if the provider is `kubernetescrd`).
|
Specifying a namespace attribute in this case would not make any sense, and will be ignored (except if the provider is `kubernetescrd`).
|
||||||
|
|
||||||
### TLS Option
|
### Kind `IngressRouteTCP`
|
||||||
|
|
||||||
Additionally, to allow for the use of TLS options in an IngressRoute, we defined the CRD below for the TLSOption kind.
|
`IngressRouteTCP` is the CRD implementation of a [Traefik TCP router](../routers/index.md#configuring-tcp-routers).
|
||||||
More information about TLS Options is available in the dedicated [TLS Configuration Options](../../../https/tls/#tls-options).
|
|
||||||
|
Register the `IngressRouteTCP` kind in the Kubernetes cluster before creating `IngressRouteTCP` objects.
|
||||||
|
|
||||||
|
!!! info "IngressRouteTCP Attributes"
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
--8<-- "content/routing/providers/crd_tls_option.yml"
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: ingressroutetcpfoo
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints: # [1]
|
||||||
|
- footcp
|
||||||
|
routes: # [2]
|
||||||
|
- match: HostSNI(`*`) # [3]
|
||||||
|
services: # [4]
|
||||||
|
- name: foo # [5]
|
||||||
|
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: foo.com # [16]
|
||||||
|
sans: # [17]
|
||||||
|
- a.foo.com
|
||||||
|
- b.foo.com
|
||||||
|
passthrough: false # [18]
|
||||||
```
|
```
|
||||||
|
|
||||||
Once the TLSOption kind has been registered with the Kubernetes cluster or defined in the File Provider, it can then be used in IngressRoute definitions, such as:
|
| Ref | Attribute | Purpose |
|
||||||
|
|------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) name |
|
||||||
|
| [2] | `routes` | List of route |
|
||||||
|
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router. |
|
||||||
|
| [4] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
|
||||||
|
| [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.cetResolver` | 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 |
|
||||||
|
|
||||||
```yaml
|
??? example "Declaring an IngressRouteTCP"
|
||||||
|
|
||||||
|
```yaml tab="IngressRouteTCP"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: ingressroutetcpfoo
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- footcp
|
||||||
|
routes:
|
||||||
|
# Match is the rule corresponding to an underlying router.
|
||||||
|
- match: HostSNI(`*`)
|
||||||
|
services:
|
||||||
|
- name: foo
|
||||||
|
port: 8080
|
||||||
|
TerminationDelay: 400
|
||||||
|
weight: 10
|
||||||
|
- name: bar
|
||||||
|
port: 8081
|
||||||
|
TerminationDelay: 500
|
||||||
|
weight: 10
|
||||||
|
tls:
|
||||||
|
certResolver: foo
|
||||||
|
domains:
|
||||||
|
- main: foo.com
|
||||||
|
sans:
|
||||||
|
- a.foo.com
|
||||||
|
- b.foo.com
|
||||||
|
options:
|
||||||
|
name: opt
|
||||||
|
namespace: default
|
||||||
|
secretName: supersecret
|
||||||
|
passthrough: false
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="TLSOption"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TLSOption
|
||||||
|
metadata:
|
||||||
|
name: opt
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
minVersion: VersionTLS12
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Secret"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: supersecret
|
||||||
|
|
||||||
|
data:
|
||||||
|
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kind: `TLSOption`
|
||||||
|
|
||||||
|
`TLSOption` is the CRD implementation of a [Traefik "TLS Option"](../../https/tls.md#tls-options).
|
||||||
|
|
||||||
|
Register the `TLSOption` kind in the Kubernetes cluster before creating `TLSOption` objects
|
||||||
|
or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`IngressRouteTCP`](#kind-ingressroutetcp) objects.
|
||||||
|
|
||||||
|
!!! info "TLSOption Attributes"
|
||||||
|
|
||||||
|
```yaml tab="TLSOption"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: TLSOption
|
||||||
|
metadata:
|
||||||
|
name: mytlsoption
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
minVersion: VersionTLS12 # [1]
|
||||||
|
maxVersion: VersionTLS13 # [1]
|
||||||
|
curvePreferences: # [3]
|
||||||
|
- CurveP521
|
||||||
|
- CurveP384
|
||||||
|
cipherSuites: # [4]
|
||||||
|
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
|
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||||
|
clientAuth: # [5]
|
||||||
|
secretNames: # [6]
|
||||||
|
- secretCA1
|
||||||
|
- secretCA2
|
||||||
|
clientAuthType: VerifyClientCertIfGiven # [7]
|
||||||
|
sniStrict: true # [8]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Ref | Attribute | Purpose |
|
||||||
|
|-----|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| [1] | `minVersion` | Defines the [minimum TLS version](../../https/tls.md#minimum-tls-version) that is acceptable |
|
||||||
|
| [2] | `maxVersion` | Defines the [maximum TLS version](../../https/tls.md#maximum-tls-version) that is acceptable |
|
||||||
|
| [3] | `cipherSuites` | list of supported [cipher suites](../../https/tls.md#cipher-suites) for TLS versions up to TLS 1.2 |
|
||||||
|
| [4] | `curvePreferences` | List of the [elliptic curves references](../../https/tls.md#curve-preferences) that will be used in an ECDHE handshake, in preference order |
|
||||||
|
| [5] | `clientAuth` | determines the server's policy for TLS [Client Authentication](../../https/tls.md#client-authentication-mtls) |
|
||||||
|
| [6] | `clientAuth.secretNames` | list of names of the referenced Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) (in TLSOption namespace) |
|
||||||
|
| [7] | `clientAuth.clientAuthType` | defines the client authentication type to apply. The available values are: `NoClientCert`, `RequestClientCert`, `VerifyClientCertIfGiven` and `RequireAndVerifyClientCert` |
|
||||||
|
| [8] | `sniStrict` | if `true`, Traefik won't allow connections from clients connections that do not specify a server_name extension |
|
||||||
|
|
||||||
|
??? example "Declaring and referencing a TLSOption"
|
||||||
|
|
||||||
|
```yaml tab="TLSOption"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: TLSOption
|
kind: TLSOption
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -214,8 +828,18 @@ metadata:
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
minVersion: VersionTLS12
|
minVersion: VersionTLS12
|
||||||
|
sniStrict: true
|
||||||
|
cipherSuites:
|
||||||
|
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
|
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||||
|
clientAuth:
|
||||||
|
secretNames:
|
||||||
|
- secretCA1
|
||||||
|
- secretCA2
|
||||||
|
clientAuthType: VerifyClientCertIfGiven
|
||||||
|
```
|
||||||
|
|
||||||
---
|
```yaml tab="IngressRoute"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -236,6 +860,27 @@ spec:
|
||||||
namespace: default
|
namespace: default
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```yaml tab="Secrets"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secretCA1
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
data:
|
||||||
|
tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secretCA2
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
data:
|
||||||
|
tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
```
|
||||||
|
|
||||||
!!! important "References and namespaces"
|
!!! important "References and namespaces"
|
||||||
|
|
||||||
If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the IngressRoute.
|
If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the IngressRoute.
|
||||||
|
@ -245,39 +890,6 @@ spec:
|
||||||
just as in the [middleware case](../../middlewares/overview.md#provider-namespace).
|
just as in the [middleware case](../../middlewares/overview.md#provider-namespace).
|
||||||
Specifying a namespace attribute in this case would not make any sense, and will be ignored.
|
Specifying a namespace attribute in this case would not make any sense, and will be ignored.
|
||||||
|
|
||||||
### TLS
|
|
||||||
|
|
||||||
To allow for TLS, we made use of the `Secret` kind, as it was already defined, and it can be directly used in an `IngressRoute`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: supersecret
|
|
||||||
|
|
||||||
data:
|
|
||||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
|
||||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
|
||||||
kind: IngressRoute
|
|
||||||
metadata:
|
|
||||||
name: ingressroutetls
|
|
||||||
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- websecure
|
|
||||||
routes:
|
|
||||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
|
||||||
kind: Rule
|
|
||||||
services:
|
|
||||||
- name: whoami
|
|
||||||
port: 443
|
|
||||||
tls:
|
|
||||||
secretName: supersecret
|
|
||||||
```
|
|
||||||
|
|
||||||
## Further
|
## Further
|
||||||
|
|
||||||
Also see the [full example](../../user-guides/crd-acme/index.md) with Let's Encrypt.
|
Also see the [full example](../../user-guides/crd-acme/index.md) with Let's Encrypt.
|
||||||
|
|
|
@ -160,6 +160,7 @@ nav:
|
||||||
- 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md'
|
- 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md'
|
||||||
- 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md'
|
- 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md'
|
||||||
- 'Migration':
|
- 'Migration':
|
||||||
|
- 'Traefik v2 minor migrations': 'migration/v2.md'
|
||||||
- 'Traefik v1 to v2': 'migration/v1-to-v2.md'
|
- 'Traefik v1 to v2': 'migration/v1-to-v2.md'
|
||||||
- 'Contributing':
|
- 'Contributing':
|
||||||
- 'Thank You!': 'contributing/thank-you.md'
|
- 'Thank You!': 'contributing/thank-you.md'
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -81,7 +81,7 @@ require (
|
||||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||||
github.com/tinylib/msgp v1.0.2 // indirect
|
github.com/tinylib/msgp v1.0.2 // indirect
|
||||||
github.com/transip/gotransip v5.8.2+incompatible // indirect
|
github.com/transip/gotransip v5.8.2+incompatible // indirect
|
||||||
github.com/uber/jaeger-client-go v2.20.1+incompatible
|
github.com/uber/jaeger-client-go v2.21.1+incompatible
|
||||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||||
github.com/unrolled/render v1.0.1
|
github.com/unrolled/render v1.0.1
|
||||||
github.com/unrolled/secure v1.0.6
|
github.com/unrolled/secure v1.0.6
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -599,8 +599,8 @@ github.com/transip/gotransip v5.8.2+incompatible/go.mod h1:uacMoJVmrfOcscM4Bi5NV
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
|
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
|
||||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||||
github.com/uber/jaeger-client-go v2.20.1+incompatible h1:HgqpYBng0n7tLJIlyT4kPCIv5XgCsF+kai1NnnrJzEU=
|
github.com/uber/jaeger-client-go v2.21.1+incompatible h1:oozboeZmWz+tyh3VZttJWlF3K73mHgbokieceqKccLo=
|
||||||
github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
github.com/uber/jaeger-client-go v2.21.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||||
github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw=
|
github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw=
|
||||||
github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||||
github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY=
|
github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY=
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/integration/try"
|
"github.com/containous/traefik/v2/integration/try"
|
||||||
|
@ -62,23 +61,13 @@ func (s *ConsulCatalogSuite) TearDownSuite(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string, onAgent bool) error {
|
func (s *ConsulCatalogSuite) registerService(reg *api.AgentServiceRegistration, onAgent bool) error {
|
||||||
iPort, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client := s.consulClient
|
client := s.consulClient
|
||||||
if onAgent {
|
if onAgent {
|
||||||
client = s.consulAgentClient
|
client = s.consulAgentClient
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.Agent().ServiceRegister(&api.AgentServiceRegistration{
|
return client.Agent().ServiceRegister(reg)
|
||||||
ID: id,
|
|
||||||
Name: name,
|
|
||||||
Address: address,
|
|
||||||
Port: iPort,
|
|
||||||
Tags: tags,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error {
|
func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error {
|
||||||
|
@ -90,11 +79,34 @@ func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) {
|
func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) {
|
||||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
|
reg1 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{"traefik.enable=true"},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err := s.registerService(reg1, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
|
|
||||||
|
reg2 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami2",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{"traefik.enable=true"},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err = s.registerService(reg2, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
|
|
||||||
|
reg3 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami3",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{"traefik.enable=true"},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err = s.registerService(reg3, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
tempObjects := struct {
|
tempObjects := struct {
|
||||||
|
@ -128,14 +140,21 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
|
func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
|
||||||
labels := []string{
|
containerIP := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
|
||||||
|
|
||||||
|
reg := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{
|
||||||
"traefik.enable=true",
|
"traefik.enable=true",
|
||||||
"traefik.http.routers.router1.rule=Path(`/whoami`)",
|
"traefik.http.routers.router1.rule=Path(`/whoami`)",
|
||||||
"traefik.http.routers.router1.service=service1",
|
"traefik.http.routers.router1.service=service1",
|
||||||
"traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
"traefik.http.services.service1.loadBalancer.server.url=http://" + containerIP,
|
||||||
|
},
|
||||||
|
Port: 80,
|
||||||
|
Address: containerIP,
|
||||||
}
|
}
|
||||||
|
err := s.registerService(reg, false)
|
||||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
|
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
tempObjects := struct {
|
tempObjects := struct {
|
||||||
|
@ -172,7 +191,14 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
|
reg := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{"traefik.enable=true"},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err := s.registerService(reg, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
@ -204,7 +230,14 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}, false)
|
reg := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{"traefik.enable=true"},
|
||||||
|
Port: 80,
|
||||||
|
Address: "",
|
||||||
|
}
|
||||||
|
err := s.registerService(reg, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
@ -236,7 +269,13 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil, false)
|
reg := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err := s.registerService(reg, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// Start traefik
|
// Start traefik
|
||||||
|
@ -269,14 +308,20 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
// Start a container with some labels
|
// Start a container with some tags
|
||||||
labels := []string{
|
reg := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoamitcp",
|
||||||
|
Name: "whoamitcp",
|
||||||
|
Tags: []string{
|
||||||
"traefik.tcp.Routers.Super.Rule=HostSNI(`my.super.host`)",
|
"traefik.tcp.Routers.Super.Rule=HostSNI(`my.super.host`)",
|
||||||
"traefik.tcp.Routers.Super.tls=true",
|
"traefik.tcp.Routers.Super.tls=true",
|
||||||
"traefik.tcp.Services.Super.Loadbalancer.server.port=8080",
|
"traefik.tcp.Services.Super.Loadbalancer.server.port=8080",
|
||||||
|
},
|
||||||
|
Port: 8080,
|
||||||
|
Address: s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels, false)
|
err := s.registerService(reg, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// Start traefik
|
// Start traefik
|
||||||
|
@ -310,18 +355,31 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
// Start a container with some labels
|
// Start a container with some tags
|
||||||
labels := []string{
|
reg1 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{
|
||||||
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
||||||
|
},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
}
|
}
|
||||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
|
|
||||||
|
err := s.registerService(reg1, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// Start another container by replacing a '.' by a '-'
|
// Start another container by replacing a '.' by a '-'
|
||||||
labels = []string{
|
reg2 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami2",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{
|
||||||
"traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)",
|
"traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)",
|
||||||
|
},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress,
|
||||||
}
|
}
|
||||||
err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, false)
|
err = s.registerService(reg2, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// Start traefik
|
// Start traefik
|
||||||
|
@ -364,16 +422,31 @@ func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C)
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
// Start a container with some labels
|
// Start a container with some tags
|
||||||
labels := []string{
|
tags := []string{
|
||||||
"traefik.enable=true",
|
"traefik.enable=true",
|
||||||
"traefik.http.Routers.Super.service=whoami",
|
"traefik.http.Routers.Super.service=whoami",
|
||||||
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
||||||
}
|
}
|
||||||
err := s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
|
|
||||||
|
reg1 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: tags,
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err := s.registerService(reg1, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, true)
|
reg2 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: tags,
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
err = s.registerService(reg2, true)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// Start traefik
|
// Start traefik
|
||||||
|
@ -417,11 +490,18 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
// Start a container with some labels
|
// Start a container with some tags
|
||||||
labels := []string{
|
reg := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: []string{
|
||||||
"traefik.random.value=my.super.host",
|
"traefik.random.value=my.super.host",
|
||||||
|
},
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
}
|
}
|
||||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
|
|
||||||
|
err := s.registerService(reg, false)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// Start traefik
|
// Start traefik
|
||||||
|
@ -441,3 +521,82 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
|
||||||
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) {
|
||||||
|
tags := []string{
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.router1.rule=Path(`/whoami`)",
|
||||||
|
"traefik.http.routers.router1.service=service1",
|
||||||
|
"traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
reg1 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami1",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: tags,
|
||||||
|
Port: 80,
|
||||||
|
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||||
|
Check: &api.AgentServiceCheck{
|
||||||
|
CheckID: "some-failed-check",
|
||||||
|
TCP: "127.0.0.1:1234",
|
||||||
|
Name: "some-failed-check",
|
||||||
|
Interval: "1s",
|
||||||
|
Timeout: "1s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.registerService(reg1, false)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
tempObjects := struct {
|
||||||
|
ConsulAddress string
|
||||||
|
}{
|
||||||
|
ConsulAddress: s.consulAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||||
|
defer os.Remove(file)
|
||||||
|
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
defer display(c)
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = s.deregisterService("whoami1", false)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
containerIP := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress
|
||||||
|
|
||||||
|
reg2 := &api.AgentServiceRegistration{
|
||||||
|
ID: "whoami2",
|
||||||
|
Name: "whoami",
|
||||||
|
Tags: tags,
|
||||||
|
Port: 80,
|
||||||
|
Address: containerIP,
|
||||||
|
Check: &api.AgentServiceCheck{
|
||||||
|
CheckID: "some-ok-check",
|
||||||
|
TCP: containerIP + ":80",
|
||||||
|
Name: "some-ok-check",
|
||||||
|
Interval: "1s",
|
||||||
|
Timeout: "1s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.registerService(reg2, false)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "whoami"
|
||||||
|
|
||||||
|
// FIXME Need to wait for up to 10 seconds (for consul discovery or traefik to boot up ?)
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = s.deregisterService("whoami2", false)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
2
integration/testdata/rawdata-consul.json
vendored
2
integration/testdata/rawdata-consul.json
vendored
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
"dashboard_redirect@internal": {
|
"dashboard_redirect@internal": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
},
|
},
|
||||||
|
|
2
integration/testdata/rawdata-etcd.json
vendored
2
integration/testdata/rawdata-etcd.json
vendored
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
"dashboard_redirect@internal": {
|
"dashboard_redirect@internal": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
},
|
},
|
||||||
|
|
2
integration/testdata/rawdata-ingress.json
vendored
2
integration/testdata/rawdata-ingress.json
vendored
|
@ -60,7 +60,7 @@
|
||||||
"middlewares": {
|
"middlewares": {
|
||||||
"dashboard_redirect@internal": {
|
"dashboard_redirect@internal": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
},
|
},
|
||||||
|
|
2
integration/testdata/rawdata-redis.json
vendored
2
integration/testdata/rawdata-redis.json
vendored
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
"dashboard_redirect@internal": {
|
"dashboard_redirect@internal": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
},
|
},
|
||||||
|
|
2
integration/testdata/rawdata-zk.json
vendored
2
integration/testdata/rawdata-zk.json
vendored
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
"dashboard_redirect@internal": {
|
"dashboard_redirect@internal": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,8 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// decodeFileToNode decodes the configuration in filePath in a tree of untyped nodes.
|
// decodeFileToNode decodes the configuration in filePath in a tree of untyped nodes.
|
||||||
// If filters is not empty, it skips any configuration element whose name is
|
// If filters is not empty, it skips any configuration element whose name is not among filters.
|
||||||
// not among filters.
|
|
||||||
func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error) {
|
func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error) {
|
||||||
content, err := ioutil.ReadFile(filePath)
|
content, err := ioutil.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -40,7 +39,20 @@ func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error)
|
||||||
return nil, fmt.Errorf("unsupported file extension: %s", filePath)
|
return nil, fmt.Errorf("unsupported file extension: %s", filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodeRawToNode(data, parser.DefaultRootName, filters...)
|
if len(data) == 0 {
|
||||||
|
return nil, fmt.Errorf("no configuration found in file: %s", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
node, err := decodeRawToNode(data, parser.DefaultRootName, filters...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(node.Children) == 0 {
|
||||||
|
return nil, fmt.Errorf("no valid configuration found in file: %s", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRootFieldNames(element interface{}) []string {
|
func getRootFieldNames(element interface{}) []string {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/parser"
|
"github.com/containous/traefik/v2/pkg/config/parser"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_getRootFieldNames(t *testing.T) {
|
func Test_getRootFieldNames(t *testing.T) {
|
||||||
|
@ -42,17 +43,43 @@ func Test_getRootFieldNames(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_decodeFileToNode_errors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
confFile string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "non existing file",
|
||||||
|
confFile: "./fixtures/not_existing.toml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "file without content",
|
||||||
|
confFile: "./fixtures/empty.toml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "file without any valid configuration",
|
||||||
|
confFile: "./fixtures/no_conf.toml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
node, err := decodeFileToNode(test.confFile,
|
||||||
|
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Nil(t, node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_decodeFileToNode_compare(t *testing.T) {
|
func Test_decodeFileToNode_compare(t *testing.T) {
|
||||||
nodeToml, err := decodeFileToNode("./fixtures/sample.toml",
|
nodeToml, err := decodeFileToNode("./fixtures/sample.toml",
|
||||||
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeYaml, err := decodeFileToNode("./fixtures/sample.yml")
|
nodeYaml, err := decodeFileToNode("./fixtures/sample.yml")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, nodeToml, nodeYaml)
|
assert.Equal(t, nodeToml, nodeYaml)
|
||||||
}
|
}
|
||||||
|
@ -60,9 +87,7 @@ func Test_decodeFileToNode_compare(t *testing.T) {
|
||||||
func Test_decodeFileToNode_Toml(t *testing.T) {
|
func Test_decodeFileToNode_Toml(t *testing.T) {
|
||||||
node, err := decodeFileToNode("./fixtures/sample.toml",
|
node, err := decodeFileToNode("./fixtures/sample.toml",
|
||||||
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := &parser.Node{
|
expected := &parser.Node{
|
||||||
Name: "traefik",
|
Name: "traefik",
|
||||||
|
@ -294,9 +319,7 @@ func Test_decodeFileToNode_Toml(t *testing.T) {
|
||||||
|
|
||||||
func Test_decodeFileToNode_Yaml(t *testing.T) {
|
func Test_decodeFileToNode_Yaml(t *testing.T) {
|
||||||
node, err := decodeFileToNode("./fixtures/sample.yml")
|
node, err := decodeFileToNode("./fixtures/sample.yml")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := &parser.Node{
|
expected := &parser.Node{
|
||||||
Name: "traefik",
|
Name: "traefik",
|
||||||
|
|
0
pkg/config/file/fixtures/empty.toml
Normal file
0
pkg/config/file/fixtures/empty.toml
Normal file
2
pkg/config/file/fixtures/no_conf.toml
Normal file
2
pkg/config/file/fixtures/no_conf.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[foo]
|
||||||
|
bar = "test"
|
|
@ -21,6 +21,10 @@ func DecodeToNode(labels map[string]string, rootName string, filters ...string)
|
||||||
|
|
||||||
var parts []string
|
var parts []string
|
||||||
for _, v := range split {
|
for _, v := range split {
|
||||||
|
if v == "" {
|
||||||
|
return nil, fmt.Errorf("invalid element: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
if v[0] == '[' {
|
if v[0] == '[' {
|
||||||
return nil, fmt.Errorf("invalid leading character '[' in field name (bracket is a slice delimiter): %s", v)
|
return nil, fmt.Errorf("invalid leading character '[' in field name (bracket is a slice delimiter): %s", v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,15 @@ func TestDecodeToNode(t *testing.T) {
|
||||||
in: map[string]string{},
|
in: map[string]string{},
|
||||||
expected: expected{node: nil},
|
expected: expected{node: nil},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid label, ending by a dot",
|
||||||
|
in: map[string]string{
|
||||||
|
"traefik.http.": "bar",
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "level 1",
|
desc: "level 1",
|
||||||
in: map[string]string{
|
in: map[string]string{
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ middlewares.Stateful = &captureResponseWriter{}
|
_ middlewares.Stateful = &captureResponseWriterWithCloseNotify{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type capturer interface {
|
type capturer interface {
|
||||||
|
@ -24,7 +24,7 @@ func newCaptureResponseWriter(rw http.ResponseWriter) capturer {
|
||||||
if _, ok := rw.(http.CloseNotifier); !ok {
|
if _, ok := rw.(http.CloseNotifier); !ok {
|
||||||
return capt
|
return capt
|
||||||
}
|
}
|
||||||
return captureResponseWriterWithCloseNotify{capt}
|
return &captureResponseWriterWithCloseNotify{capt}
|
||||||
}
|
}
|
||||||
|
|
||||||
// captureResponseWriter is a wrapper of type http.ResponseWriter
|
// captureResponseWriter is a wrapper of type http.ResponseWriter
|
||||||
|
@ -76,13 +76,6 @@ func (crw *captureResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error)
|
||||||
return nil, nil, fmt.Errorf("not a hijacker: %T", crw.rw)
|
return nil, nil, fmt.Errorf("not a hijacker: %T", crw.rw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (crw *captureResponseWriter) CloseNotify() <-chan bool {
|
|
||||||
if c, ok := crw.rw.(http.CloseNotifier); ok {
|
|
||||||
return c.CloseNotify()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crw *captureResponseWriter) Status() int {
|
func (crw *captureResponseWriter) Status() int {
|
||||||
return crw.status
|
return crw.status
|
||||||
}
|
}
|
||||||
|
|
50
pkg/middlewares/accesslog/capture_response_writer_test.go
Normal file
50
pkg/middlewares/accesslog/capture_response_writer_test.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package accesslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rwWithCloseNotify struct {
|
||||||
|
*httptest.ResponseRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rwWithCloseNotify) CloseNotify() <-chan bool {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseNotifier(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
rw http.ResponseWriter
|
||||||
|
desc string
|
||||||
|
implementsCloseNotifier bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
rw: httptest.NewRecorder(),
|
||||||
|
desc: "does not implement CloseNotifier",
|
||||||
|
implementsCloseNotifier: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rw: &rwWithCloseNotify{httptest.NewRecorder()},
|
||||||
|
desc: "implements CloseNotifier",
|
||||||
|
implementsCloseNotifier: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, ok := test.rw.(http.CloseNotifier)
|
||||||
|
assert.Equal(t, test.implementsCloseNotifier, ok)
|
||||||
|
|
||||||
|
rw := newCaptureResponseWriter(test.rw)
|
||||||
|
_, impl := rw.(http.CloseNotifier)
|
||||||
|
assert.Equal(t, test.implementsCloseNotifier, impl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,9 +88,11 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHeader(req, forwardReq, fa.trustForwardHeader)
|
// Ensure tracing headers are in the request before we copy the headers to the
|
||||||
|
// forwardReq.
|
||||||
|
tracing.InjectRequestHeaders(req)
|
||||||
|
|
||||||
tracing.InjectRequestHeaders(forwardReq)
|
writeHeader(req, forwardReq, fa.trustForwardHeader)
|
||||||
|
|
||||||
forwardResponse, forwardErr := httpClient.Do(forwardReq)
|
forwardResponse, forwardErr := httpClient.Do(forwardReq)
|
||||||
if forwardErr != nil {
|
if forwardErr != nil {
|
||||||
|
|
|
@ -3,13 +3,18 @@ package auth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
|
tracingMiddleware "github.com/containous/traefik/v2/pkg/middlewares/tracing"
|
||||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||||
|
"github.com/containous/traefik/v2/pkg/tracing"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
|
"github.com/opentracing/opentracing-go/mocktracer"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/vulcand/oxy/forward"
|
"github.com/vulcand/oxy/forward"
|
||||||
|
@ -394,3 +399,44 @@ func Test_writeHeader(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestForwardAuthUsesTracing(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Mockpfx-Ids-Traceid") == "" {
|
||||||
|
t.Errorf("expected Mockpfx-Ids-Traceid header to be present in request")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
next := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
|
||||||
|
auth := dynamic.ForwardAuth{
|
||||||
|
Address: server.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
tracer := mocktracer.New()
|
||||||
|
opentracing.SetGlobalTracer(tracer)
|
||||||
|
|
||||||
|
tr, _ := tracing.NewTracing("testApp", 100, &mockBackend{tracer})
|
||||||
|
|
||||||
|
next, err := NewForward(context.Background(), next, auth, "authTest")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
next = tracingMiddleware.NewEntryPoint(context.Background(), tr, "tracingTest", next)
|
||||||
|
|
||||||
|
ts := httptest.NewServer(next)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
opentracing.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *mockBackend) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||||
|
return b.Tracer, ioutil.NopCloser(nil), nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-kit/kit/metrics"
|
"github.com/go-kit/kit/metrics"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CollectingCounter is a metrics.Counter implementation that enables access to the CounterValue and LastLabelValues.
|
// CollectingCounter is a metrics.Counter implementation that enables access to the CounterValue and LastLabelValues.
|
||||||
|
@ -56,3 +57,44 @@ func newCollectingRetryMetrics() *collectingRetryMetrics {
|
||||||
func (m *collectingRetryMetrics) ServiceRetriesCounter() metrics.Counter {
|
func (m *collectingRetryMetrics) ServiceRetriesCounter() metrics.Counter {
|
||||||
return m.retriesCounter
|
return m.retriesCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rwWithCloseNotify struct {
|
||||||
|
*httptest.ResponseRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rwWithCloseNotify) CloseNotify() <-chan bool {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseNotifier(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
rw http.ResponseWriter
|
||||||
|
desc string
|
||||||
|
implementsCloseNotifier bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
rw: httptest.NewRecorder(),
|
||||||
|
desc: "does not implement CloseNotifier",
|
||||||
|
implementsCloseNotifier: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rw: &rwWithCloseNotify{httptest.NewRecorder()},
|
||||||
|
desc: "implements CloseNotifier",
|
||||||
|
implementsCloseNotifier: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, ok := test.rw.(http.CloseNotifier)
|
||||||
|
assert.Equal(t, test.implementsCloseNotifier, ok)
|
||||||
|
|
||||||
|
rw := newResponseRecorder(test.rw)
|
||||||
|
_, impl := rw.(http.CloseNotifier)
|
||||||
|
assert.Equal(t, test.implementsCloseNotifier, impl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func newResponseRecorder(rw http.ResponseWriter) recorder {
|
||||||
if _, ok := rw.(http.CloseNotifier); !ok {
|
if _, ok := rw.(http.CloseNotifier); !ok {
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
return responseRecorderWithCloseNotify{rec}
|
return &responseRecorderWithCloseNotify{rec}
|
||||||
}
|
}
|
||||||
|
|
||||||
// responseRecorder captures information from the response and preserves it for
|
// responseRecorder captures information from the response and preserves it for
|
||||||
|
@ -55,13 +55,6 @@ func (r *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
return r.ResponseWriter.(http.Hijacker).Hijack()
|
return r.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseNotify returns a channel that receives at most a
|
|
||||||
// single value (true) when the client connection has gone
|
|
||||||
// away.
|
|
||||||
func (r *responseRecorder) CloseNotify() <-chan bool {
|
|
||||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush sends any buffered data to the client.
|
// Flush sends any buffered data to the client.
|
||||||
func (r *responseRecorder) Flush() {
|
func (r *responseRecorder) Flush() {
|
||||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
||||||
|
|
|
@ -152,12 +152,12 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error
|
||||||
|
|
||||||
var data []itemData
|
var data []itemData
|
||||||
for name := range consulServiceNames {
|
for name := range consulServiceNames {
|
||||||
consulServices, err := p.fetchService(ctx, name)
|
consulServices, healthServices, err := p.fetchService(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, consulService := range consulServices {
|
for i, consulService := range consulServices {
|
||||||
address := consulService.ServiceAddress
|
address := consulService.ServiceAddress
|
||||||
if address == "" {
|
if address == "" {
|
||||||
address = consulService.Address
|
address = consulService.Address
|
||||||
|
@ -171,7 +171,7 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error
|
||||||
Port: strconv.Itoa(consulService.ServicePort),
|
Port: strconv.Itoa(consulService.ServicePort),
|
||||||
Labels: tagsToNeutralLabels(consulService.ServiceTags, p.Prefix),
|
Labels: tagsToNeutralLabels(consulService.ServiceTags, p.Prefix),
|
||||||
Tags: consulService.ServiceTags,
|
Tags: consulService.ServiceTags,
|
||||||
Status: consulService.Checks.AggregatedStatus(),
|
Status: healthServices[i].Checks.AggregatedStatus(),
|
||||||
}
|
}
|
||||||
|
|
||||||
extraConf, err := p.getConfiguration(item)
|
extraConf, err := p.getConfiguration(item)
|
||||||
|
@ -187,15 +187,21 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) fetchService(ctx context.Context, name string) ([]*api.CatalogService, error) {
|
func (p *Provider) fetchService(ctx context.Context, name string) ([]*api.CatalogService, []*api.ServiceEntry, error) {
|
||||||
var tagFilter string
|
var tagFilter string
|
||||||
if !p.ExposedByDefault {
|
if !p.ExposedByDefault {
|
||||||
tagFilter = p.Prefix + ".enable=true"
|
tagFilter = p.Prefix + ".enable=true"
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &api.QueryOptions{AllowStale: p.Stale, RequireConsistent: p.RequireConsistent, UseCache: p.Cache}
|
opts := &api.QueryOptions{AllowStale: p.Stale, RequireConsistent: p.RequireConsistent, UseCache: p.Cache}
|
||||||
|
|
||||||
consulServices, _, err := p.client.Catalog().Service(name, tagFilter, opts)
|
consulServices, _, err := p.client.Catalog().Service(name, tagFilter, opts)
|
||||||
return consulServices, err
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
healthServices, _, err := p.client.Health().Service(name, tagFilter, false, opts)
|
||||||
|
return consulServices, healthServices, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) fetchServices(ctx context.Context) (map[string][]string, error) {
|
func (p *Provider) fetchServices(ctx context.Context) (map[string][]string, error) {
|
||||||
|
|
|
@ -307,8 +307,13 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||||
|
|
||||||
var servers []dynamic.Server
|
var servers []dynamic.Server
|
||||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||||
|
protocol := "http"
|
||||||
|
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
|
||||||
return append(servers, dynamic.Server{
|
return append(servers, dynamic.Server{
|
||||||
URL: fmt.Sprintf("http://%s:%d", service.Spec.ExternalName, portSpec.Port),
|
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port),
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,11 +380,11 @@ func (c configBuilder) nameAndService(ctx context.Context, namespaceService stri
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fullName := fullServiceName(svcCtx, namespace, service.Name, service.Port)
|
fullName := fullServiceName(svcCtx, namespace, service, service.Port)
|
||||||
|
|
||||||
return fullName, serversLB, nil
|
return fullName, serversLB, nil
|
||||||
case service.Kind == "TraefikService":
|
case service.Kind == "TraefikService":
|
||||||
return fullServiceName(svcCtx, namespace, service.Name, 0), nil, nil
|
return fullServiceName(svcCtx, namespace, service, 0), nil, nil
|
||||||
default:
|
default:
|
||||||
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
|
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
|
||||||
}
|
}
|
||||||
|
@ -394,27 +399,22 @@ func splitSvcNameProvider(name string) (string, string) {
|
||||||
return svc, pvd
|
return svc, pvd
|
||||||
}
|
}
|
||||||
|
|
||||||
func fullServiceName(ctx context.Context, namespace, serviceName string, port int32) string {
|
func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port int32) string {
|
||||||
if port != 0 {
|
if port != 0 {
|
||||||
return provider.Normalize(fmt.Sprintf("%s-%s-%d", namespace, serviceName, port))
|
return provider.Normalize(fmt.Sprintf("%s-%s-%d", namespace, service.Name, port))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(serviceName, providerNamespaceSeparator) {
|
if !strings.Contains(service.Name, providerNamespaceSeparator) {
|
||||||
return provider.Normalize(fmt.Sprintf("%s-%s", namespace, serviceName))
|
return provider.Normalize(fmt.Sprintf("%s-%s", namespace, service.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
name, pName := splitSvcNameProvider(serviceName)
|
name, pName := splitSvcNameProvider(service.Name)
|
||||||
if pName == providerName {
|
if pName == providerName {
|
||||||
return provider.Normalize(fmt.Sprintf("%s-%s", namespace, name))
|
return provider.Normalize(fmt.Sprintf("%s-%s", namespace, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, if namespace == "default", we do not know whether it had been intentionally set as such,
|
if service.Namespace != "" {
|
||||||
// or if we're simply hitting the value set by default.
|
log.FromContext(ctx).Warnf("namespace %q is ignored in cross-provider context", service.Namespace)
|
||||||
// But as it is most likely very much the latter,
|
|
||||||
// and we do not want to systematically log spam users in that case,
|
|
||||||
// we skip logging whenever the namespace is "default".
|
|
||||||
if namespace != "default" {
|
|
||||||
log.FromContext(ctx).Warnf("namespace %q is ignored in cross-provider context", namespace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.Normalize(name) + providerNamespaceSeparator + pName
|
return provider.Normalize(name) + providerNamespaceSeparator + pName
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: ""
|
||||||
|
namespace: testing
|
||||||
|
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: testing.example.com
|
|
@ -313,12 +313,14 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
conf.HTTP.Services["default-backend"] = service
|
conf.HTTP.Services["default-backend"] = service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range ingress.Spec.Rules {
|
for _, rule := range ingress.Spec.Rules {
|
||||||
if err := checkStringQuoteValidity(rule.Host); err != nil {
|
if err := checkStringQuoteValidity(rule.Host); err != nil {
|
||||||
log.FromContext(ctx).Errorf("Invalid syntax for host: %s", rule.Host)
|
log.FromContext(ctx).Errorf("Invalid syntax for host: %s", rule.Host)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.HTTP != nil {
|
||||||
for _, p := range rule.HTTP.Paths {
|
for _, p := range rule.HTTP.Paths {
|
||||||
service, err := loadService(client, ingress.Namespace, p.Backend)
|
service, err := loadService(client, ingress.Namespace, p.Backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -360,6 +362,8 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
}
|
}
|
||||||
conf.HTTP.Services[serviceName] = service
|
conf.HTTP.Services[serviceName] = service
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := p.updateIngressStatus(ingress, client)
|
err := p.updateIngressStatus(ingress, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Errorf("Error while updating ingress status: %v", err)
|
log.FromContext(ctx).Errorf("Error while updating ingress status: %v", err)
|
||||||
|
|
|
@ -38,6 +38,17 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Ingress one rule host only",
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Ingress with a basic rule on one path",
|
desc: "Ingress with a basic rule on one path",
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"middlewares": {
|
"middlewares": {
|
||||||
"dashboard_redirect": {
|
"dashboard_redirect": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
"middlewares": {
|
"middlewares": {
|
||||||
"dashboard_redirect": {
|
"dashboard_redirect": {
|
||||||
"redirectRegex": {
|
"redirectRegex": {
|
||||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||||
"replacement": "${1}/dashboard/",
|
"replacement": "${1}/dashboard/",
|
||||||
"permanent": true
|
"permanent": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
|
||||||
|
|
||||||
cfg.HTTP.Middlewares["dashboard_redirect"] = &dynamic.Middleware{
|
cfg.HTTP.Middlewares["dashboard_redirect"] = &dynamic.Middleware{
|
||||||
RedirectRegex: &dynamic.RedirectRegex{
|
RedirectRegex: &dynamic.RedirectRegex{
|
||||||
Regex: `^(http:\/\/[^:]+(:\d+)?)/$`,
|
Regex: `^(http:\/\/[^:\/]+(:\d+)?)\/$`,
|
||||||
Replacement: "${1}/dashboard/",
|
Replacement: "${1}/dashboard/",
|
||||||
Permanent: true,
|
Permanent: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,9 +52,9 @@ func (h *httpForwarder) Accept() (net.Conn, error) {
|
||||||
type TCPEntryPoints map[string]*TCPEntryPoint
|
type TCPEntryPoints map[string]*TCPEntryPoint
|
||||||
|
|
||||||
// NewTCPEntryPoints creates a new TCPEntryPoints.
|
// NewTCPEntryPoints creates a new TCPEntryPoints.
|
||||||
func NewTCPEntryPoints(staticConfiguration static.Configuration) (TCPEntryPoints, error) {
|
func NewTCPEntryPoints(entryPointsConfig static.EntryPoints) (TCPEntryPoints, error) {
|
||||||
serverEntryPointsTCP := make(TCPEntryPoints)
|
serverEntryPointsTCP := make(TCPEntryPoints)
|
||||||
for entryPointName, config := range staticConfiguration.EntryPoints {
|
for entryPointName, config := range entryPointsConfig {
|
||||||
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
|
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -171,6 +171,23 @@ func (e *TCPEntryPoint) StartTCP(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
|
// Enforce read/write deadlines at the connection level,
|
||||||
|
// because when we're peeking the first byte to determine whether we are doing TLS,
|
||||||
|
// the deadlines at the server level are not taken into account.
|
||||||
|
if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 {
|
||||||
|
err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout)))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error while setting read deadline: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.transportConfiguration.RespondingTimeouts.WriteTimeout > 0 {
|
||||||
|
err = writeCloser.SetWriteDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.WriteTimeout)))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error while setting write deadline: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker))
|
e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -191,48 +208,48 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
|
||||||
logger.Debugf("Waiting %s seconds before killing connections.", graceTimeOut)
|
logger.Debugf("Waiting %s seconds before killing connections.", graceTimeOut)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
if e.httpServer.Server != nil {
|
|
||||||
wg.Add(1)
|
shutdownServer := func(server stoppableServer) {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := e.httpServer.Server.Shutdown(ctx); err != nil {
|
err := server.Shutdown(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
logger.Debugf("Wait server shutdown is overdue to: %s", err)
|
logger.Debugf("Server failed to shutdown within deadline because: %s", err)
|
||||||
err = e.httpServer.Server.Close()
|
if err = server.Close(); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
logger.Error(err)
|
||||||
|
// We expect Close to fail again because Shutdown most likely failed when trying to close a listener.
|
||||||
|
// We still call it however, to make sure that all connections get closed as well.
|
||||||
|
server.Close()
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
if e.httpServer.Server != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go shutdownServer(e.httpServer.Server)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.httpsServer.Server != nil {
|
if e.httpsServer.Server != nil {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go shutdownServer(e.httpsServer.Server)
|
||||||
defer wg.Done()
|
|
||||||
if err := e.httpsServer.Server.Shutdown(ctx); err != nil {
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
logger.Debugf("Wait server shutdown is overdue to: %s", err)
|
|
||||||
err = e.httpsServer.Server.Close()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.tracker != nil {
|
if e.tracker != nil {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := e.tracker.Shutdown(ctx); err != nil {
|
err := e.tracker.Shutdown(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
logger.Debugf("Wait hijack connection is overdue to: %s", err)
|
logger.Debugf("Server failed to shutdown before deadline because: %s", err)
|
||||||
|
}
|
||||||
e.tracker.Close()
|
e.tracker.Close()
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,6 +478,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
||||||
serverHTTP := &http.Server{
|
serverHTTP := &http.Server{
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
ErrorLog: httpServerLogger,
|
ErrorLog: httpServerLogger,
|
||||||
|
ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout),
|
||||||
|
WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout),
|
||||||
|
IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
|
||||||
}
|
}
|
||||||
|
|
||||||
listener := newHTTPForwarder(ln)
|
listener := newHTTPForwarder(ln)
|
||||||
|
|
|
@ -3,8 +3,11 @@ package server
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,128 +18,206 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShutdownHTTP(t *testing.T) {
|
func TestShutdownHijacked(t *testing.T) {
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
|
||||||
Address: ":0",
|
|
||||||
Transport: &static.EntryPointsTransport{
|
|
||||||
LifeCycle: &static.LifeCycle{
|
|
||||||
RequestAcceptGraceTimeout: 0,
|
|
||||||
GraceTimeOut: types.Duration(5 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
go entryPoint.StartTCP(context.Background())
|
|
||||||
|
|
||||||
router := &tcp.Router{}
|
|
||||||
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
entryPoint.SwitchRouter(router)
|
|
||||||
|
|
||||||
conn, err := net.Dial("tcp", entryPoint.listener.Addr().String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
go entryPoint.Shutdown(context.Background())
|
|
||||||
|
|
||||||
request, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8082", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = request.Write(conn)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), request)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShutdownHTTPHijacked(t *testing.T) {
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
|
||||||
Address: ":0",
|
|
||||||
Transport: &static.EntryPointsTransport{
|
|
||||||
LifeCycle: &static.LifeCycle{
|
|
||||||
RequestAcceptGraceTimeout: 0,
|
|
||||||
GraceTimeOut: types.Duration(5 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
go entryPoint.StartTCP(context.Background())
|
|
||||||
|
|
||||||
router := &tcp.Router{}
|
router := &tcp.Router{}
|
||||||
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
conn, _, err := rw.(http.Hijacker).Hijack()
|
conn, _, err := rw.(http.Hijacker).Hijack()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
resp := http.Response{StatusCode: http.StatusOK}
|
resp := http.Response{StatusCode: http.StatusOK}
|
||||||
err = resp.Write(conn)
|
err = resp.Write(conn)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}))
|
}))
|
||||||
|
testShutdown(t, router)
|
||||||
entryPoint.SwitchRouter(router)
|
|
||||||
|
|
||||||
conn, err := net.Dial("tcp", entryPoint.listener.Addr().String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
go entryPoint.Shutdown(context.Background())
|
|
||||||
|
|
||||||
request, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8082", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = request.Write(conn)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), request)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShutdownTCPConn(t *testing.T) {
|
func TestShutdownHTTP(t *testing.T) {
|
||||||
|
router := &tcp.Router{}
|
||||||
|
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}))
|
||||||
|
testShutdown(t, router)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShutdownTCP(t *testing.T) {
|
||||||
|
router := &tcp.Router{}
|
||||||
|
router.AddCatchAllNoTLS(tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||||
|
for {
|
||||||
|
_, err := http.ReadRequest(bufio.NewReader(conn))
|
||||||
|
|
||||||
|
if err == io.EOF || (err != nil && strings.HasSuffix(err.Error(), "use of closed network connection")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp := http.Response{StatusCode: http.StatusOK}
|
||||||
|
err = resp.Write(conn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
testShutdown(t, router)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShutdown(t *testing.T, router *tcp.Router) {
|
||||||
|
epConfig := &static.EntryPointsTransport{}
|
||||||
|
epConfig.SetDefaults()
|
||||||
|
|
||||||
|
epConfig.LifeCycle.RequestAcceptGraceTimeout = 0
|
||||||
|
epConfig.LifeCycle.GraceTimeOut = types.Duration(5 * time.Second)
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
||||||
Address: ":0",
|
// We explicitly use an IPV4 address because on Alpine, with an IPV6 address
|
||||||
Transport: &static.EntryPointsTransport{
|
// there seems to be shenanigans related to properly cleaning up file descriptors
|
||||||
LifeCycle: &static.LifeCycle{
|
Address: "127.0.0.1:0",
|
||||||
RequestAcceptGraceTimeout: 0,
|
Transport: epConfig,
|
||||||
GraceTimeOut: types.Duration(5 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
go entryPoint.StartTCP(context.Background())
|
conn, err := startEntrypoint(entryPoint, router)
|
||||||
|
|
||||||
router := &tcp.Router{}
|
|
||||||
router.AddCatchAllNoTLS(tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
|
||||||
_, err := http.ReadRequest(bufio.NewReader(conn))
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
resp := http.Response{StatusCode: http.StatusOK}
|
epAddr := entryPoint.listener.Addr().String()
|
||||||
err = resp.Write(conn)
|
|
||||||
|
request, err := http.NewRequest(http.MethodHead, "http://127.0.0.1:8082", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}))
|
|
||||||
|
|
||||||
entryPoint.SwitchRouter(router)
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
|
||||||
conn, err := net.Dial("tcp", entryPoint.listener.Addr().String())
|
// We need to do a write on the conn before the shutdown to make it "exist".
|
||||||
|
// Because the connection indeed exists as far as TCP is concerned,
|
||||||
|
// but since we only pass it along to the HTTP server after at least one byte is peaked,
|
||||||
|
// the HTTP server (and hence its shutdown) does not know about the connection until that first byte peaking.
|
||||||
|
err = request.Write(conn)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
go entryPoint.Shutdown(context.Background())
|
go entryPoint.Shutdown(context.Background())
|
||||||
|
|
||||||
request, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8082", nil)
|
// Make sure that new connections are not permitted anymore.
|
||||||
require.NoError(t, err)
|
// Note that this should be true not only after Shutdown has returned,
|
||||||
|
// but technically also as early as the Shutdown has closed the listener,
|
||||||
|
// i.e. during the shutdown and before the gracetime is over.
|
||||||
|
var testOk bool
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
loopConn, err := net.Dial("tcp", epAddr)
|
||||||
|
if err == nil {
|
||||||
|
loopConn.Close()
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(err.Error(), "connection refused") && !strings.HasSuffix(err.Error(), "reset by peer") {
|
||||||
|
t.Fatalf(`unexpected error: got %v, wanted "connection refused" or "reset by peer"`, err)
|
||||||
|
}
|
||||||
|
testOk = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !testOk {
|
||||||
|
t.Fatal("entry point never closed")
|
||||||
|
}
|
||||||
|
|
||||||
err = request.Write(conn)
|
// And make sure that the connection we had opened before shutting things down is still operational
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), request)
|
resp, err := http.ReadResponse(bufio.NewReader(conn), request)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startEntrypoint(entryPoint *TCPEntryPoint, router *tcp.Router) (net.Conn, error) {
|
||||||
|
go entryPoint.StartTCP(context.Background())
|
||||||
|
|
||||||
|
entryPoint.SwitchRouter(router)
|
||||||
|
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
var epStarted bool
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
conn, err = net.Dial("tcp", entryPoint.listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
epStarted = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !epStarted {
|
||||||
|
return nil, errors.New("entry point never started")
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadTimeoutWithoutFirstByte(t *testing.T) {
|
||||||
|
epConfig := &static.EntryPointsTransport{}
|
||||||
|
epConfig.SetDefaults()
|
||||||
|
epConfig.RespondingTimeouts.ReadTimeout = types.Duration(time.Second * 2)
|
||||||
|
|
||||||
|
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
||||||
|
Address: ":0",
|
||||||
|
Transport: epConfig,
|
||||||
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
router := &tcp.Router{}
|
||||||
|
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
conn, err := startEntrypoint(entryPoint, router)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b := make([]byte, 2048)
|
||||||
|
_, err := conn.Read(b)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
require.Equal(t, io.EOF, err)
|
||||||
|
case <-time.Tick(time.Second * 5):
|
||||||
|
t.Error("Timeout while read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadTimeoutWithFirstByte(t *testing.T) {
|
||||||
|
epConfig := &static.EntryPointsTransport{}
|
||||||
|
epConfig.SetDefaults()
|
||||||
|
epConfig.RespondingTimeouts.ReadTimeout = types.Duration(time.Second * 2)
|
||||||
|
|
||||||
|
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
||||||
|
Address: ":0",
|
||||||
|
Transport: epConfig,
|
||||||
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
router := &tcp.Router{}
|
||||||
|
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
conn, err := startEntrypoint(entryPoint, router)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = conn.Write([]byte("GET /some HTTP/1.1\r\n"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b := make([]byte, 2048)
|
||||||
|
_, err := conn.Read(b)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
require.Equal(t, io.EOF, err)
|
||||||
|
case <-time.Tick(time.Second * 5):
|
||||||
|
t.Error("Timeout while read")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,10 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
|
||||||
outReq.ProtoMajor = 1
|
outReq.ProtoMajor = 1
|
||||||
outReq.ProtoMinor = 1
|
outReq.ProtoMinor = 1
|
||||||
|
|
||||||
|
if _, ok := outReq.Header["User-Agent"]; !ok {
|
||||||
|
outReq.Header.Set("User-Agent", "")
|
||||||
|
}
|
||||||
|
|
||||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||||
if passHostHeader != nil && !*passHostHeader {
|
if passHostHeader != nil && !*passHostHeader {
|
||||||
outReq.Host = outReq.URL.Host
|
outReq.Host = outReq.URL.Host
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/log"
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
)
|
)
|
||||||
|
@ -34,7 +35,23 @@ func (r *Router) ServeTCP(conn WriteCloser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
br := bufio.NewReader(conn)
|
br := bufio.NewReader(conn)
|
||||||
serverName, tls, peeked := clientHelloServerName(br)
|
serverName, tls, peeked, err := clientHelloServerName(br)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove read/write deadline and delegate this to underlying tcp server (for now only handled by HTTP Server)
|
||||||
|
err = conn.SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
log.WithoutContext().Errorf("Error while setting read deadline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.SetWriteDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
log.WithoutContext().Errorf("Error while setting write deadline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if !tls {
|
if !tls {
|
||||||
switch {
|
switch {
|
||||||
case r.catchAllNoTLS != nil:
|
case r.catchAllNoTLS != nil:
|
||||||
|
@ -176,33 +193,34 @@ func (c *Conn) Read(p []byte) (n int, err error) {
|
||||||
// clientHelloServerName returns the SNI server name inside the TLS ClientHello,
|
// clientHelloServerName returns the SNI server name inside the TLS ClientHello,
|
||||||
// without consuming any bytes from br.
|
// without consuming any bytes from br.
|
||||||
// On any error, the empty string is returned.
|
// On any error, the empty string is returned.
|
||||||
func clientHelloServerName(br *bufio.Reader) (string, bool, string) {
|
func clientHelloServerName(br *bufio.Reader) (string, bool, string, error) {
|
||||||
hdr, err := br.Peek(1)
|
hdr, err := br.Peek(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
opErr, ok := err.(*net.OpError)
|
||||||
log.Errorf("Error while Peeking first byte: %s", err)
|
if err != io.EOF && (!ok || !opErr.Timeout()) {
|
||||||
|
log.WithoutContext().Errorf("Error while Peeking first byte: %s", err)
|
||||||
}
|
}
|
||||||
return "", false, ""
|
return "", false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordTypeHandshake = 0x16
|
const recordTypeHandshake = 0x16
|
||||||
if hdr[0] != recordTypeHandshake {
|
if hdr[0] != recordTypeHandshake {
|
||||||
// log.Errorf("Error not tls")
|
// log.Errorf("Error not tls")
|
||||||
return "", false, getPeeked(br) // Not TLS.
|
return "", false, getPeeked(br), nil // Not TLS.
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordHeaderLen = 5
|
const recordHeaderLen = 5
|
||||||
hdr, err = br.Peek(recordHeaderLen)
|
hdr, err = br.Peek(recordHeaderLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error while Peeking hello: %s", err)
|
log.Errorf("Error while Peeking hello: %s", err)
|
||||||
return "", false, getPeeked(br)
|
return "", false, getPeeked(br), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recLen := int(hdr[3])<<8 | int(hdr[4]) // ignoring version in hdr[1:3]
|
recLen := int(hdr[3])<<8 | int(hdr[4]) // ignoring version in hdr[1:3]
|
||||||
helloBytes, err := br.Peek(recordHeaderLen + recLen)
|
helloBytes, err := br.Peek(recordHeaderLen + recLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error while Hello: %s", err)
|
log.Errorf("Error while Hello: %s", err)
|
||||||
return "", true, getPeeked(br)
|
return "", true, getPeeked(br), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sni := ""
|
sni := ""
|
||||||
|
@ -214,7 +232,7 @@ func clientHelloServerName(br *bufio.Reader) (string, bool, string) {
|
||||||
})
|
})
|
||||||
_ = server.Handshake()
|
_ = server.Handshake()
|
||||||
|
|
||||||
return sni, true, getPeeked(br)
|
return sni, true, getPeeked(br), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPeeked(br *bufio.Reader) string {
|
func getPeeked(br *bufio.Reader) string {
|
||||||
|
|
Loading…
Reference in a new issue