diff --git a/CHANGELOG.md b/CHANGELOG.md
index 671055716..afa75a697 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+## [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.0...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)
diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md
index 34aa0a49d..db5dcc2ef 100644
--- a/docs/content/https/acme.md
+++ b/docs/content/https/acme.md
@@ -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 Enter. | |
@@ -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:
diff --git a/docs/content/https/ref-acme.toml b/docs/content/https/ref-acme.toml
index e4dcf90db..39cd22689 100644
--- a/docs/content/https/ref-acme.toml
+++ b/docs/content/https/ref-acme.toml
@@ -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
diff --git a/docs/content/https/ref-acme.txt b/docs/content/https/ref-acme.txt
index 27f8942f2..7144e7932 100644
--- a/docs/content/https/ref-acme.txt
+++ b/docs/content/https/ref-acme.txt
@@ -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
diff --git a/docs/content/https/ref-acme.yaml b/docs/content/https/ref-acme.yaml
index d4b7fd7a4..43802083a 100644
--- a/docs/content/https/ref-acme.yaml
+++ b/docs/content/https/ref-acme.yaml
@@ -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
diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md
index 1923dc517..3d936cb94 100644
--- a/docs/content/migration/v2.md
+++ b/docs/content/migration/v2.md
@@ -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
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index 2cbe646c9..ba168964f 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -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
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index b5c7b0b82..9ffa87a87 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -81,6 +81,9 @@ HTTP challenge EntryPoint
`--certificatesresolvers..acme.keytype`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
+`--certificatesresolvers..acme.preferredchain`:
+Preferred chain to use.
+
`--certificatesresolvers..acme.storage`:
Storage to use. (Default: ```acme.json```)
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 62e9b1334..11fbd4be2 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -81,6 +81,9 @@ HTTP challenge EntryPoint
`TRAEFIK_CERTIFICATESRESOLVERS__ACME_KEYTYPE`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
+`TRAEFIK_CERTIFICATESRESOLVERS__ACME_PREFERREDCHAIN`:
+Preferred chain to use.
+
`TRAEFIK_CERTIFICATESRESOLVERS__ACME_STORAGE`:
Storage to use. (Default: ```acme.json```)
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index b4f944dce..bd2af833e 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -336,6 +336,7 @@
[certificatesResolvers.CertificateResolver0.acme]
email = "foobar"
caServer = "foobar"
+ preferredChain = "foobar"
storage = "foobar"
keyType = "foobar"
[certificatesResolvers.CertificateResolver0.acme.dnsChallenge]
@@ -350,6 +351,7 @@
[certificatesResolvers.CertificateResolver1.acme]
email = "foobar"
caServer = "foobar"
+ preferredChain = "foobar"
storage = "foobar"
keyType = "foobar"
[certificatesResolvers.CertificateResolver1.acme.dnsChallenge]
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index 4e36e08df..046019157 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -352,6 +352,7 @@ certificatesResolvers:
acme:
email: foobar
caServer: foobar
+ preferredChain: foobar
storage: foobar
keyType: foobar
dnsChallenge:
@@ -368,6 +369,7 @@ certificatesResolvers:
acme:
email: foobar
caServer: foobar
+ preferredChain: foobar
storage: foobar
keyType: foobar
dnsChallenge:
diff --git a/go.mod b/go.mod
index 5986d1366..66b2743e8 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 117034e94..e0a9401a3 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go
index 9490d7eb9..83851f802 100644
--- a/pkg/config/dynamic/middlewares.go
+++ b/pkg/config/dynamic/middlewares.go
@@ -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"`
}
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index 40e2ef9a8..6b4aedf17 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -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"
)
diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go
index d5f687fde..3789142e5 100644
--- a/pkg/middlewares/customerrors/custom_errors.go
+++ b/pkg/middlewares/customerrors/custom_errors.go
@@ -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
}
diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go
index 58a3a1673..5b36dd0b1 100644
--- a/pkg/middlewares/customerrors/custom_errors_test.go
+++ b/pkg/middlewares/customerrors/custom_errors_test.go
@@ -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
}
diff --git a/pkg/middlewares/headers/header.go b/pkg/middlewares/headers/header.go
new file mode 100644
index 000000000..862b376ed
--- /dev/null
+++ b/pkg/middlewares/headers/header.go
@@ -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, ""
+}
diff --git a/pkg/middlewares/headers/header_test.go b/pkg/middlewares/headers/header_test.go
new file mode 100644
index 000000000..f0aa43218
--- /dev/null
+++ b/pkg/middlewares/headers/header_test.go
@@ -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)
+ })
+ }
+}
diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go
index 81d410a94..881ad1565 100644
--- a/pkg/middlewares/headers/headers.go
+++ b/pkg/middlewares/headers/headers.go
@@ -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, ""
-}
diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go
index 2482bb233..a7be06a97 100644
--- a/pkg/middlewares/headers/headers_test.go
+++ b/pkg/middlewares/headers/headers_test.go
@@ -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)
- })
- }
-}
diff --git a/pkg/middlewares/headers/responsewriter.go b/pkg/middlewares/headers/responsewriter.go
new file mode 100644
index 000000000..274a58181
--- /dev/null
+++ b/pkg/middlewares/headers/responsewriter.go
@@ -0,0 +1,75 @@
+package headers
+
+import (
+ "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)
+}
diff --git a/pkg/middlewares/headers/secure.go b/pkg/middlewares/headers/secure.go
new file mode 100644
index 000000000..92cbe20d7
--- /dev/null
+++ b/pkg/middlewares/headers/secure.go
@@ -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)
+ })
+}
diff --git a/pkg/middlewares/headers/secure_test.go b/pkg/middlewares/headers/secure_test.go
new file mode 100644
index 000000000..66cc564d9
--- /dev/null
+++ b/pkg/middlewares/headers/secure_test.go
@@ -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)
+ })
+ }
+}
diff --git a/pkg/provider/acme/account.go b/pkg/provider/acme/account.go
index bf6fb654a..66ca15710 100644
--- a/pkg/provider/acme/account.go
+++ b/pkg/provider/acme/account.go
@@ -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.
diff --git a/pkg/provider/acme/challenge_http.go b/pkg/provider/acme/challenge_http.go
index 5da1d8c4f..42575a54c 100644
--- a/pkg/provider/acme/challenge_http.go
+++ b/pkg/provider/acme/challenge_http.go
@@ -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"
)
diff --git a/pkg/provider/acme/challenge_tls.go b/pkg/provider/acme/challenge_tls.go
index 2adba5537..d841e7fbc 100644
--- a/pkg/provider/acme/challenge_tls.go
+++ b/pkg/provider/acme/challenge_tls.go
@@ -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)
diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go
index 33e090c2d..6a7c2d101 100644
--- a/pkg/provider/acme/provider.go
+++ b/pkg/provider/acme/provider.go
@@ -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
diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go
index 49df9ac5b..27022910e 100644
--- a/pkg/provider/acme/provider_test.go
+++ b/pkg/provider/acme/provider_test.go
@@ -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"
)
diff --git a/pkg/responsemodifiers/headers.go b/pkg/responsemodifiers/headers.go
deleted file mode 100644
index 3a6a47e25..000000000
--- a/pkg/responsemodifiers/headers.go
+++ /dev/null
@@ -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
- }
-}
diff --git a/pkg/responsemodifiers/log.go b/pkg/responsemodifiers/log.go
deleted file mode 100644
index 43eac2a1b..000000000
--- a/pkg/responsemodifiers/log.go
+++ /dev/null
@@ -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)
-}
diff --git a/pkg/responsemodifiers/response_modifier.go b/pkg/responsemodifiers/response_modifier.go
deleted file mode 100644
index a49cb8cea..000000000
--- a/pkg/responsemodifiers/response_modifier.go
+++ /dev/null
@@ -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
-}
diff --git a/pkg/responsemodifiers/response_modifier_test.go b/pkg/responsemodifiers/response_modifier_test.go
deleted file mode 100644
index f75f9dfae..000000000
--- a/pkg/responsemodifiers/response_modifier_test.go
+++ /dev/null
@@ -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)
- })
- }
-}
diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go
index 69c8b00e2..e14faad99 100644
--- a/pkg/server/middleware/middlewares.go
+++ b/pkg/server/middleware/middlewares.go
@@ -46,7 +46,7 @@ type Builder struct {
}
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.
diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go
index 3392e94ff..994e0a595 100644
--- a/pkg/server/router/router.go
+++ b/pkg/server/router/router.go
@@ -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
}
diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go
index a5af6e66c..ed41ed1d0 100644
--- a/pkg/server/router/router_test.go
+++ b/pkg/server/router/router_test.go
@@ -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)
diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go
index eb76b1190..53d53bc48 100644
--- a/pkg/server/routerfactory.go
+++ b/pkg/server/routerfactory.go
@@ -7,7 +7,6 @@ import (
"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"
@@ -68,9 +67,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)
diff --git a/pkg/server/service/internalhandler.go b/pkg/server/service/internalhandler.go
index 6ff7e0a1e..f50e37065 100644
--- a/pkg/server/service/internalhandler.go
+++ b/pkg/server/service/internalhandler.go
@@ -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) {
diff --git a/pkg/server/service/proxy.go b/pkg/server/service/proxy.go
index 2347f7671..f6702d195 100644
--- a/pkg/server/service/proxy.go
+++ b/pkg/server/service/proxy.go
@@ -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
diff --git a/pkg/server/service/proxy_test.go b/pkg/server/service/proxy_test.go
index 38966b509..5318a0a67 100644
--- a/pkg/server/service/proxy_test.go
+++ b/pkg/server/service/proxy_test.go
@@ -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++ {
diff --git a/pkg/server/service/proxy_websocket_test.go b/pkg/server/service/proxy_websocket_test.go
index d309e5379..fe169e1c5 100644
--- a/pkg/server/service/proxy_websocket_test.go
+++ b/pkg/server/service/proxy_websocket_test.go
@@ -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)
diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go
index 2c641cb0e..37f02c4db 100644
--- a/pkg/server/service/service.go
+++ b/pkg/server/service/service.go
@@ -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
}
diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go
index 522f5308b..23b5f6b35 100644
--- a/pkg/server/service/service_test.go
+++ b/pkg/server/service/service_test.go
@@ -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")
}
diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go
index bae3e623a..36d6d06d6 100644
--- a/pkg/tls/tlsmanager.go
+++ b/pkg/tls/tlsmanager.go
@@ -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"
)