Merge branch v2.3 into master

This commit is contained in:
Fernandez Ludovic 2020-09-08 10:48:09 +02:00
commit 6075f7e8fd
53 changed files with 1422 additions and 1374 deletions

View file

@ -1,3 +1,51 @@
## [v2.3.0-rc5](https://github.com/containous/traefik/tree/v2.3.0-rc5) (2020-09-07)
[All Commits](https://github.com/containous/traefik/compare/v2.3.0-rc4...v2.3.0-rc5)
**Enhancements:**
- **[k8s,k8s/ingress]** Add example for the IngressClass usage ([#7219](https://github.com/containous/traefik/pull/7219) by [SantoDE](https://github.com/SantoDE))
- **[plugins]** Improve plugins builder. ([#7255](https://github.com/containous/traefik/pull/7255) by [ldez](https://github.com/ldez))
**Bug fixes:**
- **[pilot,webui]** Add ability to dismiss pilot notification ([#7200](https://github.com/containous/traefik/pull/7200) by [matthieuh](https://github.com/matthieuh))
**Misc:**
- Merge current v2.2 branch into v2.3 ([#7249](https://github.com/containous/traefik/pull/7249) by [ldez](https://github.com/ldez))
- Merge current v2.2 branch into v2.3 ([#7218](https://github.com/containous/traefik/pull/7218) by [ldez](https://github.com/ldez))
## [v2.2.11](https://github.com/containous/traefik/tree/v2.2.11) (2020-09-07)
[All Commits](https://github.com/containous/traefik/compare/v2.2.10...v2.2.11)
**Bug fixes:**
- **[middleware]** fix: header middleware response writer. ([#7252](https://github.com/containous/traefik/pull/7252) by [ldez](https://github.com/ldez))
**Documentation:**
- **[healthcheck]** Clarified hostname documentation for load balancer healthcheck ([#7254](https://github.com/containous/traefik/pull/7254) by [AndrewSav](https://github.com/AndrewSav))
## [v2.2.10](https://github.com/containous/traefik/tree/v2.2.10) (2020-09-04)
[All Commits](https://github.com/containous/traefik/compare/v2.2.7...v2.2.10)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.0.1 ([#7238](https://github.com/containous/traefik/pull/7238) by [ldez](https://github.com/ldez))
- **[middleware]** Add missing IPStrategy struct tag for YAML ([#7233](https://github.com/containous/traefik/pull/7233) by [kevinpollet](https://github.com/kevinpollet))
- **[middleware]** Headers response modifier is directly applied by headers middleware ([#7230](https://github.com/containous/traefik/pull/7230) by [juliens](https://github.com/juliens))
- **[webui]** chore(webui): upgrade nodejs to Node current LTS ([#7125](https://github.com/containous/traefik/pull/7125) by [Slashgear](https://github.com/Slashgear))
**Documentation:**
- **[docker]** doc: fix dead link. ([#7172](https://github.com/containous/traefik/pull/7172) by [ldez](https://github.com/ldez))
- **[k8s]** kubernetes-crd: fix whitespace in configuration examples ([#7134](https://github.com/containous/traefik/pull/7134) by [NT-florianernst](https://github.com/NT-florianernst))
- **[k8s]** doc: replace underscore by hyphen for k8s metadata names. ([#7131](https://github.com/containous/traefik/pull/7131) by [ldez](https://github.com/ldez))
- **[logs]** doc: added tz section to access log ([#7178](https://github.com/containous/traefik/pull/7178) by [notsureifkevin](https://github.com/notsureifkevin))
- **[tls]** doc: Minor language improvement in TLS documentation ([#7206](https://github.com/containous/traefik/pull/7206) by [sharmarajdaksh](https://github.com/sharmarajdaksh))
- doc: fix typo in migration guide ([#7181](https://github.com/containous/traefik/pull/7181) by [ScuttleSE](https://github.com/ScuttleSE))
- doc: specify HostSNI rule removal only for HTTP routers ([#7237](https://github.com/containous/traefik/pull/7237) by [rtribotte](https://github.com/rtribotte))
- Reorder migrations for v2 minor upgrades ([#7214](https://github.com/containous/traefik/pull/7214) by [peschmae](https://github.com/peschmae))
- Harmonize docs ([#7124](https://github.com/containous/traefik/pull/7124) by [matthieuh](https://github.com/matthieuh))
## [v2.2.9](https://github.com/containous/traefik/tree/v2.2.9) (2020-09-04)
[All Commits](https://github.com/containous/traefik/compare/v2.2.8...v2.2.9)
Release canceled due to a bad tag.
## [v2.3.0-rc4](https://github.com/containous/traefik/tree/v2.3.0-rc4) (2020-08-19)
[All Commits](https://github.com/containous/traefik/compare/v2.3.0-rc3...v2.3.0-rc4)

View file

@ -37,6 +37,8 @@ DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INT
PRE_TARGET ?= build-dev-image
PLATFORM_URL := $(if $(PLATFORM_URL),$(PLATFORM_URL),"https://pilot.traefik.io")
default: binary
## Build Dev Docker image
@ -53,7 +55,7 @@ dist:
## Build WebUI Docker image
build-webui-image:
docker build -t traefik-webui -f webui/Dockerfile webui
docker build -t traefik-webui --build-arg ARG_PLATFORM_URL=$(PLATFORM_URL) -f webui/Dockerfile webui
## Generate WebUI
generate-webui: build-webui-image

View file

@ -308,9 +308,10 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/dyn) |
| [Dynu](https://www.dynu.com) | `dynu` | `DYNU_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dynu) |
| [EasyDNS](https://easydns.com/) | `easydns` | `EASYDNS_TOKEN`, `EASYDNS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/easydns) |
| [EdgeDNS](https://www.akamai.com/) | `edgedns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
| External Program | `exec` | `EXEC_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/exec) |
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) |
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/fastdns) |
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandi) |
| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandiv5) |
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | [Additional configuration](https://go-acme.github.io/lego/dns/glesys) |
@ -319,12 +320,12 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) |
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) |
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` [^1] | [Additional configuration](https://go-acme.github.io/lego/dns/httpreq) |
| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) |
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iij) |
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) |
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linodev4) |
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
| [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) |
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
| manual | `manual` | none, but you need to run Traefik interactively [^4], turn on debug log to see instructions and press <kbd>Enter</kbd>. | |
@ -336,7 +337,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/netcup) |
| [Netlify](https://www.netlify.com) | `netlify` | `NETLIFY_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/netlify) |
| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/nifcloud) |
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) |
| [NS1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) |
| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/otc) |
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) |
| [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/designate) |
@ -484,6 +485,37 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
!!! warning
For concurrency reasons, this file cannot be shared across multiple instances of Traefik.
### `preferredChain`
_Optional, Default=""_
Preferred chain to use.
If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
If no match, the default offered chain will be used.
```toml tab="File (TOML)"
[certificatesResolvers.myresolver.acme]
# ...
preferredChain = "ISRG Root X1"
# ...
```
```yaml tab="File (YAML)"
certificatesResolvers:
myresolver:
acme:
# ...
preferredChain: 'ISRG Root X1'
# ...
```
```bash tab="CLI"
# ...
--certificatesresolvers.myresolver.acme.preferredChain="ISRG Root X1"
# ...
```
## Fallback
If Let's Encrypt is not reachable, the following certificates will apply:

View file

@ -22,6 +22,16 @@
#
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
# Preferred chain to use.
#
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
# If no match, the default offered chain will be used.
#
# Optional
# Default: ""
#
# preferredChain = "ISRG Root X1"
# KeyType to use.
#
# Optional

View file

@ -21,6 +21,16 @@
#
--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
# Preferred chain to use.
#
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
# If no match, the default offered chain will be used.
#
# Optional
# Default: ""
#
--certificatesresolvers.myresolver.acme.preferredchain="ISRG Root X1"
# KeyType to use.
#
# Optional

View file

@ -24,6 +24,16 @@ certificatesResolvers:
#
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
# Preferred chain to use.
#
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
# If no match, the default offered chain will be used.
#
# Optional
# Default: ""
#
# preferredChain: 'ISRG Root X1'
# KeyType to use.
#
# Optional

View file

@ -183,9 +183,9 @@ spec:
### Maximum TLS Version
We discourages the use of this setting to disable TLS1.3.
We discourage the use of this setting to disable TLS1.3.
The right approach is to update the clients to support TLS1.3.
The recommended approach is to update the clients to support TLS1.3.
```toml tab="File (TOML)"
# Dynamic configuration
@ -316,7 +316,7 @@ spec:
### Strict SNI Checking
With strict SNI checking, Traefik won't allow connections from clients connections
With strict SNI checking enabled, Traefik won't allow connections from clients
that do not specify a server_name extension or don't match any certificate configured on the tlsOption.
```toml tab="File (TOML)"

View file

@ -501,7 +501,7 @@ To apply a redirection:
[http.routers.router1.tls]
[http.middlewares]
[http.middlewares.https-redirect.redirectScheme]
[http.middlewares.https_redirect.redirectScheme]
scheme = "https"
permanent = true
```

View file

@ -312,8 +312,8 @@ Since `v2.2.5` this global option has been removed, and you should not use it an
### HostSNI rule matcher removal
In `v2.2.2` we introduced a new rule matcher (`HostSNI`) which was allowing to match the Server Name Indication at the router level.
Since `v2.2.5` this rule has been removed, and you should not use it anymore.
In `v2.2.2` we introduced a new rule matcher (`HostSNI`) for HTTP routers which was allowing to match the Server Name Indication at the router level.
Since `v2.2.5` this rule has been removed for HTTP routers, and you should not use it anymore.
## v2.2 to v2.3

View file

@ -217,3 +217,32 @@ This allows the logs to be rotated and processed by an external program, such as
!!! warning
This does not work on Windows due to the lack of USR signals.
## Time Zones
Traefik will timestamp each log line in UTC time by default.
It is possible to configure the Traefik to timestamp in a specific timezone by ensuring the following configuration has been made in your environment:
1. Provide time zone data to `/etc/localtime` or `/usr/share/zoneinfo` (based on your distribution) or set the environment variable TZ to the desired timezone
2. Specify the field `StartLocal` by dropping the field named `StartUTC` (available on the default Common Log Format (CLF) as well as JSON)
Example utilizing Docker Compose:
```yaml
version: "3.7"
services:
traefik:
image: traefik:v2.2
environment:
- TZ=US/Alaska
command:
- --accesslog
- --accesslog.fields.names.StartUTC=drop
- --providers.docker
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```

View file

@ -264,7 +264,33 @@ Otherwise, Ingresses missing the annotation, having an empty value, or with the
the new `IngressClass` resource can be leveraged to identify Ingress objects that should be processed.
In that case, Traefik will look for an `IngressClass` in the cluster with the controller value equal to *traefik.io/ingress-controller*.
Please see [this article](https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/) for more information.
Please see [this article](https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/) for more information or the example below.
```yaml tab="IngressClass"
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-lb
spec:
controller: traefik.io/ingress-controller
```
```yaml tab="Ingress"
apiVersion: "networking.k8s.io/v1beta1"
kind: "Ingress"
metadata:
name: "example-ingress"
spec:
ingressClassName: "traefik-lb"
rules:
- host: "*.example.com"
http:
paths:
- path: "/example"
backend:
serviceName: "example-service"
servicePort: 80
```
### `ingressEndpoint`

View file

@ -222,7 +222,7 @@ http:
inFlightReq:
amount: 42
sourceCriterion:
ipstrategy:
ipStrategy:
depth: 42
excludedIPs:
- foobar
@ -263,7 +263,7 @@ http:
period: 42
burst: 42
sourceCriterion:
ipstrategy:
ipStrategy:
depth: 42
excludedIPs:
- foobar

View file

@ -81,6 +81,9 @@ HTTP challenge EntryPoint
`--certificatesresolvers.<name>.acme.keytype`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
`--certificatesresolvers.<name>.acme.preferredchain`:
Preferred chain to use.
`--certificatesresolvers.<name>.acme.storage`:
Storage to use. (Default: ```acme.json```)

View file

@ -81,6 +81,9 @@ HTTP challenge EntryPoint
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_KEYTYPE`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_PREFERREDCHAIN`:
Preferred chain to use.
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_STORAGE`:
Storage to use. (Default: ```acme.json```)

View file

@ -337,6 +337,7 @@
[certificatesResolvers.CertificateResolver0.acme]
email = "foobar"
caServer = "foobar"
preferredChain = "foobar"
storage = "foobar"
keyType = "foobar"
[certificatesResolvers.CertificateResolver0.acme.dnsChallenge]
@ -351,6 +352,7 @@
[certificatesResolvers.CertificateResolver1.acme]
email = "foobar"
caServer = "foobar"
preferredChain = "foobar"
storage = "foobar"
keyType = "foobar"
[certificatesResolvers.CertificateResolver1.acme.dnsChallenge]

View file

@ -353,6 +353,7 @@ certificatesResolvers:
acme:
email: foobar
caServer: foobar
preferredChain: foobar
storage: foobar
keyType: foobar
dnsChallenge:
@ -369,6 +370,7 @@ certificatesResolvers:
acme:
email: foobar
caServer: foobar
preferredChain: foobar
storage: foobar
keyType: foobar
dnsChallenge:

View file

@ -317,7 +317,7 @@ Below are the available options for the health check mechanism:
- `path` is appended to the server URL to set the health check endpoint.
- `scheme`, if defined, will replace the server URL `scheme` for the health check endpoint
- `hostname`, if defined, will replace the server URL `hostname` for the health check endpoint.
- `hostname`, if defined, will apply `Host` header `hostname` to the the health check request.
- `port`, if defined, will replace the server URL `port` for the health check endpoint.
- `interval` defines the frequency of the health check calls.
- `timeout` defines the maximum duration Traefik will wait for a health check request before considering the server failed (unhealthy).

12
go.mod
View file

@ -16,7 +16,7 @@ require (
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd
github.com/aws/aws-sdk-go v1.30.20
github.com/c0va23/go-proxyprotocol v0.9.1
github.com/cenkalti/backoff/v4 v4.0.0
github.com/cenkalti/backoff/v4 v4.0.2
github.com/containerd/containerd v1.3.2 // indirect
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
github.com/containous/yaegi v0.8.14
@ -37,7 +37,7 @@ require (
github.com/fatih/structs v1.1.0
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
github.com/go-acme/lego/v3 v3.8.0
github.com/go-acme/lego/v4 v4.0.1
github.com/go-check/check v0.0.0-00010101000000-000000000000
github.com/go-kit/kit v0.9.0
github.com/golang/protobuf v1.3.4
@ -53,10 +53,10 @@ require (
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591
github.com/magiconair/properties v1.8.1 // indirect
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
github.com/miekg/dns v1.1.27
github.com/miekg/dns v1.1.31
github.com/mitchellh/copystructure v1.0.0
github.com/mitchellh/hashstructure v1.0.0
github.com/mitchellh/mapstructure v1.3.2
github.com/mitchellh/mapstructure v1.3.3
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
@ -85,8 +85,8 @@ require (
go.elastic.co/apm v1.7.0
go.elastic.co/apm/module/apmot v1.7.0
golang.org/x/mod v0.2.0
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/grpc v1.27.1
gopkg.in/DataDog/dd-trace-go.v1 v1.19.0
gopkg.in/fsnotify.v1 v1.4.7

135
go.sum
View file

@ -98,12 +98,12 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd h1:UlQRt3CZdeD+WfDamDtdDDOu84CYbGIh9/B28TgzCZk=
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd/go.mod h1:2RUNONRAQ8bS1QcVJF3dYO/faiEro6NAAIQ6CqBkpD0=
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8 h1:6rJvj+NXjjauunLeS7uGy891F1cuAwsWKa9iGzTjz1s=
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8=
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18 h1:KyEv96ncdgOIJRTKMcWlIqM0umf8X3LQP6oOyg0hNsM=
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112 h1:E273ePcLllLIBGg5BHr3T0Fp1BJTvUyh5Y57ziSy81w=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.458 h1:UdFGeD4Eg6gZFQ7tLWdguNLpBTevJwBa97S0YunGy1k=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.458/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@ -125,8 +125,9 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34=
github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320=
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs=
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -135,8 +136,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.10.2 h1:VBodKICVPnwmDxstcW3biKcDSpFIfS/RELUXsZSBYK4=
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A=
github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
@ -178,8 +179,8 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpu/goacmedns v0.0.2 h1:hYAgjnPu7HogTgb8trqQouR/RrBgXq1TPBgmxbK9eRA=
github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
github.com/cpu/goacmedns v0.0.3 h1:QOeMpIEsIdm1LSASSswjaTf8CXmzcrgy5OeCfHjppA4=
github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -188,10 +189,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2 h1:G9/PqfhOrt8JXnw0DGTfVoOkKHDhOlEZqhE/cu+NvQM=
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnsimple/dnsimple-go v0.60.0 h1:N+q+ML1CZGf+5r4udu9Opy7WJNtOaFT9aM86Af9gLhk=
github.com/dnsimple/dnsimple-go v0.60.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
github.com/dnsimple/dnsimple-go v0.63.0 h1:0doY8VW/ckRIMTmOw4E1vwqo+bhtjDzvh1pU2ZteFGA=
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0 h1:hlGHcYGaaHs/yffSubcUKlp8TyV1v7qhcZZ5nGNQ2Fw=
github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
@ -239,8 +238,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI=
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
github.com/exoscale/egoscale v0.23.0 h1:hoUDzrO8yNoobNdnrRvlRFjfg3Ng0vQTrv6bXRJu6z0=
github.com/exoscale/egoscale v0.23.0/go.mod h1:hRo78jkjkCDKpivQdRBEpNYF5+cVpCJCPDg2/r45KaY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@ -255,8 +254,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v3 v3.8.0 h1:9OOEn54eZvEPRRdM7xiC5f7EBW0MlEeChr+kzlIhdN8=
github.com/go-acme/lego/v3 v3.8.0/go.mod h1:kYiHYgSRzb1l2NQPWvWvkVG5etNCusGFsZc2MTak3m0=
github.com/go-acme/lego/v4 v4.0.1 h1:vPwbTYfw5+fOaON9rWCN43iNrPw5cdJBhNMnA8oxBTM=
github.com/go-acme/lego/v4 v4.0.1/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@ -285,6 +284,8 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -353,8 +354,11 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.3.0 h1:6sjpKIpVwRIIwmcEGp+WwNovNsem+c+2vm6oxshRpL8=
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM=
github.com/gophercloud/gophercloud v0.7.0 h1:vhmQQEM2SbnGCg2/3EzQnQZ3V7+UCGy9s8exQCprNYg=
github.com/gophercloud/gophercloud v0.7.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+GmkaVO5sN+k8YONibXyDO8RlsC+1bs=
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
@ -397,8 +401,8 @@ github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo=
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
@ -482,10 +486,10 @@ github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807 h1:/7J1WDQd6Xn1Pr
github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807/go.mod h1:std11u6pTaNwryy0Hy1dTQNdHKka1jNpflEieKtv5VE=
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591 h1:+zkZyvOyHZZUnITx0oJxAG/2+YLOjmy8YMMa1aWyrs4=
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591/go.mod h1:EBQ0jeOrBpOTkquwjmJl4W6z5xqlf5oA2LZfTqRNcO0=
github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jvM=
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
github.com/liquidweb/liquidweb-go v1.6.0 h1:vIj1I/Wf97fUnyirD+bi6Y63c0GiXk9nKI1+sFFl3G0=
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
github.com/linode/linodego v0.21.0 h1:XykohqzVIV6hvjBn03cj7FGxYARFSrlfJodQrtHynqk=
github.com/linode/linodego v0.21.0/go.mod h1:UTpq1JUZD0CZsJ8rt+0CRkqbzrp1MbGakVPt2DXY5Mk=
github.com/liquidweb/liquidweb-go v1.6.1 h1:O51RbJo3ZEWFkZFfP32zIF6MCoZzwuuybuXsvZvVEEI=
github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20=
github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@ -503,13 +507,13 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@ -526,10 +530,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -549,13 +551,15 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y=
github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
github.com/nrdcg/desec v0.5.0 h1:foL7hqivYOMlv0qDhHXJtuuEXkqf0wW9EQMqyrt228g=
github.com/nrdcg/desec v0.5.0/go.mod h1:2ejvMazkav1VdDbv2HeQO7w+Ta1CGHqzQr27ZBYTuEQ=
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
github.com/nrdcg/goinwx v0.7.0 h1:j6JlOp0nNwtvaP09TvKqc9pktjH81nOad0+Gx9S1t9U=
github.com/nrdcg/goinwx v0.7.0/go.mod h1:4tKJOCi/1lTxuw9/yB2Ez0aojwtUCSkckjc22eALpqE=
github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -589,10 +593,10 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/oracle/oci-go-sdk v7.0.0+incompatible h1:oj5ESjXwwkFRdhZSnPlShvLWYdt/IZ65RQxveYM3maA=
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/oracle/oci-go-sdk v24.2.0+incompatible h1:T+OS7BSWy5vVKfngy6Ln5lzIO09nqVxNxHJY2Waivs8=
github.com/oracle/oci-go-sdk v24.2.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk=
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -649,8 +653,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sacloud/libsacloud v1.26.1 h1:td3Kd7lvpSAxxHEVpnaZ9goHmmhi0D/RfP0Rqqf/kek=
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY=
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
@ -691,16 +695,14 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g=
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8=
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/traefik/paerser v0.1.0 h1:B4v1tbvd8YnHsA7spwHKEWJoGrRP+2jYpIozsCMHhl0=
github.com/traefik/paerser v0.1.0/go.mod h1:yYnAgdEC2wJH5CgG75qGWC8SsFDEapg09o9RrA6FfrE=
github.com/transip/gotransip/v6 v6.0.2 h1:rOCMY607PYF+YvMHHtJt7eZRd0mx/uhyz6dsXWPmn+4=
github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
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/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
@ -713,15 +715,15 @@ github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0ob
github.com/unrolled/secure v1.0.7 h1:BcQHp3iKZyZCKj5gRqwQG+5urnGBF00wGgoPPwtheVQ=
github.com/unrolled/secure v1.0.7/go.mod h1:uGc1OcRF8gCVBA+ANksKmvM85Hka6SZtQIbrKc3sHS4=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM=
github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ=
github.com/vulcand/oxy v1.1.0 h1:DbBijGo1+6cFqR9jarkMxasdj0lgWwrrFtue6ijek4Q=
github.com/vulcand/oxy v1.1.0/go.mod h1:ADiMYHi8gkGl2987yQIzDRoXZilANF4WtKaQ92OppKY=
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
github.com/vultr/govultr v0.4.2 h1:9i8xKZ+xp6vwZ9raqHoBLzhB4wCnMj7nOQTj5YIRLWY=
github.com/vultr/govultr v0.4.2/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ=
github.com/vultr/govultr v0.5.0 h1:iQzYhzbokmpDARbvIkvTkoyS7WMH82zVTKAL1PZ4JOA=
github.com/vultr/govultr v0.5.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
@ -773,12 +775,13 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -811,7 +814,6 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -829,11 +831,13 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -841,6 +845,8 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -883,6 +889,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -892,6 +900,8 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -900,9 +910,10 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -930,6 +941,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -1001,6 +1013,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -1011,6 +1025,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
@ -1021,20 +1037,21 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc440
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc h1:GAcf+t0o8gdJAdSFYdE9wChu4bIyguMVqz0RHiFL5VY=
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
gopkg.in/ns1/ns1-go.v2 v2.4.2 h1:H6VnvLez0GjxXsXat6MUFmKuiMFuDaMBdGF9qtkmODo=
gopkg.in/ns1/ns1-go.v2 v2.4.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw=
gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY=
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View file

@ -302,7 +302,7 @@ type PassTLSClientCert struct {
// If none are set, the default is to use the request's remote address field.
// All fields are mutually exclusive.
type SourceCriterion struct {
IPStrategy *IPStrategy `json:"ipStrategy" toml:"ipStrategy, omitempty"`
IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty"`
RequestHeaderName string `json:"requestHeaderName,omitempty" toml:"requestHeaderName,omitempty" yaml:"requestHeaderName,omitempty"`
RequestHost bool `json:"requestHost,omitempty" toml:"requestHost,omitempty" yaml:"requestHost,omitempty"`
}

View file

@ -32,7 +32,7 @@ import (
"github.com/containous/traefik/v2/pkg/tracing/zipkin"
"github.com/containous/traefik/v2/pkg/types"
assetfs "github.com/elazarl/go-bindata-assetfs"
legolog "github.com/go-acme/lego/v3/log"
legolog "github.com/go-acme/lego/v4/log"
"github.com/sirupsen/logrus"
ptypes "github.com/traefik/paerser/types"
)

View file

@ -33,7 +33,7 @@ const (
)
type serviceBuilder interface {
BuildHTTP(ctx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error)
}
// customErrors is a middleware that provides the custom error pages..
@ -54,7 +54,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
return nil, err
}
backend, err := serviceBuilder.BuildHTTP(ctx, config.Service, nil)
backend, err := serviceBuilder.BuildHTTP(ctx, config.Service)
if err != nil {
return nil, err
}

View file

@ -150,7 +150,7 @@ type mockServiceBuilder struct {
handler http.Handler
}
func (m *mockServiceBuilder) BuildHTTP(_ context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
func (m *mockServiceBuilder) BuildHTTP(_ context.Context, _ string) (http.Handler, error) {
return m.handler, nil
}

View file

@ -0,0 +1,170 @@
package headers
import (
"context"
"net/http"
"strconv"
"strings"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log"
)
// Header is a middleware that helps setup a few basic security features.
// A single headerOptions struct can be provided to configure which features should be enabled,
// and the ability to override a few of the default values.
type Header struct {
next http.Handler
hasCustomHeaders bool
hasCorsHeaders bool
headers *dynamic.Headers
}
// NewHeader constructs a new header instance from supplied frontend header struct.
func NewHeader(next http.Handler, cfg dynamic.Headers) *Header {
hasCustomHeaders := cfg.HasCustomHeadersDefined()
hasCorsHeaders := cfg.HasCorsHeadersDefined()
ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName))
handleDeprecation(ctx, &cfg)
return &Header{
next: next,
headers: &cfg,
hasCustomHeaders: hasCustomHeaders,
hasCorsHeaders: hasCorsHeaders,
}
}
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Handle Cors headers and preflight if configured.
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
return
}
if s.hasCustomHeaders {
s.modifyCustomRequestHeaders(req)
}
// If there is a next, call it.
if s.next != nil {
s.next.ServeHTTP(newResponseModifier(rw, req, s.PostRequestModifyResponseHeaders), req)
}
}
// modifyCustomRequestHeaders sets or deletes custom request headers.
func (s *Header) modifyCustomRequestHeaders(req *http.Request) {
// Loop through Custom request headers
for header, value := range s.headers.CustomRequestHeaders {
switch {
case value == "":
req.Header.Del(header)
case strings.EqualFold(header, "Host"):
req.Host = value
default:
req.Header.Set(header, value)
}
}
}
// PostRequestModifyResponseHeaders set or delete response headers.
// This method is called AFTER the response is generated from the backend
// and can merge/override headers from the backend response.
func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error {
// Loop through Custom response headers
for header, value := range s.headers.CustomResponseHeaders {
if value == "" {
res.Header.Del(header)
} else {
res.Header.Set(header, value)
}
}
if res != nil && res.Request != nil {
originHeader := res.Request.Header.Get("Origin")
allowed, match := s.isOriginAllowed(originHeader)
if allowed {
res.Header.Set("Access-Control-Allow-Origin", match)
}
}
if s.headers.AccessControlAllowCredentials {
res.Header.Set("Access-Control-Allow-Credentials", "true")
}
if len(s.headers.AccessControlExposeHeaders) > 0 {
exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",")
res.Header.Set("Access-Control-Expose-Headers", exposeHeaders)
}
if !s.headers.AddVaryHeader {
return nil
}
varyHeader := res.Header.Get("Vary")
if varyHeader == "Origin" {
return nil
}
if varyHeader != "" {
varyHeader += ","
}
varyHeader += "Origin"
res.Header.Set("Vary", varyHeader)
return nil
}
// processCorsHeaders processes the incoming request,
// and returns if it is a preflight request.
// If not a preflight, it handles the preRequestModifyCorsResponseHeaders.
func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) bool {
if !s.hasCorsHeaders {
return false
}
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
originHeader := req.Header.Get("Origin")
if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
// and Origin headers, then it is a CORS preflight request,
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
if s.headers.AccessControlAllowCredentials {
rw.Header().Set("Access-Control-Allow-Credentials", "true")
}
allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",")
if allowHeaders != "" {
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
}
allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",")
if allowMethods != "" {
rw.Header().Set("Access-Control-Allow-Methods", allowMethods)
}
allowed, match := s.isOriginAllowed(originHeader)
if allowed {
rw.Header().Set("Access-Control-Allow-Origin", match)
}
rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge)))
return true
}
return false
}
func (s *Header) isOriginAllowed(origin string) (bool, string) {
for _, item := range s.headers.AccessControlAllowOriginList {
if item == "*" || item == origin {
return true, item
}
}
return false, ""
}

View file

@ -0,0 +1,492 @@
package headers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/stretchr/testify/assert"
)
func TestNewHeader_customRequestHeader(t *testing.T) {
testCases := []struct {
desc string
cfg dynamic.Headers
expected http.Header
}{
{
desc: "adds a header",
cfg: dynamic.Headers{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "test_request",
},
},
expected: http.Header{"Foo": []string{"bar"}, "X-Custom-Request-Header": []string{"test_request"}},
},
{
desc: "delete a header",
cfg: dynamic.Headers{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "",
"Foo": "",
},
},
expected: http.Header{},
},
{
desc: "override a header",
cfg: dynamic.Headers{
CustomRequestHeaders: map[string]string{
"Foo": "test",
},
},
expected: http.Header{"Foo": []string{"test"}},
},
}
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
mid := NewHeader(emptyHandler, test.cfg)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
req.Header.Set("Foo", "bar")
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, http.StatusOK, rw.Code)
assert.Equal(t, test.expected, req.Header)
})
}
}
func TestNewHeader_customRequestHeader_Host(t *testing.T) {
testCases := []struct {
desc string
customHeaders map[string]string
expectedHost string
expectedURLHost string
}{
{
desc: "standard Host header",
customHeaders: map[string]string{},
expectedHost: "example.org",
expectedURLHost: "example.org",
},
{
desc: "custom Host header",
customHeaders: map[string]string{
"Host": "example.com",
},
expectedHost: "example.com",
expectedURLHost: "example.org",
},
}
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(emptyHandler, dynamic.Headers{CustomRequestHeaders: test.customHeaders})
req := httptest.NewRequest(http.MethodGet, "http://example.org/foo", nil)
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, http.StatusOK, rw.Code)
assert.Equal(t, test.expectedHost, req.Host)
assert.Equal(t, test.expectedURLHost, req.URL.Host)
})
}
}
func TestNewHeader_CORSPreflights(t *testing.T) {
testCases := []struct {
desc string
cfg dynamic.Headers
requestHeaders http.Header
expected http.Header
}{
{
desc: "Test Simple Preflight",
cfg: dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AccessControlMaxAge: 600,
},
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
},
},
{
desc: "Wildcard origin Preflight",
cfg: dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlMaxAge: 600,
},
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
},
},
{
desc: "Allow Credentials Preflight",
cfg: dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowCredentials: true,
AccessControlMaxAge: 600,
},
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Credentials": {"true"},
},
},
{
desc: "Allow Headers Preflight",
cfg: dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
AccessControlMaxAge: 600,
},
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
},
},
{
desc: "No Request Headers Preflight",
cfg: dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
AccessControlMaxAge: 600,
},
requestHeaders: map[string][]string{
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
},
},
}
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(emptyHandler, test.cfg)
req := httptest.NewRequest(http.MethodOptions, "/foo", nil)
req.Header = test.requestHeaders
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}
func TestNewHeader_CORSResponses(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
testCases := []struct {
desc string
next http.Handler
cfg dynamic.Headers
requestHeaders http.Header
expected http.Header
}{
{
desc: "Test Simple Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
},
},
{
desc: "Wildcard origin Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Empty origin Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
},
requestHeaders: map[string][]string{},
expected: map[string][]string{},
},
{
desc: "Not Defined origin Request",
next: emptyHandler,
requestHeaders: map[string][]string{},
expected: map[string][]string{},
},
{
desc: "Allow Credentials Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowCredentials: true,
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Allow-Credentials": {"true"},
},
},
{
desc: "Expose Headers Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Expose-Headers": {"origin,X-Forwarded-For"},
},
},
{
desc: "Test Simple Request with Vary Headers",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AddVaryHeader: true,
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Origin"},
},
},
{
desc: "Test Simple Request with Vary Headers and non-empty response",
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// nonEmptyHandler
w.Header().Set("Vary", "Testing")
w.WriteHeader(http.StatusOK)
}),
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AddVaryHeader: true,
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Testing,Origin"},
},
},
{
desc: "Test Simple Request with Vary Headers and existing vary:origin response",
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// existingOriginHandler
w.Header().Set("Vary", "Origin")
w.WriteHeader(http.StatusOK)
}),
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AddVaryHeader: true,
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Origin"},
},
},
{
desc: "Test Simple Request with non-empty response: set ACAO",
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// existingAccessControlAllowOriginHandlerSet
w.Header().Set("Access-Control-Allow-Origin", "http://foo.bar.org")
w.WriteHeader(http.StatusOK)
}),
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Test Simple Request with non-empty response: add ACAO",
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// existingAccessControlAllowOriginHandlerAdd
w.Header().Add("Access-Control-Allow-Origin", "http://foo.bar.org")
w.WriteHeader(http.StatusOK)
}),
cfg: dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Test Simple CustomRequestHeaders Not Hijacked by CORS",
next: emptyHandler,
cfg: dynamic.Headers{
CustomRequestHeaders: map[string]string{"foo": "bar"},
},
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(test.next, test.cfg)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
req.Header = test.requestHeaders
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}
func TestNewHeader_customResponseHeaders(t *testing.T) {
testCases := []struct {
desc string
config map[string]string
expected http.Header
}{
{
desc: "Test Simple Response",
config: map[string]string{
"Testing": "foo",
"Testing2": "bar",
},
expected: map[string][]string{
"Foo": {"bar"},
"Testing": {"foo"},
"Testing2": {"bar"},
},
},
{
desc: "empty Custom Header",
config: map[string]string{
"Testing": "foo",
"Testing2": "",
},
expected: map[string][]string{
"Foo": {"bar"},
"Testing": {"foo"},
},
},
{
desc: "Deleting Custom Header",
config: map[string]string{
"Testing": "foo",
"Foo": "",
},
expected: map[string][]string{
"Testing": {"foo"},
},
},
}
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Foo", "bar")
w.WriteHeader(http.StatusOK)
})
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(emptyHandler, dynamic.Headers{CustomResponseHeaders: test.config})
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}

View file

@ -5,15 +5,12 @@ import (
"context"
"errors"
"net/http"
"strconv"
"strings"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/middlewares"
"github.com/containous/traefik/v2/pkg/tracing"
"github.com/opentracing/opentracing-go/ext"
"github.com/unrolled/secure"
)
const (
@ -77,204 +74,3 @@ func (h *headers) GetTracingInformation() (string, ext.SpanKindEnum) {
func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
h.handler.ServeHTTP(rw, req)
}
type secureHeader struct {
next http.Handler
secure *secure.Secure
}
// newSecure constructs a new secure instance with supplied options.
func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader {
opt := secure.Options{
BrowserXssFilter: cfg.BrowserXSSFilter,
ContentTypeNosniff: cfg.ContentTypeNosniff,
ForceSTSHeader: cfg.ForceSTSHeader,
FrameDeny: cfg.FrameDeny,
IsDevelopment: cfg.IsDevelopment,
SSLRedirect: cfg.SSLRedirect,
SSLForceHost: cfg.SSLForceHost,
SSLTemporaryRedirect: cfg.SSLTemporaryRedirect,
STSIncludeSubdomains: cfg.STSIncludeSubdomains,
STSPreload: cfg.STSPreload,
ContentSecurityPolicy: cfg.ContentSecurityPolicy,
CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
PublicKey: cfg.PublicKey,
ReferrerPolicy: cfg.ReferrerPolicy,
SSLHost: cfg.SSLHost,
AllowedHosts: cfg.AllowedHosts,
HostsProxyHeaders: cfg.HostsProxyHeaders,
SSLProxyHeaders: cfg.SSLProxyHeaders,
STSSeconds: cfg.STSSeconds,
FeaturePolicy: cfg.FeaturePolicy,
SecureContextKey: contextKey,
}
return &secureHeader{
next: next,
secure: secure.New(opt),
}
}
func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
s.secure.HandlerFuncWithNextForRequestOnly(rw, req, s.next.ServeHTTP)
}
// Header is a middleware that helps setup a few basic security features.
// A single headerOptions struct can be provided to configure which features should be enabled,
// and the ability to override a few of the default values.
type Header struct {
next http.Handler
hasCustomHeaders bool
hasCorsHeaders bool
headers *dynamic.Headers
}
// NewHeader constructs a new header instance from supplied frontend header struct.
func NewHeader(next http.Handler, cfg dynamic.Headers) *Header {
hasCustomHeaders := cfg.HasCustomHeadersDefined()
hasCorsHeaders := cfg.HasCorsHeadersDefined()
ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName))
handleDeprecation(ctx, &cfg)
return &Header{
next: next,
headers: &cfg,
hasCustomHeaders: hasCustomHeaders,
hasCorsHeaders: hasCorsHeaders,
}
}
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Handle Cors headers and preflight if configured.
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
return
}
if s.hasCustomHeaders {
s.modifyCustomRequestHeaders(req)
}
// If there is a next, call it.
if s.next != nil {
s.next.ServeHTTP(rw, req)
}
}
// modifyCustomRequestHeaders sets or deletes custom request headers.
func (s *Header) modifyCustomRequestHeaders(req *http.Request) {
// Loop through Custom request headers
for header, value := range s.headers.CustomRequestHeaders {
switch {
case value == "":
req.Header.Del(header)
case strings.EqualFold(header, "Host"):
req.Host = value
default:
req.Header.Set(header, value)
}
}
}
// PostRequestModifyResponseHeaders set or delete response headers.
// This method is called AFTER the response is generated from the backend
// and can merge/override headers from the backend response.
func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error {
// Loop through Custom response headers
for header, value := range s.headers.CustomResponseHeaders {
if value == "" {
res.Header.Del(header)
} else {
res.Header.Set(header, value)
}
}
if res != nil && res.Request != nil {
originHeader := res.Request.Header.Get("Origin")
allowed, match := s.isOriginAllowed(originHeader)
if allowed {
res.Header.Set("Access-Control-Allow-Origin", match)
}
}
if s.headers.AccessControlAllowCredentials {
res.Header.Set("Access-Control-Allow-Credentials", "true")
}
if len(s.headers.AccessControlExposeHeaders) > 0 {
exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",")
res.Header.Set("Access-Control-Expose-Headers", exposeHeaders)
}
if !s.headers.AddVaryHeader {
return nil
}
varyHeader := res.Header.Get("Vary")
if varyHeader == "Origin" {
return nil
}
if varyHeader != "" {
varyHeader += ","
}
varyHeader += "Origin"
res.Header.Set("Vary", varyHeader)
return nil
}
// processCorsHeaders processes the incoming request,
// and returns if it is a preflight request.
// If not a preflight, it handles the preRequestModifyCorsResponseHeaders.
func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) bool {
if !s.hasCorsHeaders {
return false
}
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
originHeader := req.Header.Get("Origin")
if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
// and Origin headers, then it is a CORS preflight request,
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
if s.headers.AccessControlAllowCredentials {
rw.Header().Set("Access-Control-Allow-Credentials", "true")
}
allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",")
if allowHeaders != "" {
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
}
allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",")
if allowMethods != "" {
rw.Header().Set("Access-Control-Allow-Methods", allowMethods)
}
allowed, match := s.isOriginAllowed(originHeader)
if allowed {
rw.Header().Set("Access-Control-Allow-Origin", match)
}
rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge)))
return true
}
return false
}
func (s *Header) isOriginAllowed(origin string) (bool, string) {
for _, item := range s.headers.AccessControlAllowOriginList {
if item == "*" || item == origin {
return true, item
}
}
return false, ""
}

View file

@ -9,104 +9,21 @@ import (
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/testhelpers"
"github.com/containous/traefik/v2/pkg/tracing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCustomRequestHeader(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
func TestNew_withoutOptions(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
header := NewHeader(emptyHandler, dynamic.Headers{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "test_request",
},
})
mid, err := New(context.Background(), next, dynamic.Headers{}, "testing")
require.Errorf(t, err, "headers configuration not valid")
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
header.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code)
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
assert.Nil(t, mid)
}
func TestCustomRequestHeader_Host(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
testCases := []struct {
desc string
customHeaders map[string]string
expectedHost string
expectedURLHost string
}{
{
desc: "standard Host header",
customHeaders: map[string]string{},
expectedHost: "example.org",
expectedURLHost: "example.org",
},
{
desc: "custom Host header",
customHeaders: map[string]string{
"Host": "example.com",
},
expectedHost: "example.com",
expectedURLHost: "example.org",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
header := NewHeader(emptyHandler, dynamic.Headers{
CustomRequestHeaders: test.customHeaders,
})
res := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodGet, "http://example.org/foo", nil)
require.NoError(t, err)
header.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code)
assert.Equal(t, test.expectedHost, req.Host)
assert.Equal(t, test.expectedURLHost, req.URL.Host)
})
}
}
func TestCustomRequestHeaderEmptyValue(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
header := NewHeader(emptyHandler, dynamic.Headers{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "test_request",
},
})
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
header.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code)
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
header = NewHeader(emptyHandler, dynamic.Headers{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "",
},
})
header.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code)
assert.Equal(t, "", req.Header.Get("X-Custom-Request-Header"))
}
func TestSecureHeader(t *testing.T) {
func TestNew_allowedHosts(t *testing.T) {
testCases := []struct {
desc string
fromHost string
@ -129,10 +46,13 @@ func TestSecureHeader(t *testing.T) {
},
}
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
header, err := New(context.Background(), emptyHandler, dynamic.Headers{
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
cfg := dynamic.Headers{
AllowedHosts: []string{"foo.com", "bar.com"},
}, "foo")
}
mid, err := New(context.Background(), emptyHandler, cfg, "foo")
require.NoError(t, err)
for _, test := range testCases {
@ -140,491 +60,54 @@ func TestSecureHeader(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
req.Host = test.fromHost
header.ServeHTTP(res, req)
assert.Equal(t, test.expected, res.Code)
})
}
}
func TestSSLForceHost(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
_, _ = rw.Write([]byte("OK"))
})
testCases := []struct {
desc string
host string
secureMiddleware *secureHeader
expected int
}{
{
desc: "http should return a 301",
host: "http://powpow.example.com",
secureMiddleware: newSecure(next, dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
"mymiddleware",
),
expected: http.StatusMovedPermanently,
},
{
desc: "http sub domain should return a 301",
host: "http://www.powpow.example.com",
secureMiddleware: newSecure(next, dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
"mymiddleware",
),
expected: http.StatusMovedPermanently,
},
{
desc: "https should return a 200",
host: "https://powpow.example.com",
secureMiddleware: newSecure(next, dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
"mymiddleware",
),
expected: http.StatusOK,
},
{
desc: "https sub domain should return a 301",
host: "https://www.powpow.example.com",
secureMiddleware: newSecure(next, dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
"mymiddleware",
),
expected: http.StatusMovedPermanently,
},
{
desc: "http without force host and sub domain should return a 301",
host: "http://www.powpow.example.com",
secureMiddleware: newSecure(next, dynamic.Headers{
SSLRedirect: true,
SSLForceHost: false,
SSLHost: "powpow.example.com",
},
"mymiddleware",
),
expected: http.StatusMovedPermanently,
},
{
desc: "https without force host and sub domain should return a 301",
host: "https://www.powpow.example.com",
secureMiddleware: newSecure(next, dynamic.Headers{
SSLRedirect: true,
SSLForceHost: false,
SSLHost: "powpow.example.com",
},
"mymiddleware",
),
expected: http.StatusOK,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, test.host, nil)
rw := httptest.NewRecorder()
test.secureMiddleware.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().StatusCode)
mid.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Code)
})
}
}
func TestCORSPreflights(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
func TestNew_customHeaders(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
testCases := []struct {
desc string
header *Header
requestHeaders http.Header
expected http.Header
}{
{
desc: "Test Simple Preflight",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AccessControlMaxAge: 600,
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
},
},
{
desc: "Wildcard origin Preflight",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlMaxAge: 600,
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
},
},
{
desc: "Allow Credentials Preflight",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowCredentials: true,
AccessControlMaxAge: 600,
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Credentials": {"true"},
},
},
{
desc: "Allow Headers Preflight",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
AccessControlMaxAge: 600,
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
},
},
{
desc: "No Request Headers Preflight",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
AccessControlMaxAge: 600,
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodOptions, "/foo", nil)
req.Header = test.requestHeaders
rw := httptest.NewRecorder()
test.header.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}
func TestEmptyHeaderObject(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
_, err := New(context.Background(), next, dynamic.Headers{}, "testing")
require.Errorf(t, err, "headers configuration not valid")
}
func TestCustomHeaderHandler(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
header, _ := New(context.Background(), next, dynamic.Headers{
cfg := dynamic.Headers{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "test_request",
},
}, "testing")
CustomResponseHeaders: map[string]string{
"X-Custom-Response-Header": "test_response",
},
}
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
mid, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err)
header.ServeHTTP(res, req)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
assert.Equal(t, http.StatusOK, res.Code)
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, http.StatusOK, rw.Code)
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
assert.Equal(t, "test_response", rw.Header().Get("X-Custom-Response-Header"))
}
func TestGetTracingInformation(t *testing.T) {
func Test_headers_getTracingInformation(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
header := &headers{
mid := &headers{
handler: next,
name: "testing",
}
name, trace := header.GetTracingInformation()
name, trace := mid.GetTracingInformation()
assert.Equal(t, "testing", name)
assert.Equal(t, tracing.SpanKindNoneEnum, trace)
}
func TestCORSResponses(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
nonEmptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Testing") })
existingOriginHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Origin") })
existingAccessControlAllowOriginHandlerSet := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "http://foo.bar.org")
})
existingAccessControlAllowOriginHandlerAdd := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "http://foo.bar.org")
})
testCases := []struct {
desc string
header *Header
requestHeaders http.Header
expected http.Header
}{
{
desc: "Test Simple Request",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
},
},
{
desc: "Wildcard origin Request",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Empty origin Request",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
}),
requestHeaders: map[string][]string{},
expected: map[string][]string{},
},
{
desc: "Not Defined origin Request",
header: NewHeader(emptyHandler, dynamic.Headers{}),
requestHeaders: map[string][]string{},
expected: map[string][]string{},
},
{
desc: "Allow Credentials Request",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
AccessControlAllowCredentials: true,
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Allow-Credentials": {"true"},
},
},
{
desc: "Expose Headers Request",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"},
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Expose-Headers": {"origin,X-Forwarded-For"},
},
},
{
desc: "Test Simple Request with Vary Headers",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AddVaryHeader: true,
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Origin"},
},
},
{
desc: "Test Simple Request with Vary Headers and non-empty response",
header: NewHeader(nonEmptyHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AddVaryHeader: true,
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Testing,Origin"},
},
},
{
desc: "Test Simple Request with Vary Headers and existing vary:origin response",
header: NewHeader(existingOriginHandler, dynamic.Headers{
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
AddVaryHeader: true,
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Origin"},
},
},
{
desc: "Test Simple Request with non-empty response: set ACAO",
header: NewHeader(existingAccessControlAllowOriginHandlerSet, dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Test Simple Request with non-empty response: add ACAO",
header: NewHeader(existingAccessControlAllowOriginHandlerAdd, dynamic.Headers{
AccessControlAllowOriginList: []string{"*"},
}),
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Test Simple CustomRequestHeaders Not Hijacked by CORS",
header: NewHeader(emptyHandler, dynamic.Headers{
CustomRequestHeaders: map[string]string{"foo": "bar"},
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
req.Header = test.requestHeaders
rw := httptest.NewRecorder()
test.header.ServeHTTP(rw, req)
res := rw.Result()
res.Request = req
err := test.header.PostRequestModifyResponseHeaders(res)
require.NoError(t, err)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}
func TestCustomResponseHeaders(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
testCases := []struct {
desc string
header *Header
expected http.Header
}{
{
desc: "Test Simple Response",
header: NewHeader(emptyHandler, dynamic.Headers{
CustomResponseHeaders: map[string]string{
"Testing": "foo",
"Testing2": "bar",
},
}),
expected: map[string][]string{
"Testing": {"foo"},
"Testing2": {"bar"},
},
},
{
desc: "Deleting Custom Header",
header: NewHeader(emptyHandler, dynamic.Headers{
CustomResponseHeaders: map[string]string{
"Testing": "foo",
"Testing2": "",
},
}),
expected: map[string][]string{
"Testing": {"foo"},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
rw := httptest.NewRecorder()
test.header.ServeHTTP(rw, req)
err := test.header.PostRequestModifyResponseHeaders(rw.Result())
require.NoError(t, err)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}

View file

@ -0,0 +1,94 @@
package headers
import (
"bufio"
"fmt"
"net"
"net/http"
"github.com/containous/traefik/v2/pkg/log"
)
type responseModifier struct {
r *http.Request
w http.ResponseWriter
headersSent bool // whether headers have already been sent
code int // status code, must default to 200
modifier func(*http.Response) error // can be nil
modified bool // whether modifier has already been called for the current request
modifierErr error // returned by modifier call
}
// modifier can be nil.
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
return &responseModifier{
r: r,
w: w,
modifier: modifier,
code: http.StatusOK,
}
}
func (w *responseModifier) WriteHeader(code int) {
if w.headersSent {
return
}
defer func() {
w.code = code
w.headersSent = true
}()
if w.modifier == nil || w.modified {
w.w.WriteHeader(code)
return
}
resp := http.Response{
Header: w.w.Header(),
Request: w.r,
}
if err := w.modifier(&resp); err != nil {
w.modifierErr = err
// we are propagating when we are called in Write, but we're logging anyway,
// because we could be called from another place which does not take care of
// checking w.modifierErr.
log.WithoutContext().Errorf("Error when applying response modifier: %v", err)
w.w.WriteHeader(http.StatusInternalServerError)
return
}
w.modified = true
w.w.WriteHeader(code)
}
func (w *responseModifier) Header() http.Header {
return w.w.Header()
}
func (w *responseModifier) Write(b []byte) (int, error) {
w.WriteHeader(w.code)
if w.modifierErr != nil {
return 0, w.modifierErr
}
return w.w.Write(b)
}
// Hijack hijacks the connection.
func (w *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if h, ok := w.w.(http.Hijacker); ok {
return h.Hijack()
}
return nil, nil, fmt.Errorf("not a hijacker: %T", w.w)
}
// Flush sends any buffered data to the client.
func (w *responseModifier) Flush() {
if flusher, ok := w.w.(http.Flusher); ok {
flusher.Flush()
}
}

View file

@ -0,0 +1,54 @@
package headers
import (
"net/http"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/unrolled/secure"
)
type secureHeader struct {
next http.Handler
secure *secure.Secure
cfg dynamic.Headers
}
// newSecure constructs a new secure instance with supplied options.
func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader {
opt := secure.Options{
BrowserXssFilter: cfg.BrowserXSSFilter,
ContentTypeNosniff: cfg.ContentTypeNosniff,
ForceSTSHeader: cfg.ForceSTSHeader,
FrameDeny: cfg.FrameDeny,
IsDevelopment: cfg.IsDevelopment,
SSLRedirect: cfg.SSLRedirect,
SSLForceHost: cfg.SSLForceHost,
SSLTemporaryRedirect: cfg.SSLTemporaryRedirect,
STSIncludeSubdomains: cfg.STSIncludeSubdomains,
STSPreload: cfg.STSPreload,
ContentSecurityPolicy: cfg.ContentSecurityPolicy,
CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
PublicKey: cfg.PublicKey,
ReferrerPolicy: cfg.ReferrerPolicy,
SSLHost: cfg.SSLHost,
AllowedHosts: cfg.AllowedHosts,
HostsProxyHeaders: cfg.HostsProxyHeaders,
SSLProxyHeaders: cfg.SSLProxyHeaders,
STSSeconds: cfg.STSSeconds,
FeaturePolicy: cfg.FeaturePolicy,
SecureContextKey: contextKey,
}
return &secureHeader{
next: next,
secure: secure.New(opt),
cfg: cfg,
}
}
func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
s.secure.HandlerFuncWithNextForRequestOnly(rw, req, func(writer http.ResponseWriter, request *http.Request) {
s.next.ServeHTTP(newResponseModifier(writer, request, s.secure.ModifyResponseHeaders), request)
})
}

View file

@ -0,0 +1,191 @@
package headers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/stretchr/testify/assert"
)
// Middleware tests based on https://github.com/unrolled/secure
func Test_newSecure_sslForceHost(t *testing.T) {
type expected struct {
statusCode int
location string
}
testCases := []struct {
desc string
host string
cfg dynamic.Headers
expected
}{
{
desc: "http should return a 301",
host: "http://powpow.example.com",
cfg: dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
expected: expected{
statusCode: http.StatusMovedPermanently,
location: "https://powpow.example.com",
},
},
{
desc: "http sub domain should return a 301",
host: "http://www.powpow.example.com",
cfg: dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
expected: expected{
statusCode: http.StatusMovedPermanently,
location: "https://powpow.example.com",
},
},
{
desc: "https should return a 200",
host: "https://powpow.example.com",
cfg: dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
expected: expected{statusCode: http.StatusOK},
},
{
desc: "https sub domain should return a 301",
host: "https://www.powpow.example.com",
cfg: dynamic.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
},
expected: expected{
statusCode: http.StatusMovedPermanently,
location: "https://powpow.example.com",
},
},
{
desc: "http without force host and sub domain should return a 301",
host: "http://www.powpow.example.com",
cfg: dynamic.Headers{
SSLRedirect: true,
SSLForceHost: false,
SSLHost: "powpow.example.com",
},
expected: expected{
statusCode: http.StatusMovedPermanently,
location: "https://powpow.example.com",
},
},
{
desc: "https without force host and sub domain should return a 301",
host: "https://www.powpow.example.com",
cfg: dynamic.Headers{
SSLRedirect: true,
SSLForceHost: false,
SSLHost: "powpow.example.com",
},
expected: expected{statusCode: http.StatusOK},
},
}
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
_, _ = rw.Write([]byte("OK"))
})
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := newSecure(next, test.cfg, "mymiddleware")
req := httptest.NewRequest(http.MethodGet, test.host, nil)
rw := httptest.NewRecorder()
mid.ServeHTTP(rw, req)
assert.Equal(t, test.expected.statusCode, rw.Result().StatusCode)
assert.Equal(t, test.expected.location, rw.Header().Get("Location"))
})
}
}
func Test_newSecure_modifyResponse(t *testing.T) {
testCases := []struct {
desc string
cfg dynamic.Headers
expected http.Header
}{
{
desc: "FeaturePolicy",
cfg: dynamic.Headers{
FeaturePolicy: "vibrate 'none';",
},
expected: http.Header{"Feature-Policy": []string{"vibrate 'none';"}},
},
{
desc: "STSSeconds",
cfg: dynamic.Headers{
STSSeconds: 1,
ForceSTSHeader: true,
},
expected: http.Header{"Strict-Transport-Security": []string{"max-age=1"}},
},
{
desc: "STSSeconds and STSPreload",
cfg: dynamic.Headers{
STSSeconds: 1,
ForceSTSHeader: true,
STSPreload: true,
},
expected: http.Header{"Strict-Transport-Security": []string{"max-age=1; preload"}},
},
{
desc: "CustomFrameOptionsValue",
cfg: dynamic.Headers{
CustomFrameOptionsValue: "foo",
},
expected: http.Header{"X-Frame-Options": []string{"foo"}},
},
{
desc: "FrameDeny",
cfg: dynamic.Headers{
FrameDeny: true,
},
expected: http.Header{"X-Frame-Options": []string{"DENY"}},
},
{
desc: "ContentTypeNosniff",
cfg: dynamic.Headers{
ContentTypeNosniff: true,
},
expected: http.Header{"X-Content-Type-Options": []string{"nosniff"}},
},
}
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
secure := newSecure(emptyHandler, test.cfg, "mymiddleware")
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
rw := httptest.NewRecorder()
secure.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().Header)
})
}
}

View file

@ -15,6 +15,9 @@ import (
const devPluginName = "dev"
// Constructor creates a plugin handler.
type Constructor func(context.Context, http.Handler) (http.Handler, error)
// pluginContext The static part of a plugin configuration.
type pluginContext struct {
// GoPath plugin's GOPATH
@ -89,7 +92,7 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, devPlugin *DevPlu
}
// Build builds a plugin.
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (*Middleware, error) {
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) {
if b.descriptors == nil {
return nil, fmt.Errorf("plugin: no plugin definition in the static configuration: %s", pName)
}
@ -99,7 +102,12 @@ func (b Builder) Build(pName string, config map[string]interface{}, middlewareNa
return nil, fmt.Errorf("plugin: unknown plugin type: %s", pName)
}
return newMiddleware(descriptor, config, middlewareName)
m, err := newMiddleware(descriptor, config, middlewareName)
if err != nil {
return nil, err
}
return m.NewHandler, err
}
// Middleware is a HTTP handler plugin wrapper.

View file

@ -8,8 +8,8 @@ import (
"crypto/x509"
"github.com/containous/traefik/v2/pkg/log"
"github.com/go-acme/lego/v3/certcrypto"
"github.com/go-acme/lego/v3/registration"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/registration"
)
// Account is used to store lets encrypt registration info.

View file

@ -9,8 +9,8 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/challenge/http01"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/gorilla/mux"
)

View file

@ -5,8 +5,8 @@ import (
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/types"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
)
var _ challenge.Provider = (*challengeTLSALPN)(nil)

View file

@ -19,12 +19,12 @@ import (
traefiktls "github.com/containous/traefik/v2/pkg/tls"
"github.com/containous/traefik/v2/pkg/types"
"github.com/containous/traefik/v2/pkg/version"
"github.com/go-acme/lego/v3/certificate"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/lego"
"github.com/go-acme/lego/v3/providers/dns"
"github.com/go-acme/lego/v3/registration"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/registration"
ptypes "github.com/traefik/paerser/types"
)
@ -33,13 +33,14 @@ var oscpMustStaple = false
// Configuration holds ACME configuration provided by users.
type Configuration struct {
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"`
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"`
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty"`
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"`
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"`
}
// SetDefaults sets the default values.
@ -263,14 +264,18 @@ func (p *Provider) getClient() (*lego.Client, error) {
err = client.Challenge.SetDNS01Provider(provider,
dns01.CondOption(len(p.DNSChallenge.Resolvers) > 0, dns01.AddRecursiveNameservers(p.DNSChallenge.Resolvers)),
dns01.CondOption(p.DNSChallenge.DisablePropagationCheck || p.DNSChallenge.DelayBeforeCheck > 0,
dns01.AddPreCheck(func(_, _ string) (bool, error) {
if p.DNSChallenge.DelayBeforeCheck > 0 {
log.Debugf("Delaying %d rather than validating DNS propagation now.", p.DNSChallenge.DelayBeforeCheck)
time.Sleep(time.Duration(p.DNSChallenge.DelayBeforeCheck))
}
dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) {
if p.DNSChallenge.DisablePropagationCheck {
return true, nil
})),
}
if p.DNSChallenge.DelayBeforeCheck > 0 {
logger.Debugf("Delaying %d rather than validating DNS propagation now.", p.DNSChallenge.DelayBeforeCheck)
time.Sleep(time.Duration(p.DNSChallenge.DelayBeforeCheck))
}
return check(fqdn, value)
}),
)
if err != nil {
return nil, err
@ -627,7 +632,7 @@ func (p *Provider) renewCertificates(ctx context.Context) {
Domain: cert.Domain.Main,
PrivateKey: cert.Key,
Certificate: cert.Certificate.Certificate,
}, true, oscpMustStaple)
}, true, oscpMustStaple, p.PreferredChain)
if err != nil {
logger.Errorf("Error renewing certificate from LE: %v, %v", cert.Domain, err)
continue

View file

@ -7,7 +7,7 @@ import (
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/types"
"github.com/go-acme/lego/v3/certcrypto"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/stretchr/testify/assert"
)

View file

@ -1,54 +0,0 @@
package responsemodifiers
import (
"net/http"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/middlewares/headers"
"github.com/unrolled/secure"
)
func buildHeaders(hdrs *dynamic.Headers, contextKey string) func(*http.Response) error {
opt := secure.Options{
BrowserXssFilter: hdrs.BrowserXSSFilter,
ContentTypeNosniff: hdrs.ContentTypeNosniff,
ForceSTSHeader: hdrs.ForceSTSHeader,
FrameDeny: hdrs.FrameDeny,
IsDevelopment: hdrs.IsDevelopment,
SSLRedirect: hdrs.SSLRedirect,
SSLForceHost: hdrs.SSLForceHost,
SSLTemporaryRedirect: hdrs.SSLTemporaryRedirect,
STSIncludeSubdomains: hdrs.STSIncludeSubdomains,
STSPreload: hdrs.STSPreload,
ContentSecurityPolicy: hdrs.ContentSecurityPolicy,
CustomBrowserXssValue: hdrs.CustomBrowserXSSValue,
CustomFrameOptionsValue: hdrs.CustomFrameOptionsValue,
PublicKey: hdrs.PublicKey,
ReferrerPolicy: hdrs.ReferrerPolicy,
SSLHost: hdrs.SSLHost,
AllowedHosts: hdrs.AllowedHosts,
HostsProxyHeaders: hdrs.HostsProxyHeaders,
SSLProxyHeaders: hdrs.SSLProxyHeaders,
STSSeconds: hdrs.STSSeconds,
FeaturePolicy: hdrs.FeaturePolicy,
SecureContextKey: contextKey,
}
return func(resp *http.Response) error {
if hdrs.HasCustomHeadersDefined() || hdrs.HasCorsHeadersDefined() {
err := headers.NewHeader(nil, *hdrs).PostRequestModifyResponseHeaders(resp)
if err != nil {
return err
}
}
if hdrs.HasSecureHeadersDefined() {
err := secure.New(opt).ModifyResponseHeaders(resp)
if err != nil {
return err
}
}
return nil
}
}

View file

@ -1,13 +0,0 @@
package responsemodifiers
import (
"context"
"github.com/containous/traefik/v2/pkg/log"
"github.com/sirupsen/logrus"
)
// getLogger creates a logger configured with the middleware fields.
func getLogger(ctx context.Context, middleware, middlewareType string) logrus.FieldLogger {
return log.FromContext(ctx).WithField(log.MiddlewareName, middleware).WithField(log.MiddlewareType, middlewareType)
}

View file

@ -1,68 +0,0 @@
package responsemodifiers
import (
"context"
"net/http"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/server/provider"
)
// NewBuilder creates a builder.
func NewBuilder(configs map[string]*runtime.MiddlewareInfo) *Builder {
return &Builder{configs: configs}
}
// Builder holds builder configuration.
type Builder struct {
configs map[string]*runtime.MiddlewareInfo
}
// Build Builds the response modifier.
// It returns nil if there is no modifier to apply.
func (f *Builder) Build(ctx context.Context, names []string) func(*http.Response) error {
var modifiers []func(*http.Response) error
for _, middleName := range names {
conf, ok := f.configs[middleName]
if !ok {
getLogger(ctx, middleName, "undefined").Debug("Middleware name not found in config (ResponseModifier)")
continue
}
if conf == nil || conf.Middleware == nil {
getLogger(ctx, middleName, "undefined").Error("Invalid Middleware configuration (ResponseModifier)")
continue
}
if conf.Headers != nil {
getLogger(ctx, middleName, "Headers").Debug("Creating Middleware (ResponseModifier)")
modifiers = append(modifiers, buildHeaders(conf.Headers, middleName))
} else if conf.Chain != nil {
chainCtx := provider.AddInContext(ctx, middleName)
getLogger(chainCtx, middleName, "Chain").Debug("Creating Middleware (ResponseModifier)")
var qualifiedNames []string
for _, name := range conf.Chain.Middlewares {
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(chainCtx, name))
}
if rm := f.Build(ctx, qualifiedNames); rm != nil {
modifiers = append(modifiers, rm)
}
}
}
if len(modifiers) > 0 {
return func(resp *http.Response) error {
for i := len(modifiers); i > 0; i-- {
err := modifiers[i-1](resp)
if err != nil {
return err
}
}
return nil
}
}
return nil
}

View file

@ -1,214 +0,0 @@
package responsemodifiers
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/middlewares/headers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func stubResponse(_ map[string]*dynamic.Middleware) *http.Response {
return &http.Response{Header: make(http.Header)}
}
func TestBuilderBuild(t *testing.T) {
testCases := []struct {
desc string
middlewares []string
// buildResponse is needed because secure use a private context key
buildResponse func(map[string]*dynamic.Middleware) *http.Response
conf map[string]*dynamic.Middleware
assertResponse func(*testing.T, *http.Response)
}{
{
desc: "no configuration",
middlewares: []string{"foo", "bar"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{},
assertResponse: func(t *testing.T, resp *http.Response) {},
},
{
desc: "one modifier",
middlewares: []string{"foo", "bar"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {
t.Helper()
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
},
},
{
desc: "secure: one modifier",
middlewares: []string{"foo", "bar"},
buildResponse: func(middlewares map[string]*dynamic.Middleware) *http.Response {
ctx := context.Background()
var request *http.Request
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
request = req
})
headerM := *middlewares["foo"].Headers
handler, err := headers.New(ctx, next, headerM, "foo")
require.NoError(t, err)
handler.ServeHTTP(httptest.NewRecorder(),
httptest.NewRequest(http.MethodGet, "http://foo.com", nil))
return &http.Response{Header: make(http.Header), Request: request}
},
conf: map[string]*dynamic.Middleware{
"foo": {
Headers: &dynamic.Headers{
ReferrerPolicy: "no-referrer",
},
},
"bar": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Bar": "bar"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {
t.Helper()
assert.Equal(t, "no-referrer", resp.Header.Get("Referrer-Policy"))
},
},
{
desc: "two modifiers",
middlewares: []string{"foo", "bar"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
},
},
"bar": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Bar": "bar"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {
t.Helper()
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
assert.Equal(t, "bar", resp.Header.Get("X-Bar"))
},
},
{
desc: "modifier order",
middlewares: []string{"foo", "bar"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
},
},
"bar": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Foo": "bar"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {
t.Helper()
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
},
},
{
desc: "chain",
middlewares: []string{"chain"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
},
},
"bar": {
Headers: &dynamic.Headers{
CustomResponseHeaders: map[string]string{"X-Foo": "bar"},
},
},
"chain": {
Chain: &dynamic.Chain{
Middlewares: []string{"foo", "bar"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {
t.Helper()
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
},
},
{
desc: "nil middleware",
middlewares: []string{"foo"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": nil,
},
assertResponse: func(t *testing.T, resp *http.Response) {},
},
{
desc: "chain without headers",
middlewares: []string{"chain"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": {IPWhiteList: &dynamic.IPWhiteList{}},
"chain": {
Chain: &dynamic.Chain{
Middlewares: []string{"foo"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rtConf := runtime.NewConfig(dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Middlewares: test.conf,
},
})
builder := NewBuilder(rtConf.Middlewares)
rm := builder.Build(context.Background(), test.middlewares)
if rm == nil {
return
}
resp := test.buildResponse(test.conf)
err := rm(resp)
require.NoError(t, err)
test.assertResponse(t, resp)
})
}
}

View file

@ -28,7 +28,6 @@ import (
"github.com/containous/traefik/v2/pkg/middlewares/stripprefix"
"github.com/containous/traefik/v2/pkg/middlewares/stripprefixregex"
"github.com/containous/traefik/v2/pkg/middlewares/tracing"
"github.com/containous/traefik/v2/pkg/plugins"
"github.com/containous/traefik/v2/pkg/server/provider"
)
@ -41,16 +40,16 @@ const (
// Builder the middleware builder.
type Builder struct {
configs map[string]*runtime.MiddlewareInfo
pluginBuilder *plugins.Builder
pluginBuilder PluginsBuilder
serviceBuilder serviceBuilder
}
type serviceBuilder interface {
BuildHTTP(ctx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error)
}
// NewBuilder creates a new Builder.
func NewBuilder(configs map[string]*runtime.MiddlewareInfo, serviceBuilder serviceBuilder, pluginBuilder *plugins.Builder) *Builder {
func NewBuilder(configs map[string]*runtime.MiddlewareInfo, serviceBuilder serviceBuilder, pluginBuilder PluginsBuilder) *Builder {
return &Builder{configs: configs, serviceBuilder: serviceBuilder, pluginBuilder: pluginBuilder}
}
@ -351,13 +350,13 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
return nil, err
}
m, err := b.pluginBuilder.Build(pluginType, rawPluginConfig, middlewareName)
plug, err := b.pluginBuilder.Build(pluginType, rawPluginConfig, middlewareName)
if err != nil {
return nil, err
}
middleware = func(next http.Handler) (http.Handler, error) {
return m.NewHandler(ctx, next)
return plug(ctx, next)
}
}

View file

@ -5,8 +5,14 @@ import (
"fmt"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/plugins"
)
// PluginsBuilder the plugin's builder interface.
type PluginsBuilder interface {
Build(pName string, config map[string]interface{}, middlewareName string) (plugins.Constructor, error)
}
func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[string]interface{}, error) {
if len(rawConfig) != 1 {
return "", nil, errors.New("plugin: invalid configuration: no configuration or too many plugin definition")

View file

@ -24,12 +24,8 @@ type middlewareBuilder interface {
BuildChain(ctx context.Context, names []string) *alice.Chain
}
type responseModifierBuilder interface {
Build(ctx context.Context, names []string) func(*http.Response) error
}
type serviceManager interface {
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
LaunchHealthCheck()
}
@ -39,22 +35,15 @@ type Manager struct {
serviceManager serviceManager
middlewaresBuilder middlewareBuilder
chainBuilder *middleware.ChainBuilder
modifierBuilder responseModifierBuilder
conf *runtime.Configuration
}
// NewManager Creates a new Manager.
func NewManager(conf *runtime.Configuration,
serviceManager serviceManager,
middlewaresBuilder middlewareBuilder,
modifierBuilder responseModifierBuilder,
chainBuilder *middleware.ChainBuilder,
) *Manager {
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder) *Manager {
return &Manager{
routerHandlers: make(map[string]http.Handler),
serviceManager: serviceManager,
middlewaresBuilder: middlewaresBuilder,
modifierBuilder: modifierBuilder,
chainBuilder: chainBuilder,
conf: conf,
}
@ -176,13 +165,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(ctx, name))
}
router.Middlewares = qualifiedNames
rm := m.modifierBuilder.Build(ctx, qualifiedNames)
if router.Service == "" {
return nil, errors.New("the service is missing on the router")
}
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service, rm)
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service)
if err != nil {
return nil, err
}

View file

@ -13,7 +13,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/service"
"github.com/containous/traefik/v2/pkg/testhelpers"
@ -290,10 +289,9 @@ func TestRouterManager_Get(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -395,10 +393,9 @@ func TestAccessLog(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -683,10 +680,9 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -765,10 +761,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -826,10 +821,9 @@ func BenchmarkRouterServe(b *testing.B) {
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -871,7 +865,7 @@ func BenchmarkService(b *testing.B) {
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service", nil)
handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service")
b.ReportAllocs()
for i := 0; i < b.N; i++ {
handler.ServeHTTP(w, req)

View file

@ -6,8 +6,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/plugins"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/router"
routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp"
@ -27,14 +25,14 @@ type RouterFactory struct {
managerFactory *service.ManagerFactory
pluginBuilder *plugins.Builder
pluginBuilder middleware.PluginsBuilder
chainBuilder *middleware.ChainBuilder
tlsManager *tls.Manager
}
// NewRouterFactory creates a new RouterFactory.
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder, pluginBuilder *plugins.Builder) *RouterFactory {
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder) *RouterFactory {
var entryPointsTCP, entryPointsUDP []string
for name, cfg := range staticConfiguration.EntryPoints {
protocol, err := cfg.GetProtocol()
@ -68,9 +66,8 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
serviceManager := f.managerFactory.Build(rtConf)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder)
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)

View file

@ -8,11 +8,10 @@ import (
"strings"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
)
type serviceManager interface {
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
LaunchHealthCheck()
}
@ -43,87 +42,18 @@ func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Han
}
}
type responseModifier struct {
r *http.Request
w http.ResponseWriter
headersSent bool // whether headers have already been sent
code int // status code, must default to 200
modifier func(*http.Response) error // can be nil
modified bool // whether modifier has already been called for the current request
modifierErr error // returned by modifier call
}
// modifier can be nil.
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
return &responseModifier{
r: r,
w: w,
modifier: modifier,
code: http.StatusOK,
}
}
func (w *responseModifier) WriteHeader(code int) {
if w.headersSent {
return
}
defer func() {
w.code = code
w.headersSent = true
}()
if w.modifier == nil || w.modified {
w.w.WriteHeader(code)
return
}
resp := http.Response{
Header: w.w.Header(),
Request: w.r,
}
if err := w.modifier(&resp); err != nil {
w.modifierErr = err
// we are propagating when we are called in Write, but we're logging anyway,
// because we could be called from another place which does not take care of
// checking w.modifierErr.
log.Errorf("Error when applying response modifier: %v", err)
w.w.WriteHeader(http.StatusInternalServerError)
return
}
w.modified = true
w.w.WriteHeader(code)
}
func (w *responseModifier) Header() http.Header {
return w.w.Header()
}
func (w *responseModifier) Write(b []byte) (int, error) {
w.WriteHeader(w.code)
if w.modifierErr != nil {
return 0, w.modifierErr
}
return w.w.Write(b)
}
// BuildHTTP builds an HTTP handler.
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string, respModifier func(*http.Response) error) (http.Handler, error) {
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
if !strings.HasSuffix(serviceName, "@internal") {
return m.serviceManager.BuildHTTP(rootCtx, serviceName, respModifier)
return m.serviceManager.BuildHTTP(rootCtx, serviceName)
}
internalHandler, err := m.get(serviceName)
if err != nil {
return nil, err
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
internalHandler.ServeHTTP(newResponseModifier(w, r, respModifier), r)
}), nil
return internalHandler, nil
}
func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {

View file

@ -21,7 +21,7 @@ const StatusClientClosedRequest = 499
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
const StatusClientClosedRequestText = "Client Closed Request"
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool, responseModifier func(*http.Response) error) (http.Handler, error) {
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
var flushInterval ptypes.Duration
if responseForwarding != nil {
err := flushInterval.Set(responseForwarding.FlushInterval)
@ -76,10 +76,9 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
delete(outReq.Header, "Sec-Websocket-Protocol")
delete(outReq.Header, "Sec-Websocket-Version")
},
Transport: defaultRoundTripper,
FlushInterval: time.Duration(flushInterval),
ModifyResponse: responseModifier,
BufferPool: bufferPool,
Transport: defaultRoundTripper,
FlushInterval: time.Duration(flushInterval),
BufferPool: bufferPool,
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
statusCode := http.StatusInternalServerError

View file

@ -28,7 +28,7 @@ func BenchmarkProxy(b *testing.B) {
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
pool := newBufferPool()
handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool, nil)
handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool)
b.ReportAllocs()
for i := 0; i < b.N; i++ {

View file

@ -20,7 +20,7 @@ import (
func Bool(v bool) *bool { return &v }
func TestWebSocketTCPClose(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
errChan := make(chan error, 1)
@ -59,7 +59,7 @@ func TestWebSocketTCPClose(t *testing.T) {
}
func TestWebSocketPingPong(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
@ -125,7 +125,7 @@ func TestWebSocketPingPong(t *testing.T) {
}
func TestWebSocketEcho(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
mux := http.NewServeMux()
@ -191,7 +191,7 @@ func TestWebSocketPassHost(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil)
require.NoError(t, err)
@ -250,7 +250,7 @@ func TestWebSocketPassHost(t *testing.T) {
}
func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
@ -291,7 +291,7 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
}
func TestWebSocketRequestWithOrigin(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
upgrader := gorillawebsocket.Upgrader{}
@ -337,7 +337,7 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
}
func TestWebSocketRequestWithQueryParams(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
upgrader := gorillawebsocket.Upgrader{}
@ -377,7 +377,7 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
}
func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
mux := http.NewServeMux()
@ -409,7 +409,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
}
func TestWebSocketRequestWithEncodedChar(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
upgrader := gorillawebsocket.Upgrader{}
@ -449,7 +449,7 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
}
func TestWebSocketUpgradeFailed(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
mux := http.NewServeMux()
@ -499,7 +499,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
}
func TestForwardsWebsocketTraffic(t *testing.T) {
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
mux := http.NewServeMux()
@ -555,7 +555,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
srv := createTLSWebsocketServer()
defer srv.Close()
forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
proxyWithoutTLSConfig := createProxyWithForwarder(t, forwarderWithoutTLSConfig, srv.URL)
@ -574,7 +574,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil, nil)
forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil)
require.NoError(t, err)
proxyWithTLSConfig := createProxyWithForwarder(t, forwarderWithTLSConfig, srv.URL)
@ -593,7 +593,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
require.NoError(t, err)
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, forwarderWithTLSConfigFromDefaultTransport, srv.URL)

View file

@ -62,7 +62,7 @@ type Manager struct {
}
// BuildHTTP Creates a http.Handler for a service configuration.
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName))
serviceName = provider.GetQualifiedName(ctx, serviceName)
@ -91,21 +91,21 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
switch {
case conf.LoadBalancer != nil:
var err error
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier)
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer)
if err != nil {
conf.AddError(err, true)
return nil, err
}
case conf.Weighted != nil:
var err error
lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier)
lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted)
if err != nil {
conf.AddError(err, true)
return nil, err
}
case conf.Mirroring != nil:
var err error
lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring, responseModifier)
lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring)
if err != nil {
conf.AddError(err, true)
return nil, err
@ -119,8 +119,8 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
return lb, nil
}
func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) {
serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier)
func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring) (http.Handler, error) {
serviceHandler, err := m.BuildHTTP(ctx, config.Service)
if err != nil {
return nil, err
}
@ -131,7 +131,7 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M
}
handler := mirror.New(serviceHandler, m.routinePool, maxBodySize)
for _, mirrorConfig := range config.Mirrors {
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name, responseModifier)
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name)
if err != nil {
return nil, err
}
@ -144,7 +144,7 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M
return handler, nil
}
func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) {
func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin) (http.Handler, error) {
// TODO Handle accesslog and metrics with multiple service name
if config.Sticky != nil && config.Sticky.Cookie != nil {
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
@ -152,7 +152,7 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
balancer := wrr.New(config.Sticky)
for _, service := range config.Services {
serviceHandler, err := m.BuildHTTP(ctx, service.Name, responseModifier)
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
if err != nil {
return nil, err
}
@ -162,18 +162,13 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
return balancer, nil
}
func (m *Manager) getLoadBalancerServiceHandler(
ctx context.Context,
serviceName string,
service *dynamic.ServersLoadBalancer,
responseModifier func(*http.Response) error,
) (http.Handler, error) {
func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer) (http.Handler, error) {
if service.PassHostHeader == nil {
defaultPassHostHeader := true
service.PassHostHeader = &defaultPassHostHeader
}
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool, responseModifier)
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool)
if err != nil {
return nil, err
}

View file

@ -259,7 +259,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service, test.responseModifier)
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service)
assert.NoError(t, err)
assert.NotNil(t, handler)
@ -339,7 +339,7 @@ func TestManager_Build(t *testing.T) {
ctx = provider.AddInContext(ctx, "foobar@"+test.providerName)
}
_, err := manager.BuildHTTP(ctx, test.serviceName, nil)
_, err := manager.BuildHTTP(ctx, test.serviceName)
require.NoError(t, err)
})
}
@ -357,7 +357,7 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) {
manager := NewManager(services, http.DefaultTransport, nil, nil)
_, err := manager.BuildHTTP(context.Background(), "test@file", nil)
_, err := manager.BuildHTTP(context.Background(), "test@file")
assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
}

View file

@ -11,7 +11,7 @@ import (
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/tls/generate"
"github.com/containous/traefik/v2/pkg/types"
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/sirupsen/logrus"
)

View file

@ -1,6 +1,8 @@
FROM node:12.18
ENV WEBUI_DIR /src/webui
ARG ARG_PLATFORM_URL=https://pilot.traefik.io
ENV PLATFORM_URL=${ARG_PLATFORM_URL}
RUN mkdir -p $WEBUI_DIR
COPY package.json $WEBUI_DIR/