Compare commits

..

32 Commits

Author SHA1 Message Date
Ludovic Fernandez
5afa78e18e Prepare release v1.7.26 2020-07-28 17:32:03 +02:00
Ludovic Fernandez
cfa04c300c fix: clean X-Forwarded-Prefix header for the dashboard. 2020-07-28 10:10:03 +02:00
Cory Claflin
cb6b94ff40 Change apiVersion to apps/v1 in examples/k8s/cheese-deployments.yaml 2020-07-27 16:24:03 +02:00
Ludovic Fernandez
0b2a6cecd1 Prepare release v1.7.25 2020-07-15 12:28:03 +02:00
Fernandez Ludovic
fee89273a3 chore: update doc analytics. 2020-07-03 12:07:46 +02:00
Richard Kojedzinszky
463e2285d6 Backport "Same Configuration Check" from master 2020-07-01 11:36:03 +02:00
Romain
c65277a6f0 Fix ipv6 handling in redirect middleware 2020-06-19 10:08:04 +02:00
Douglas De Toni Machado
bb14c3e812 Doc improvement on entrypoints whitelist for v1.7 2020-05-20 10:44:04 +02:00
kobayashi
85580dde3c Add ingress setup for minikube in docs 2020-05-14 18:06:05 +02:00
Ludovic Fernandez
4bb6d1bf4f Prepare release v1.7.24 2020-03-25 14:48:05 +01:00
Ludovic Fernandez
459cabca8e fix: stickiness annotations support. 2020-03-25 10:58:04 +01:00
Ludovic Fernandez
dcca37fe29 Prepare release v1.7.23 2020-03-23 16:50:04 +01:00
Julien Salleyron
a610f0b2a1 Force http/1.1 for upgrade (Traefik v1) 2020-03-23 15:18:04 +01:00
Ludovic Fernandez
ded285be29 Fix sameSite (Traefik v1) 2020-03-23 13:10:04 +01:00
Ludovic Fernandez
4b31d3306b fix: max TLS version. 2020-03-22 17:54:03 +01:00
Ludovic Fernandez
5a6652fc20 Prepare release v1.7.22 2020-03-18 15:00:07 +01:00
robotte
91b0e2e4dc Complete TLS example for Kubernetes Ingress in user guide 2020-03-09 19:16:05 +01:00
Ludovic Fernandez
554bceb75b Skip redirection with invalid regex syntax. 2020-03-09 13:24:05 +01:00
Ludovic Fernandez
ce76212605 fix: manual provider code name. 2020-03-08 12:08:03 +01:00
bjthomas1
d474c87cde Updated rbac and demonset example bloc 2020-03-02 09:48:04 +01:00
Ludovic Fernandez
862772230c Update to go1.14 2020-03-02 09:30:05 +01:00
Ludovic Fernandez
09fa1e6546 Fix typo in user guide. 2020-02-27 21:06:05 +01:00
Omer Karjevsky
c3125104d9 Clear closed hijacked h2c connections 2020-02-27 14:08:04 +01:00
Ludovic Fernandez
31786f7163 Prepare release v1.7.21 2020-02-20 18:22:04 +01:00
pierresteiner
d7ed42d614 Fix sample for ssl-header in docs 2020-02-19 17:18:04 +01:00
mpl
4c709c2fcd Improve rate-limiting doc 2020-02-07 17:26:05 +01:00
Yazan Dabain
8dac076711 Fix finding proper provided certificate when ACME is enabled 2020-02-06 10:08:04 +01:00
Julien Salleyron
0b039499c6 don't create http client in each request in forward auth 2020-02-04 18:36:04 +01:00
Steven
2124975432 Fix dnspod update. 2020-01-26 12:42:03 +01:00
Jean-Baptiste Doumenjou
e4725619ea Update the k8s api version in the documentation 2020-01-13 15:44:05 +01:00
Ludovic Fernandez
d24f78222c fix: skip ECS container when information are missing instead of panic. 2020-01-06 15:10:05 +01:00
Daniel Tomcej
3f1925e20e Add edge case for root path with rewrite-target 2019-12-19 09:24:04 +01:00
76 changed files with 1141 additions and 336 deletions

View File

@@ -2,19 +2,19 @@
set -e
curl -O https://dl.google.com/go/go1.12.linux-amd64.tar.gz
curl -O https://dl.google.com/go/go1.14.linux-amd64.tar.gz
tar -xvf go1.12.linux-amd64.tar.gz
rm -rf go1.12.linux-amd64.tar.gz
tar -xvf go1.14.linux-amd64.tar.gz
rm -rf go1.14.linux-amd64.tar.gz
sudo mkdir -p /usr/local/golang/1.12/go
sudo mv go /usr/local/golang/1.12/
sudo mkdir -p /usr/local/golang/1.14/go
sudo mv go /usr/local/golang/1.14/
sudo rm /usr/local/bin/go
sudo chmod +x /usr/local/golang/1.12/go/bin/go
sudo ln -s /usr/local/golang/1.12/go/bin/go /usr/local/bin/go
sudo chmod +x /usr/local/golang/1.14/go/bin/go
sudo ln -s /usr/local/golang/1.14/go/bin/go /usr/local/bin/go
export GOROOT="/usr/local/golang/1.12/go"
export GOTOOLDIR="/usr/local/golang/1.12/go/pkg/tool/linux_amd64"
export GOROOT="/usr/local/golang/1.14/go"
export GOTOOLDIR="/usr/local/golang/1.14/go/pkg/tool/linux_amd64"
go version

View File

@@ -1,5 +1,67 @@
# Change Log
## [v1.7.26](https://github.com/containous/traefik/tree/v1.7.26) (2020-07-28)
[All Commits](https://github.com/containous/traefik/compare/v1.7.25...v1.7.26)
**Bug fixes:**
- **[webui]** fix: clean X-Forwarded-Prefix header for the dashboard. ([#7108](https://github.com/containous/traefik/pull/7108) by [ldez](https://github.com/ldez))
**Documentation:**
- **[k8s,k8s/ingress]** Change apiVersion to apps/v1 in examples/k8s/cheese-deployments.yaml ([#7090](https://github.com/containous/traefik/pull/7090) by [claflico](https://github.com/claflico))
## [v1.7.25](https://github.com/containous/traefik/tree/v1.7.25) (2020-07-15)
[All Commits](https://github.com/containous/traefik/compare/v1.7.24...v1.7.25)
**Bug fixes:**
- **[middleware]** Fix ipv6 handling in redirect middleware ([#6901](https://github.com/containous/traefik/pull/6901) by [rtribotte](https://github.com/rtribotte))
- **[provider]** Backport "Same Configuration Check" from master ([#6631](https://github.com/containous/traefik/pull/6631) by [rkojedzinszky](https://github.com/rkojedzinszky))
**Documentation:**
- **[k8s]** Add ingress setup for minikube in docs ([#6552](https://github.com/containous/traefik/pull/6552) by [kobayashi](https://github.com/kobayashi))
- Doc improvement on entrypoints whitelist for v1.7 ([#6571](https://github.com/containous/traefik/pull/6571) by [ddtmachado](https://github.com/ddtmachado))
## [v1.7.24](https://github.com/containous/traefik/tree/v1.7.24) (2020-03-25)
[All Commits](https://github.com/containous/traefik/compare/v1.7.23...v1.7.24)
**Bug fixes:**
- **[k8s/ingress]** fix: stickiness annotations support. ([#6576](https://github.com/containous/traefik/pull/6576) by [ldez](https://github.com/ldez))
## [v1.7.23](https://github.com/containous/traefik/tree/v1.7.23) (2020-03-23)
[All Commits](https://github.com/containous/traefik/compare/v1.7.22...v1.7.23)
**Bug fixes:**
- **[consul,consulcatalog,docker,ecs,k8s,marathon,mesos,rancher,sticky-session]** Fix sameSite ([#6537](https://github.com/containous/traefik/pull/6537) by [ldez](https://github.com/ldez))
- **[server]** Force http/1.1 for upgrade ([#6553](https://github.com/containous/traefik/pull/6553) by [juliens](https://github.com/juliens))
- **[tls]** fix: max TLS version. ([#6531](https://github.com/containous/traefik/pull/6531) by [ldez](https://github.com/ldez))
## [v1.7.22](https://github.com/containous/traefik/tree/v1.7.22) (2020-03-09)
[All Commits](https://github.com/containous/traefik/compare/v1.7.21...v1.7.22)
**Bug fixes:**
- **[provider]** Skip redirection with invalid regex syntax. ([#6446](https://github.com/containous/traefik/pull/6446) by [ldez](https://github.com/ldez))
- **[server]** Clear closed hijacked h2c connections ([#6357](https://github.com/containous/traefik/pull/6357) by [omerkay](https://github.com/omerkay))
**Documentation:**
- **[acme]** fix: manual provider code name. ([#6456](https://github.com/containous/traefik/pull/6456) by [ldez](https://github.com/ldez))
- **[docker]** Fix typo in user guide. ([#6386](https://github.com/containous/traefik/pull/6386) by [ldez](https://github.com/ldez))
- **[k8s]** Complete TLS example for Kubernetes Ingress in user guide ([#6457](https://github.com/containous/traefik/pull/6457) by [rtribotte](https://github.com/rtribotte))
- **[k8s]** Updated rbac and Daemonset example bloc ([#6375](https://github.com/containous/traefik/pull/6375) by [bjthomas1](https://github.com/bjthomas1))
## [v1.7.21](https://github.com/containous/traefik/tree/v1.7.21) (2020-02-20)
[All Commits](https://github.com/containous/traefik/compare/v1.7.20...v1.7.21)
**Bug fixes:**
- **[acme]** Fix dnspod update. ([#6240](https://github.com/containous/traefik/pull/6240) by [iineva](https://github.com/iineva))
- **[acme]** Fix finding proper provided certificate when ACME is enabled ([#5873](https://github.com/containous/traefik/pull/5873) by [yazd](https://github.com/yazd))
- **[authentication,middleware]** don't create http client in each request in forward auth ([#6273](https://github.com/containous/traefik/pull/6273) by [juliens](https://github.com/juliens))
- **[ecs]** fix: skip ECS container when information are missing instead of panic. ([#6071](https://github.com/containous/traefik/pull/6071) by [ldez](https://github.com/ldez))
- **[k8s]** Add edge case for root path with rewrite-target ([#6005](https://github.com/containous/traefik/pull/6005) by [dtomcej](https://github.com/dtomcej))
**Documentation:**
- **[k8s,k8s/ingress]** Update the k8s api version in the documentation ([#6162](https://github.com/containous/traefik/pull/6162) by [jbdoumenjou](https://github.com/jbdoumenjou))
- **[middleware]** Improve rate-limiting doc ([#6277](https://github.com/containous/traefik/pull/6277) by [mpl](https://github.com/mpl))
- **[provider]** Fix sample for ssl-header in docs ([#6337](https://github.com/containous/traefik/pull/6337) by [pierresteiner](https://github.com/pierresteiner))
## [v1.7.20](https://github.com/containous/traefik/tree/v1.7.20) (2019-12-09)
[All Commits](https://github.com/containous/traefik/compare/v1.7.19...v1.7.20)

View File

@@ -13,7 +13,7 @@ You need to run the `binary` target. This will create binaries for Linux platfor
$ make binary
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
Sending build context to Docker daemon 295.3 MB
Step 0 : FROM golang:1.12-alpine
Step 0 : FROM golang:1.14-alpine
---> 8c6473912976
Step 1 : RUN go get github.com/golang/dep/cmd/dep
[...]

11
Gopkg.lock generated
View File

@@ -477,12 +477,11 @@
version = "v1.1.0"
[[projects]]
digest = "1:fa62421bd924623ac10a160686cc55d529f7274b2caedf7d2c607d14bc50c118"
digest = "1:295f7aed734ab28daff7c0df7db42bcccf7a043d4e0a5daf5768129346b25f23"
name = "github.com/decker502/dnspod-go"
packages = ["."]
pruneopts = "NUT"
revision = "71fbbdbdf1a7eeac949586de15bf96d416d3dd63"
version = "v0.2.0"
revision = "071171b22a9b65e4f544b61c143befd54a08a64e"
[[projects]]
digest = "1:7a6852b35eb5bbc184561443762d225116ae630c26a7c4d90546619f1e7d2ad2"
@@ -1938,8 +1937,7 @@
version = "v0.1.0"
[[projects]]
branch = "master"
digest = "1:86f14aadf288fe3ad8ac060bcb2b5083cec3829dd883803486ec834d031060c9"
digest = "1:a31155114d2977ef48d2cca6810c4e055545d676bf13b604c2debd42de44cad0"
name = "github.com/vulcand/oxy"
packages = [
"buffer",
@@ -1952,7 +1950,8 @@
"utils",
]
pruneopts = "NUT"
revision = "0d102f45103cf49a95b5c6e810e092973cbcb68c"
revision = "9dbd22c030f2187f590fea61ebe84ab78a8146a2"
version = "v1.1.0"
[[projects]]
digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5"

View File

@@ -175,7 +175,7 @@
version = "0.1.0"
[[constraint]]
branch = "master"
version = "v1.1.0"
name = "github.com/vulcand/oxy"
[[constraint]]
@@ -272,3 +272,7 @@
[[override]]
name = "contrib.go.opencensus.io/exporter/ocagent"
version = "0.4.12"
[[override]]
name = "github.com/decker502/dnspod-go"
revision = "071171b22a9b65e4f544b61c143befd54a08a64e"

View File

@@ -2,6 +2,7 @@ package api
import (
"net/http"
"net/url"
"github.com/containous/mux"
"github.com/containous/traefik/log"
@@ -23,17 +24,35 @@ func (g DashboardHandler) AddRoutes(router *mux.Router) {
// Expose dashboard
router.Methods(http.MethodGet).
Path("/").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
http.Redirect(resp, req, safePrefix(req)+"/dashboard/", 302)
})
router.Methods(http.MethodGet).
Path("/dashboard/status").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, "/dashboard/", 302)
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
http.Redirect(resp, req, "/dashboard/", 302)
})
router.Methods(http.MethodGet).
PathPrefix("/dashboard/").
Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets)))
}
func safePrefix(req *http.Request) string {
prefix := req.Header.Get("X-Forwarded-Prefix")
if prefix == "" {
return ""
}
parse, err := url.Parse(prefix)
if err != nil {
return ""
}
if parse.Host != "" {
return ""
}
return parse.Path
}

54
api/dashboard_test.go Normal file
View File

@@ -0,0 +1,54 @@
package api
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_safePrefix(t *testing.T) {
testCases := []struct {
desc string
value string
expected string
}{
{
desc: "host",
value: "https://example.com",
expected: "",
},
{
desc: "host with path",
value: "https://example.com/foo/bar?test",
expected: "",
},
{
desc: "path",
value: "/foo/bar",
expected: "/foo/bar",
},
{
desc: "path without leading slash",
value: "foo/bar",
expected: "foo/bar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
req, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
require.NoError(t, err)
req.Header.Set("X-Forwarded-Prefix", test.value)
prefix := safePrefix(req)
assert.Equal(t, test.expected, prefix)
})
}
}

View File

@@ -159,6 +159,9 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}
@@ -654,6 +657,9 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}
@@ -1000,6 +1006,9 @@ var _templatesEcsTmpl = []byte(`[backends]
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}
@@ -1325,6 +1334,9 @@ var _templatesKubernetesTmpl = []byte(`[backends]
{{if $backend.LoadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $backend.LoadBalancer.Stickiness.CookieName }}"
secure = {{ $backend.LoadBalancer.Stickiness.Secure }}
httpOnly = {{ $backend.LoadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $backend.LoadBalancer.Stickiness.SameSite }}"
{{end}}
{{if $backend.MaxConn }}
@@ -1582,6 +1594,9 @@ var _templatesKvTmpl = []byte(`[backends]
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}
@@ -1970,6 +1985,9 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}
@@ -2302,6 +2320,9 @@ var _templatesMesosTmpl = []byte(`[backends]
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}
@@ -2688,6 +2709,9 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -1,4 +1,4 @@
FROM golang:1.12-alpine
FROM golang:1.14-alpine
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \

View File

@@ -212,12 +212,6 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
}
// Thanks to SSLv3 being enabled by mistake in golang 1.12,
// If no minVersion is set, apply TLS1.0 as the minimum.
if entryPoint.TLS != nil && len(entryPoint.TLS.MinVersion) == 0 {
entryPoint.TLS.MinVersion = "VersionTLS10"
}
if entryPoint.TLS != nil && entryPoint.TLS.DefaultCertificate == nil && len(entryPoint.TLS.Certificates) > 0 {
log.Infof("No tls.defaultCertificate given for %s: using the first item in tls.certificates as a fallback.", entryPointName)
entryPoint.TLS.DefaultCertificate = &entryPoint.TLS.Certificates[0]

View File

@@ -312,9 +312,7 @@ func TestSetEffectiveConfigurationTLSMinVersion(t *testing.T) {
expected: EntryPoint{
Address: ":443",
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
TLS: &tls.TLS{
MinVersion: "VersionTLS10",
},
TLS: &tls.TLS{},
},
},
}

View File

@@ -452,6 +452,27 @@ If not, a new backend will be assigned.
# Default: a sha1 (6 chars)
#
# cookieName = "my_cookie"
# Customize secure option
#
# Optional
# Default: false
#
# secure = true
# Customize http only option
#
# Optional
# Default: false
#
# httpOnly = true
# Customize same site option.
# Can be: "none", "lax", "strict"
#
# Optional
#
# sameSite = "none"
```
The deprecated way:

View File

@@ -313,7 +313,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet |
| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | Not tested yet |
| manual | - | none, but you need to run Traefik interactively, turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | YES |
| manual | `manual` | none, but you need to run Traefik interactively, turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | YES |
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | YES |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | YES |
| [Namesilo](https://www.namesilo.com/) | `namesilo` | `NAMESILO_API_KEY` | YES |

View File

@@ -124,6 +124,9 @@ Additional settings can be defined using Consul Catalog tags.
| `<prefix>.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm. |
| `<prefix>.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions. |
| `<prefix>.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions. |
| `<prefix>.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. |
| `<prefix>.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. |
| `<prefix>.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) |
| `<prefix>.backend.loadbalancer.sticky=true` | Enables backend sticky sessions. (DEPRECATED) |
| `<prefix>.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `<prefix>.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
@@ -224,7 +227,7 @@ If you need to support multiple frontends for a service, for example when having
| `<prefix>.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `<prefix>.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `<prefix>.frontend.headers.SSLForceHost=true` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
| `<prefix>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `<prefix>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `<prefix>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `<prefix>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `<prefix>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |

View File

@@ -313,6 +313,9 @@ Labels can be used on containers to override default behavior.
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) |
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
| `traefik.backend.loadbalancer.swarm=true` | Uses Swarm's inbuilt load balancer (only relevant under Swarm Mode) [3]. |
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
@@ -412,7 +415,7 @@ It also means that Traefik will manipulate only one backend, not one backend per
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `traefik.frontend.headers.SSLForceHost=true` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |

View File

@@ -160,6 +160,9 @@ Labels can be used on task containers to override default behaviour:
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions |
| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) |
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |

View File

@@ -31,6 +31,9 @@ Traefik can be configured with a file.
method = "drr"
[backends.backend1.loadBalancer.stickiness]
cookieName = "foobar"
secure = true
httpOnly = true
sameSite = "foobar"
[backends.backend1.maxConn]
amount = 10

View File

@@ -341,7 +341,7 @@ The following security annotations are applicable on the Ingress object:
| `ingress.kubernetes.io/ssl-temporary-redirect: "true"` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `ingress.kubernetes.io/ssl-host: HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `ingress.kubernetes.io/ssl-force-host: "true"` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
| `ingress.kubernetes.io/ssl-proxy-headers: EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `ingress.kubernetes.io/ssl-proxy-headers: EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`). Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
### Authentication

View File

@@ -218,6 +218,9 @@ The following labels can be defined on Marathon applications. They adjust the be
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) |
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
@@ -303,7 +306,7 @@ The following labels can be defined on Marathon applications. They adjust the be
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `traefik.frontend.headers.SSLForceHost=true` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |

View File

@@ -132,6 +132,9 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions |
| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) |
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
@@ -215,7 +218,7 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `traefik.frontend.headers.SSLForceHost=true` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |

View File

@@ -167,6 +167,9 @@ Labels can be used on task containers to override default behavior:
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. |
| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) |
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
@@ -250,7 +253,7 @@ Labels can be used on task containers to override default behavior:
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `traefik.frontend.headers.SSLForceHost=true` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |

View File

@@ -145,7 +145,7 @@ Labels, set through extensions or the property manager, can be used on services
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-Proto:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |

View File

@@ -249,8 +249,14 @@ Multiple sets of rates can be added to each frontend, but the time periods must
```
In the above example, frontend1 is configured to limit requests by the client's ip address.
An average of 100 requests every 10 seconds is allowed and an average of 5 requests every 3 seconds.
These can "burst" up to 200 and 10 in each period respectively.
A sustained rate of 100 requests every 10 seconds (10 req/s) is allowed for rateset1, and 5 requests every 3 seconds (~1.67 req/s) for rateset2.
In addition, these can "burst" up to 200 and 10 in each period respectively.
Another way to describe the above parameters, is to use the [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket) analogy:
for rateset1, the size of the bucket is 200 drops, and it is leaking at a rate of 10 drop/s.
If the incoming rate of drops falling into the bucket gets high enough that the bucket gets filled,
any subsequent drop overflows out of the bucket (i.e. the request is discarded).
This situation holds until the incoming rate gets low enough again, and remains that way, for the water level in the bucket to go down.
Valid values for `extractorfunc` are:
* `client.ip`

View File

@@ -485,6 +485,13 @@ To enable IP white listing at the entry point level.
# useXForwardedFor = true
```
By setting the `useXForwardedFor` option, the `sourceRange` addresses will be matched against the request header `X-Forwarded-For` address list, from left to right.
!!! danger
When using Traefik behind another load-balancer, its own internal address will be appended in the `X-Forwarded-For` header.
Be sure to carefully configure the `sourceRange` as adding the internal network CIDR,
or the load-balancer address directly, will cause all requests coming from it to pass through.
## ProxyProtocol
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.

View File

@@ -0,0 +1,7 @@
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-NMWC63S');</script>
<!-- End Google Tag Manager -->

View File

@@ -235,13 +235,11 @@ Let's take a look at the labels themselves for the `app` service, which is a HTT
- "traefik.admin.port=9443"
```
We use both `container labels` and `service labels`.
We use both `container labels` and `segment labels`.
#### Container labels
First, we specify the `backend` name which corresponds to the actual service we're routing **to**.
We also tell Traefik to use the `web` network to route HTTP traffic to this container.
We tell Traefik to use the `web` network to route HTTP traffic to this container.
With the `traefik.enable` label, we tell Traefik to include this container in its internal configuration.
With the `frontend.rule` label, we tell Traefik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`.
@@ -249,20 +247,20 @@ Essentially, this is the actual rule used for Layer-7 load balancing.
Finally but not unimportantly, we tell Traefik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on.
### Service labels
#### Segment labels
`Service labels` allow managing many routes for the same container.
`Segment labels` allow managing many routes for the same container.
When both `container labels` and `service labels` are defined, `container labels` are just used as default values for missing `service labels` but no frontend/backend are going to be defined only with these labels.
Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/backends creation.
When both `container labels` and `segment labels` are defined, `container labels` are just used as default values for missing `segment labels` but no frontend/backend are going to be defined only with these labels.
Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `segment labels` during the container frontends/backends creation.
In the example, two service names are defined : `basic` and `admin`.
In the example, two segment names are defined : `basic` and `admin`.
They allow creating two frontends and two backends.
- `basic` has only one `service label` : `traefik.basic.protocol`.
- `basic` has only one `segment label` : `traefik.basic.protocol`.
Traefik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `basic` frontend and backend.
The frontend listens to incoming HTTP requests which contain the `Host` `app.my-awesome-app.org` and redirect them in `HTTP` to the port `9000` of the backend.
- `admin` has all the `services labels` needed to create the `admin` frontend and backend (`traefik.admin.frontend.rule`, `traefik.admin.protocol`, `traefik.admin.port`).
- `admin` has all the `segment labels` needed to create the `admin` frontend and backend (`traefik.admin.frontend.rule`, `traefik.admin.protocol`, `traefik.admin.port`).
Traefik will create a frontend to listen to incoming HTTP requests which contain the `Host` `admin-app.my-awesome-app.org` and redirect them in `HTTPS` to the port `9443` of the backend.
#### Gotchas and tips

View File

@@ -10,10 +10,14 @@ The config files used in this guide can be found in the [examples directory](htt
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/) on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
2. Setup ingress as an add-on. It can be enabled by the following command:
`minikube addons enable ingress`
!!! note
The guide is likely not fully adequate for a production-ready setup.
2. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
3. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
### Role Based Access Control configuration (Kubernetes 1.6+ only)
@@ -53,6 +57,12 @@ rules:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses/status
verbs:
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
@@ -98,7 +108,7 @@ metadata:
namespace: kube-system
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: traefik-ingress-controller
namespace: kube-system
@@ -164,13 +174,17 @@ metadata:
namespace: kube-system
---
kind: DaemonSet
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
selector:
matchLabels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
template:
metadata:
labels:
@@ -188,6 +202,7 @@ spec:
hostPort: 80
- name: admin
containerPort: 8080
hostPort: 8080
securityContext:
capabilities:
drop:
@@ -358,7 +373,7 @@ spec:
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/v1.7/examples/k8s/ui.yaml
```
Now lets setup an entry in our `/etc/hosts` file to route `traefik-ui.minikube` to our cluster.
Now let's setup an entry in our `/etc/hosts` file to route `traefik-ui.minikube` to our cluster.
In production you would want to set up real DNS entries.
You can get the IP address of your minikube instance by running `minikube ip`:
@@ -382,6 +397,23 @@ You can add a TLS entrypoint by adding the following `args` to the container spe
--entrypoints=Name:https Address::443 TLS
--entrypoints=Name:http Address::80
```
Now let's add the TLS port either to the deployment:
```
ports:
- name: https
containerPort: 443
```
or to the daemon set:
```
ports:
- name: https
containerPort: 443
hostPort: 443
```
To setup an HTTPS-protected ingress, you can leverage the TLS feature of the ingress resource.
@@ -503,7 +535,7 @@ First lets start by launching the pods for the cheese websites.
```yaml
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: stilton
labels:
@@ -529,7 +561,7 @@ spec:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: cheddar
labels:
@@ -555,7 +587,7 @@ spec:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: wensleydale
labels:

View File

@@ -1,6 +1,6 @@
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: stilton
labels:
@@ -33,7 +33,7 @@ spec:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: cheddar
labels:
@@ -66,7 +66,7 @@ spec:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: wensleydale
labels:

View File

@@ -6,7 +6,7 @@ metadata:
namespace: kube-system
---
kind: Deployment
apiVersion: extensions/v1beta1
apiVersion: apps/v1
metadata:
name: traefik-ingress-controller
namespace: kube-system

View File

@@ -12,7 +12,7 @@ RUN yarn install
RUN npm run build
# BUILD
FROM golang:1.12-alpine as gobuild
FROM golang:1.14-alpine as gobuild
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \

View File

@@ -57,20 +57,20 @@ func (s Server) Serve(l net.Listener) error {
if http2VerboseLogs {
log.Debugf("Attempting h2c with prior knowledge.")
}
conn, err := initH2CWithPriorKnowledge(w)
conn, err := initH2CWithPriorKnowledge(w, s.closeHijackedConnQuietly)
if err != nil {
if http2VerboseLogs {
log.Debugf("Error h2c with prior knowledge: %v", err)
}
return
}
defer conn.Close()
defer s.closeHijackedConnQuietly(conn)
h2cSrv := &http2.Server{}
h2cSrv.ServeConn(conn, &http2.ServeConnOpts{Handler: originalHandler})
return
}
if conn, err := h2cUpgrade(w, r); err == nil {
defer conn.Close()
if conn, err := h2cUpgrade(w, r, s.closeHijackedConnQuietly); err == nil {
defer s.closeHijackedConnQuietly(conn)
h2cSrv := &http2.Server{}
h2cSrv.ServeConn(conn, &http2.ServeConnOpts{Handler: originalHandler})
return
@@ -80,12 +80,24 @@ func (s Server) Serve(l net.Listener) error {
return s.Server.Serve(l)
}
func (s Server) closeHijackedConnQuietly(conn net.Conn) {
connStateKey := conn
if rwConn, ok := conn.(*rwConn); ok {
connStateKey = rwConn.Conn
}
s.ConnState(connStateKey, http.StateClosed)
if err := conn.Close(); err != nil {
log.Debugf("Error closing hijacked connection: %v", err)
}
}
// initH2CWithPriorKnowledge implements creating a h2c connection with prior
// knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn.
// All we have to do is look for the client preface that is suppose to be part
// of the body, and reforward the client preface on the net.Conn this function
// creates.
func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
func initH2CWithPriorKnowledge(w http.ResponseWriter, onFailureAfterHijack func(conn net.Conn)) (net.Conn, error) {
hijacker, ok := w.(http.Hijacker)
if !ok {
return nil, errors.New("hijack not supported")
@@ -100,6 +112,7 @@ func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
buf := make([]byte, len(expectedBody))
n, err := io.ReadFull(rw, buf)
if err != nil {
onFailureAfterHijack(conn)
return nil, fmt.Errorf("fail to read body: %v", err)
}
@@ -112,7 +125,7 @@ func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
return c, nil
}
conn.Close()
onFailureAfterHijack(conn)
if http2VerboseLogs {
log.Printf(
"Missing the request body portion of the client preface. Wanted: %v Got: %v",
@@ -139,7 +152,7 @@ func drainClientPreface(r io.Reader) error {
}
// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2).
func h2cUpgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
func h2cUpgrade(w http.ResponseWriter, r *http.Request, onFailureAfterHijack func(conn net.Conn)) (net.Conn, error) {
if !isH2CUpgrade(r.Header) {
return nil, errors.New("non-conforming h2c headers")
}
@@ -167,6 +180,7 @@ func h2cUpgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
// A conforming client will now send an H2 client preface which need to drain
// since we already sent this.
if err := drainClientPreface(rw); err != nil {
onFailureAfterHijack(conn)
return nil, err
}

View File

@@ -3,8 +3,10 @@ package auth
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"time"
goauth "github.com/abbot/go-http-auth"
"github.com/containous/traefik/log"
@@ -61,7 +63,10 @@ func NewAuthenticator(authConfig *types.Auth, tracingMiddleware *tracing.Tracing
tracingAuth.name = "Auth Digest"
tracingAuth.clientSpanKind = false
} else if authConfig.Forward != nil {
tracingAuth.handler = createAuthForwardHandler(authConfig)
tracingAuth.handler, err = createAuthForwardHandler(authConfig)
if err != nil {
return nil, err
}
tracingAuth.name = "Auth Forward"
tracingAuth.clientSpanKind = true
}
@@ -74,10 +79,38 @@ func NewAuthenticator(authConfig *types.Auth, tracingMiddleware *tracing.Tracing
return authenticator, nil
}
func createAuthForwardHandler(authConfig *types.Auth) negroni.HandlerFunc {
func createAuthForwardHandler(authConfig *types.Auth) (negroni.HandlerFunc, error) {
// Ensure our request client does not follow redirects
client := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: 30 * time.Second,
}
if authConfig.Forward.TLS != nil {
tlsConfig, err := authConfig.Forward.TLS.CreateTLSConfig()
if err != nil {
return nil, err
}
client.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: tlsConfig,
}
}
return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
Forward(authConfig.Forward, w, r, next)
})
Forward(authConfig.Forward, client, w, r, next)
}), nil
}
func createAuthDigestHandler(digestAuth *goauth.DigestAuth, authConfig *types.Auth) negroni.HandlerFunc {
return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

View File

@@ -18,28 +18,8 @@ const (
xForwardedMethod = "X-Forwarded-Method"
)
// Forward the authentication to a external server
func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// Ensure our request client does not follow redirects
httpClient := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig()
if err != nil {
tracing.SetErrorAndDebugLog(r, "Unable to configure TLS to call %s. Cause %s", config.Address, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
httpClient.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}
}
// Forward the authentication to an external server
func Forward(config *types.Forward, httpClient http.Client, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
forwardReq, err := http.NewRequest(http.MethodGet, config.Address, http.NoBody)
tracing.LogRequest(tracing.GetSpan(r), forwardReq)
if err != nil {

View File

@@ -17,7 +17,7 @@ import (
)
const (
defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
defaultRedirectRegex = `^(?:https?:\/\/)?(\[[\w:.]+\]|[\w\._-]+)(?::\d+)?(.*)$`
)
// NewEntryPointHandler create a new redirection handler base on entry point

View File

@@ -84,6 +84,34 @@ func TestNewEntryPointHandler(t *testing.T) {
url: "http://foo:80",
errorExpected: true,
},
{
desc: "IPV6 HTTP to HTTP",
entryPoint: &configuration.EntryPoint{Address: ":8080"},
url: "http://[::1]",
expectedURL: "http://[::1]:8080",
expectedStatus: http.StatusFound,
},
{
desc: "IPV6 HTTP to HTTPS",
entryPoint: &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
url: "http://[::1]",
expectedURL: "https://[::1]:443",
expectedStatus: http.StatusFound,
},
{
desc: "IPV6 HTTP with port 80 to HTTP",
entryPoint: &configuration.EntryPoint{Address: ":8080"},
url: "http://[::1]:80",
expectedURL: "http://[::1]:8080",
expectedStatus: http.StatusFound,
},
{
desc: "IPV6 HTTP with port 80 to HTTPS",
entryPoint: &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
url: "http://[::1]:80",
expectedURL: "https://[::1]:443",
expectedStatus: http.StatusFound,
},
}
for _, test := range testCases {

View File

@@ -28,6 +28,7 @@ theme:
copyright: "Copyright &copy; 2016-2019 Containous"
# only to force the use of the analytics partials
google_analytics:
- 'UA-51880359-3'
- 'docs.traefik.io'

View File

@@ -254,7 +254,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
},
},
},
}, {
},
{
desc: "Should build config with a digest auth",
nodes: []catalogUpdate{
{
@@ -416,6 +417,9 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky + "=true",
label.TraefikBackendLoadBalancerStickiness + "=true",
label.TraefikBackendLoadBalancerStickinessCookieName + "=chocolate",
label.TraefikBackendLoadBalancerStickinessSecure + "=true",
label.TraefikBackendLoadBalancerStickinessHTTPOnly + "=true",
label.TraefikBackendLoadBalancerStickinessSameSite + "=none",
label.TraefikBackendMaxConnAmount + "=666",
label.TraefikBackendMaxConnExtractorFunc + "=client.ip",
label.TraefikBackendBufferingMaxResponseBodyBytes + "=10485760",
@@ -700,6 +704,9 @@ func TestProviderBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -463,6 +463,9 @@ func TestDockerBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate",
label.TraefikBackendLoadBalancerStickinessSecure: "true",
label.TraefikBackendLoadBalancerStickinessHTTPOnly: "true",
label.TraefikBackendLoadBalancerStickinessSameSite: "none",
label.TraefikBackendMaxConnAmount: "666",
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
@@ -711,6 +714,9 @@ func TestDockerBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -412,6 +412,9 @@ func TestSwarmBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate",
label.TraefikBackendLoadBalancerStickinessSecure: "true",
label.TraefikBackendLoadBalancerStickinessHTTPOnly: "true",
label.TraefikBackendLoadBalancerStickinessSameSite: "none",
label.TraefikBackendMaxConnAmount: "666",
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
@@ -611,6 +614,9 @@ func TestSwarmBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -353,6 +353,9 @@ func TestBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky: aws.String("true"),
label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"),
label.TraefikBackendLoadBalancerStickinessSecure: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessHTTPOnly: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessSameSite: aws.String("none"),
label.TraefikBackendMaxConnAmount: aws.String("666"),
label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"),
label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"),
@@ -475,6 +478,9 @@ func TestBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{
@@ -662,6 +668,9 @@ func TestBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky: aws.String("true"),
label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"),
label.TraefikBackendLoadBalancerStickinessSecure: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessHTTPOnly: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessSameSite: aws.String("none"),
label.TraefikBackendMaxConnAmount: aws.String("666"),
label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"),
label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"),
@@ -750,6 +759,9 @@ func TestBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky: aws.String("true"),
label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"),
label.TraefikBackendLoadBalancerStickinessSecure: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessHTTPOnly: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessSameSite: aws.String("none"),
label.TraefikBackendMaxConnAmount: aws.String("666"),
label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"),
label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"),
@@ -839,6 +851,9 @@ func TestBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -309,6 +309,11 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
state: aws.StringValue(task.LastStatus),
}
} else {
if containerInstance == nil {
log.Errorf("Unable to find container instance information for %s", aws.StringValue(container.Name))
continue
}
var ports []portMapping
for _, mapping := range container.NetworkBindings {
if mapping != nil {

View File

@@ -30,6 +30,9 @@ const (
annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method"
annotationKubernetesAffinity = "ingress.kubernetes.io/affinity"
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
annotationKubernetesSessionSecure = "ingress.kubernetes.io/session-secure"
annotationKubernetesSessionHTTPOnly = "ingress.kubernetes.io/session-http-only"
annotationKubernetesSessionSameSite = "ingress.kubernetes.io/session-same-site"
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"

View File

@@ -161,6 +161,23 @@ spec:
servicePort: 80
path: /api
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/rewrite-target: /app
namespace: testing
spec:
rules:
- host: rewritetargetrootpath
http:
paths:
- backend:
serviceName: service1
servicePort: 80
path: /
---
apiVersion: extensions/v1beta1
kind: Ingress

View File

@@ -674,6 +674,12 @@ func getRuleForPath(pa extensionsv1beta1.HTTPIngressPath, i *extensionsv1beta1.I
return "", fmt.Errorf("rewrite-target must not be used together with annotation %q", annotationKubernetesRuleType)
}
rewriteTargetRule := fmt.Sprintf("ReplacePathRegex: ^%s(.*) %s$1", pa.Path, strings.TrimRight(rewriteTarget, "/"))
if pa.Path == "/" {
// If path = /, then just append the cap group, as if we don't cap the path as part of the regex,
// then when we strip the right / from the rewrite target, it ends up being missed, as removed but never returned
// this only happens when path = / because it is the only case where TrimRight will catch a leading /.
rewriteTargetRule = fmt.Sprintf("ReplacePathRegex: ^(.*) %s$1", strings.TrimRight(rewriteTarget, "/"))
}
rules = append(rules, rewriteTargetRule)
}
@@ -1145,14 +1151,16 @@ func getLoadBalancer(service *corev1.Service) *types.LoadBalancer {
}
func getStickiness(service *corev1.Service) *types.Stickiness {
if getBoolValue(service.Annotations, annotationKubernetesAffinity, false) {
stickiness := &types.Stickiness{}
if cookieName := getStringValue(service.Annotations, annotationKubernetesSessionCookieName, ""); len(cookieName) > 0 {
stickiness.CookieName = cookieName
}
return stickiness
if !getBoolValue(service.Annotations, annotationKubernetesAffinity, false) {
return nil
}
return &types.Stickiness{
CookieName: getStringValue(service.Annotations, annotationKubernetesSessionCookieName, ""),
Secure: getBoolValue(service.Annotations, annotationKubernetesSessionSecure, false),
HTTPOnly: getBoolValue(service.Annotations, annotationKubernetesSessionHTTPOnly, false),
SameSite: getStringValue(service.Annotations, annotationKubernetesSessionSameSite, ""),
}
return nil
}
func getHeader(i *extensionsv1beta1.Ingress) *types.Headers {

View File

@@ -415,6 +415,12 @@ func TestProvider_loadIngresses(t *testing.T) {
server("http://example.com", weight(1))),
lbMethod("wrr"),
),
backend("rewritetargetrootpath/",
servers(
server("http://example.com", weight(1)),
server("http://example.com", weight(1))),
lbMethod("wrr"),
),
backend("error-pages/errorpages",
servers(
server("http://example.com", weight(1)),
@@ -523,6 +529,12 @@ func TestProvider_loadIngresses(t *testing.T) {
route("/whitelist-source-range", "PathPrefix:/whitelist-source-range"),
route("test", "Host:test")),
),
frontend("rewritetargetrootpath/",
passHostHeader(),
routes(
route("/", "PathPrefix:/;ReplacePathRegex: ^(.*) /app$1"),
route("rewritetargetrootpath", "Host:rewritetargetrootpath")),
),
frontend("rewrite/api",
passHostHeader(),
routes(
@@ -596,7 +608,7 @@ func TestProvider_loadIngresses(t *testing.T) {
passHostHeader(),
redirectRegex("root2/$", "root2/root2"),
routes(
route("/", "PathPrefix:/;ReplacePathRegex: ^/(.*) /abc$1"),
route("/", "PathPrefix:/;ReplacePathRegex: ^(.*) /abc$1"),
route("root2", "Host:root2"),
),
),

View File

@@ -14,6 +14,9 @@ const (
pathBackendLoadBalancerSticky = "/loadbalancer/sticky"
pathBackendLoadBalancerStickiness = "/loadbalancer/stickiness"
pathBackendLoadBalancerStickinessCookieName = "/loadbalancer/stickiness/cookiename"
pathBackendLoadBalancerStickinessSecure = "/loadbalancer/stickiness/secure"
pathBackendLoadBalancerStickinessHTTPOnly = "/loadbalancer/stickiness/httponly"
pathBackendLoadBalancerStickinessSameSite = "/loadbalancer/stickiness/samesite"
pathBackendMaxConnAmount = "/maxconn/amount"
pathBackendMaxConnExtractorFunc = "/maxconn/extractorfunc"
pathBackendServers = "/servers/"

View File

@@ -276,6 +276,9 @@ func (p *Provider) getLoadBalancer(rootPath string) *types.LoadBalancer {
if p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) {
lb.Stickiness = &types.Stickiness{
CookieName: p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName),
Secure: p.getBool(false, rootPath, pathBackendLoadBalancerStickinessSecure),
HTTPOnly: p.getBool(false, rootPath, pathBackendLoadBalancerStickinessHTTPOnly),
SameSite: p.get("", rootPath, pathBackendLoadBalancerStickinessSameSite),
}
}

View File

@@ -260,6 +260,9 @@ func TestProviderBuildConfiguration(t *testing.T) {
withPair(pathBackendLoadBalancerSticky, "true"),
withPair(pathBackendLoadBalancerStickiness, "true"),
withPair(pathBackendLoadBalancerStickinessCookieName, "tomate"),
withPair(pathBackendLoadBalancerStickinessSecure, "true"),
withPair(pathBackendLoadBalancerStickinessHTTPOnly, "true"),
withPair(pathBackendLoadBalancerStickinessSameSite, "none"),
withPair(pathBackendHealthCheckScheme, "http"),
withPair(pathBackendHealthCheckPath, "/health"),
withPair(pathBackendHealthCheckPort, "80"),
@@ -389,6 +392,9 @@ func TestProviderBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "tomate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{
@@ -1949,12 +1955,18 @@ func TestProviderGetLoadBalancer(t *testing.T) {
withPair(pathBackendLoadBalancerMethod, "drr"),
withPair(pathBackendLoadBalancerSticky, "true"),
withPair(pathBackendLoadBalancerStickiness, "true"),
withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"))),
withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"),
withPair(pathBackendLoadBalancerStickinessSecure, "true"),
withPair(pathBackendLoadBalancerStickinessHTTPOnly, "true"),
withPair(pathBackendLoadBalancerStickinessSameSite, "none"))),
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "aubergine",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
},

View File

@@ -26,6 +26,9 @@ const (
DefaultBackendLoadBalancerMethod = "wrr"
DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = ""
DefaultBackendLoadbalancerStickinessSecure = false
DefaultBackendLoadbalancerStickinessHTTPOnly = false
DefaultBackendLoadbalancerStickinessSameSite = ""
DefaultBackendHealthCheckPort = 0
)
@@ -45,6 +48,21 @@ func GetStringValue(labels map[string]string, labelName string, defaultValue str
return defaultValue
}
// GetStringSafeValue get string value associated to a label and check if the content is "quotable".
func GetStringSafeValue(labels map[string]string, labelName string, defaultValue string) (string, error) {
value, ok := labels[labelName]
if !ok || len(value) <= 0 {
return defaultValue, nil
}
_, err := strconv.Unquote(`"` + value + `"`)
if err != nil {
return value, err
}
return value, nil
}
// GetBoolValue get bool value associated to a label
func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
rawValue, ok := labels[labelName]

View File

@@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSplitAndTrimString(t *testing.T) {
@@ -106,6 +107,78 @@ func TestGetStringValue(t *testing.T) {
}
}
func TestGetStringSafeValue(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelName string
defaultValue string
expected string
expectedError bool
}{
{
desc: "empty labels map",
labelName: "foo",
defaultValue: "default",
expected: "default",
},
{
desc: "unescaped value",
labels: map[string]string{
"foo": `^https?://(test\.beta\.redacted\.org|redacted\.com)/(.*)`,
},
labelName: "foo",
defaultValue: "default",
expected: `^https?://(test\.beta\.redacted\.org|redacted\.com)/(.*)`,
expectedError: true,
},
{
desc: "escaped value",
labels: map[string]string{
"foo": `^https?://(test\\.beta\\.redacted\\.org|redacted\\.com)/(.*)`,
},
labelName: "foo",
defaultValue: "default",
expected: `^https?://(test\\.beta\\.redacted\\.org|redacted\\.com)/(.*)`,
},
{
desc: "empty value",
labels: map[string]string{
"foo": "",
},
labelName: "foo",
defaultValue: "default",
expected: "default",
},
{
desc: "non existing label",
labels: map[string]string{
"foo": "bar",
},
labelName: "fii",
defaultValue: "default",
expected: "default",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got, err := GetStringSafeValue(test.labels, test.labelName, test.defaultValue)
if test.expectedError {
assert.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, test.expected, got)
})
}
}
func TestGetBoolValue(t *testing.T) {
testCases := []struct {
desc string

View File

@@ -2,142 +2,150 @@ package label
// Traefik labels
const (
Prefix = "traefik."
SuffixBackend = "backend"
SuffixDomain = "domain"
SuffixEnable = "enable"
SuffixPort = "port"
SuffixPortName = "portName"
SuffixPortIndex = "portIndex"
SuffixProtocol = "protocol"
SuffixTags = "tags"
SuffixWeight = "weight"
SuffixBackendID = "backend.id"
SuffixBackendCircuitBreaker = "backend.circuitbreaker"
SuffixBackendCircuitBreakerExpression = "backend.circuitbreaker.expression"
SuffixBackendHealthCheckScheme = "backend.healthcheck.scheme"
SuffixBackendHealthCheckPath = "backend.healthcheck.path"
SuffixBackendHealthCheckPort = "backend.healthcheck.port"
SuffixBackendHealthCheckInterval = "backend.healthcheck.interval"
SuffixBackendHealthCheckHostname = "backend.healthcheck.hostname"
SuffixBackendHealthCheckHeaders = "backend.healthcheck.headers"
SuffixBackendLoadBalancer = "backend.loadbalancer"
SuffixBackendLoadBalancerMethod = SuffixBackendLoadBalancer + ".method"
SuffixBackendLoadBalancerSticky = SuffixBackendLoadBalancer + ".sticky"
SuffixBackendLoadBalancerStickiness = SuffixBackendLoadBalancer + ".stickiness"
SuffixBackendLoadBalancerStickinessCookieName = SuffixBackendLoadBalancer + ".stickiness.cookieName"
SuffixBackendMaxConnAmount = "backend.maxconn.amount"
SuffixBackendMaxConnExtractorFunc = "backend.maxconn.extractorfunc"
SuffixBackendBuffering = "backend.buffering"
SuffixBackendResponseForwardingFlushInterval = "backend.responseForwarding.flushInterval"
SuffixBackendBufferingMaxRequestBodyBytes = SuffixBackendBuffering + ".maxRequestBodyBytes"
SuffixBackendBufferingMemRequestBodyBytes = SuffixBackendBuffering + ".memRequestBodyBytes"
SuffixBackendBufferingMaxResponseBodyBytes = SuffixBackendBuffering + ".maxResponseBodyBytes"
SuffixBackendBufferingMemResponseBodyBytes = SuffixBackendBuffering + ".memResponseBodyBytes"
SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression"
SuffixFrontend = "frontend"
SuffixFrontendAuth = SuffixFrontend + ".auth"
SuffixFrontendAuthBasic = SuffixFrontendAuth + ".basic"
SuffixFrontendAuthBasicRemoveHeader = SuffixFrontendAuthBasic + ".removeHeader"
SuffixFrontendAuthBasicUsers = SuffixFrontendAuthBasic + ".users"
SuffixFrontendAuthBasicUsersFile = SuffixFrontendAuthBasic + ".usersFile"
SuffixFrontendAuthDigest = SuffixFrontendAuth + ".digest"
SuffixFrontendAuthDigestRemoveHeader = SuffixFrontendAuthDigest + ".removeHeader"
SuffixFrontendAuthDigestUsers = SuffixFrontendAuthDigest + ".users"
SuffixFrontendAuthDigestUsersFile = SuffixFrontendAuthDigest + ".usersFile"
SuffixFrontendAuthForward = SuffixFrontendAuth + ".forward"
SuffixFrontendAuthForwardAddress = SuffixFrontendAuthForward + ".address"
SuffixFrontendAuthForwardAuthResponseHeaders = SuffixFrontendAuthForward + ".authResponseHeaders"
SuffixFrontendAuthForwardTLS = SuffixFrontendAuthForward + ".tls"
SuffixFrontendAuthForwardTLSCa = SuffixFrontendAuthForwardTLS + ".ca"
SuffixFrontendAuthForwardTLSCaOptional = SuffixFrontendAuthForwardTLS + ".caOptional"
SuffixFrontendAuthForwardTLSCert = SuffixFrontendAuthForwardTLS + ".cert"
SuffixFrontendAuthForwardTLSInsecureSkipVerify = SuffixFrontendAuthForwardTLS + ".insecureSkipVerify"
SuffixFrontendAuthForwardTLSKey = SuffixFrontendAuthForwardTLS + ".key"
SuffixFrontendAuthForwardTrustForwardHeader = SuffixFrontendAuthForward + ".trustForwardHeader"
SuffixFrontendAuthHeaderField = SuffixFrontendAuth + ".headerField"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendHeaders = "frontend.headers."
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
SuffixFrontendResponseHeaders = SuffixFrontendHeaders + "customResponseHeaders"
SuffixFrontendHeadersAllowedHosts = SuffixFrontendHeaders + "allowedHosts"
SuffixFrontendHeadersHostsProxyHeaders = SuffixFrontendHeaders + "hostsProxyHeaders"
SuffixFrontendHeadersSSLForceHost = SuffixFrontendHeaders + "SSLForceHost"
SuffixFrontendHeadersSSLRedirect = SuffixFrontendHeaders + "SSLRedirect"
SuffixFrontendHeadersSSLTemporaryRedirect = SuffixFrontendHeaders + "SSLTemporaryRedirect"
SuffixFrontendHeadersSSLHost = SuffixFrontendHeaders + "SSLHost"
SuffixFrontendHeadersSSLProxyHeaders = SuffixFrontendHeaders + "SSLProxyHeaders"
SuffixFrontendHeadersSTSSeconds = SuffixFrontendHeaders + "STSSeconds"
SuffixFrontendHeadersSTSIncludeSubdomains = SuffixFrontendHeaders + "STSIncludeSubdomains"
SuffixFrontendHeadersSTSPreload = SuffixFrontendHeaders + "STSPreload"
SuffixFrontendHeadersForceSTSHeader = SuffixFrontendHeaders + "forceSTSHeader"
SuffixFrontendHeadersFrameDeny = SuffixFrontendHeaders + "frameDeny"
SuffixFrontendHeadersCustomFrameOptionsValue = SuffixFrontendHeaders + "customFrameOptionsValue"
SuffixFrontendHeadersContentTypeNosniff = SuffixFrontendHeaders + "contentTypeNosniff"
SuffixFrontendHeadersBrowserXSSFilter = SuffixFrontendHeaders + "browserXSSFilter"
SuffixFrontendHeadersCustomBrowserXSSValue = SuffixFrontendHeaders + "customBrowserXSSValue"
SuffixFrontendHeadersContentSecurityPolicy = SuffixFrontendHeaders + "contentSecurityPolicy"
SuffixFrontendHeadersPublicKey = SuffixFrontendHeaders + "publicKey"
SuffixFrontendHeadersReferrerPolicy = SuffixFrontendHeaders + "referrerPolicy"
SuffixFrontendHeadersIsDevelopment = SuffixFrontendHeaders + "isDevelopment"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPassTLSClientCert = "frontend.passTLSClientCert"
SuffixFrontendPassTLSClientCertPem = SuffixFrontendPassTLSClientCert + ".pem"
SuffixFrontendPassTLSClientCertInfos = SuffixFrontendPassTLSClientCert + ".infos"
SuffixFrontendPassTLSClientCertInfosIssuer = SuffixFrontendPassTLSClientCertInfos + ".issuer"
SuffixFrontendPassTLSClientCertInfosIssuerCommonName = SuffixFrontendPassTLSClientCertInfosIssuer + ".commonName"
SuffixFrontendPassTLSClientCertInfosIssuerCountry = SuffixFrontendPassTLSClientCertInfosIssuer + ".country"
SuffixFrontendPassTLSClientCertInfosIssuerDomainComponent = SuffixFrontendPassTLSClientCertInfosIssuer + ".domainComponent"
SuffixFrontendPassTLSClientCertInfosIssuerLocality = SuffixFrontendPassTLSClientCertInfosIssuer + ".locality"
SuffixFrontendPassTLSClientCertInfosIssuerOrganization = SuffixFrontendPassTLSClientCertInfosIssuer + ".organization"
SuffixFrontendPassTLSClientCertInfosIssuerProvince = SuffixFrontendPassTLSClientCertInfosIssuer + ".province"
SuffixFrontendPassTLSClientCertInfosIssuerSerialNumber = SuffixFrontendPassTLSClientCertInfosIssuer + ".serialNumber"
SuffixFrontendPassTLSClientCertInfosSubject = SuffixFrontendPassTLSClientCertInfos + ".subject"
SuffixFrontendPassTLSClientCertInfosNotAfter = SuffixFrontendPassTLSClientCertInfos + ".notAfter"
SuffixFrontendPassTLSClientCertInfosNotBefore = SuffixFrontendPassTLSClientCertInfos + ".notBefore"
SuffixFrontendPassTLSClientCertInfosSans = SuffixFrontendPassTLSClientCertInfos + ".sans"
SuffixFrontendPassTLSClientCertInfosSubjectCommonName = SuffixFrontendPassTLSClientCertInfosSubject + ".commonName"
SuffixFrontendPassTLSClientCertInfosSubjectCountry = SuffixFrontendPassTLSClientCertInfosSubject + ".country"
SuffixFrontendPassTLSClientCertInfosSubjectDomainComponent = SuffixFrontendPassTLSClientCertInfosSubject + ".domainComponent"
SuffixFrontendPassTLSClientCertInfosSubjectLocality = SuffixFrontendPassTLSClientCertInfosSubject + ".locality"
SuffixFrontendPassTLSClientCertInfosSubjectOrganization = SuffixFrontendPassTLSClientCertInfosSubject + ".organization"
SuffixFrontendPassTLSClientCertInfosSubjectProvince = SuffixFrontendPassTLSClientCertInfosSubject + ".province"
SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber = SuffixFrontendPassTLSClientCertInfosSubject + ".serialNumber"
SuffixFrontendPassTLSCert = "frontend.passTLSCert" // Deprecated
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRateLimitExtractorFunc = "frontend.rateLimit.extractorFunc"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
SuffixFrontendRule = "frontend.rule"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated
SuffixFrontendWhiteList = "frontend.whiteList."
SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange"
SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor"
TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable
TraefikPort = Prefix + SuffixPort
TraefikPortName = Prefix + SuffixPortName
TraefikPortIndex = Prefix + SuffixPortIndex
TraefikProtocol = Prefix + SuffixProtocol
TraefikTags = Prefix + SuffixTags
TraefikWeight = Prefix + SuffixWeight
TraefikBackend = Prefix + SuffixBackend
TraefikBackendID = Prefix + SuffixBackendID
TraefikBackendCircuitBreaker = Prefix + SuffixBackendCircuitBreaker
TraefikBackendCircuitBreakerExpression = Prefix + SuffixBackendCircuitBreakerExpression
TraefikBackendHealthCheckScheme = Prefix + SuffixBackendHealthCheckScheme
TraefikBackendHealthCheckPath = Prefix + SuffixBackendHealthCheckPath
TraefikBackendHealthCheckPort = Prefix + SuffixBackendHealthCheckPort
TraefikBackendHealthCheckInterval = Prefix + SuffixBackendHealthCheckInterval
TraefikBackendHealthCheckHostname = Prefix + SuffixBackendHealthCheckHostname
TraefikBackendHealthCheckHeaders = Prefix + SuffixBackendHealthCheckHeaders
TraefikBackendLoadBalancer = Prefix + SuffixBackendLoadBalancer
TraefikBackendLoadBalancerMethod = Prefix + SuffixBackendLoadBalancerMethod
TraefikBackendLoadBalancerSticky = Prefix + SuffixBackendLoadBalancerSticky
TraefikBackendLoadBalancerStickiness = Prefix + SuffixBackendLoadBalancerStickiness
TraefikBackendLoadBalancerStickinessCookieName = Prefix + SuffixBackendLoadBalancerStickinessCookieName
Prefix = "traefik."
SuffixBackend = "backend"
SuffixDomain = "domain"
SuffixEnable = "enable"
SuffixPort = "port"
SuffixPortName = "portName"
SuffixPortIndex = "portIndex"
SuffixProtocol = "protocol"
SuffixTags = "tags"
SuffixWeight = "weight"
SuffixBackendID = "backend.id"
SuffixBackendCircuitBreaker = "backend.circuitbreaker"
SuffixBackendCircuitBreakerExpression = "backend.circuitbreaker.expression"
SuffixBackendHealthCheckScheme = "backend.healthcheck.scheme"
SuffixBackendHealthCheckPath = "backend.healthcheck.path"
SuffixBackendHealthCheckPort = "backend.healthcheck.port"
SuffixBackendHealthCheckInterval = "backend.healthcheck.interval"
SuffixBackendHealthCheckHostname = "backend.healthcheck.hostname"
SuffixBackendHealthCheckHeaders = "backend.healthcheck.headers"
SuffixBackendLoadBalancer = "backend.loadbalancer"
SuffixBackendLoadBalancerMethod = SuffixBackendLoadBalancer + ".method"
SuffixBackendLoadBalancerSticky = SuffixBackendLoadBalancer + ".sticky"
SuffixBackendLoadBalancerStickiness = SuffixBackendLoadBalancer + ".stickiness"
SuffixBackendLoadBalancerStickinessCookieName = SuffixBackendLoadBalancer + ".stickiness.cookieName"
SuffixBackendLoadBalancerStickinessSecure = SuffixBackendLoadBalancer + ".stickiness.secure"
SuffixBackendLoadBalancerStickinessCHTTPOnly = SuffixBackendLoadBalancer + ".stickiness.httpOnly"
SuffixBackendLoadBalancerStickinessSameSite = SuffixBackendLoadBalancer + ".stickiness.sameSite"
SuffixBackendMaxConnAmount = "backend.maxconn.amount"
SuffixBackendMaxConnExtractorFunc = "backend.maxconn.extractorfunc"
SuffixBackendBuffering = "backend.buffering"
SuffixBackendResponseForwardingFlushInterval = "backend.responseForwarding.flushInterval"
SuffixBackendBufferingMaxRequestBodyBytes = SuffixBackendBuffering + ".maxRequestBodyBytes"
SuffixBackendBufferingMemRequestBodyBytes = SuffixBackendBuffering + ".memRequestBodyBytes"
SuffixBackendBufferingMaxResponseBodyBytes = SuffixBackendBuffering + ".maxResponseBodyBytes"
SuffixBackendBufferingMemResponseBodyBytes = SuffixBackendBuffering + ".memResponseBodyBytes"
SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression"
SuffixFrontend = "frontend"
SuffixFrontendAuth = SuffixFrontend + ".auth"
SuffixFrontendAuthBasic = SuffixFrontendAuth + ".basic"
SuffixFrontendAuthBasicRemoveHeader = SuffixFrontendAuthBasic + ".removeHeader"
SuffixFrontendAuthBasicUsers = SuffixFrontendAuthBasic + ".users"
SuffixFrontendAuthBasicUsersFile = SuffixFrontendAuthBasic + ".usersFile"
SuffixFrontendAuthDigest = SuffixFrontendAuth + ".digest"
SuffixFrontendAuthDigestRemoveHeader = SuffixFrontendAuthDigest + ".removeHeader"
SuffixFrontendAuthDigestUsers = SuffixFrontendAuthDigest + ".users"
SuffixFrontendAuthDigestUsersFile = SuffixFrontendAuthDigest + ".usersFile"
SuffixFrontendAuthForward = SuffixFrontendAuth + ".forward"
SuffixFrontendAuthForwardAddress = SuffixFrontendAuthForward + ".address"
SuffixFrontendAuthForwardAuthResponseHeaders = SuffixFrontendAuthForward + ".authResponseHeaders"
SuffixFrontendAuthForwardTLS = SuffixFrontendAuthForward + ".tls"
SuffixFrontendAuthForwardTLSCa = SuffixFrontendAuthForwardTLS + ".ca"
SuffixFrontendAuthForwardTLSCaOptional = SuffixFrontendAuthForwardTLS + ".caOptional"
SuffixFrontendAuthForwardTLSCert = SuffixFrontendAuthForwardTLS + ".cert"
SuffixFrontendAuthForwardTLSInsecureSkipVerify = SuffixFrontendAuthForwardTLS + ".insecureSkipVerify"
SuffixFrontendAuthForwardTLSKey = SuffixFrontendAuthForwardTLS + ".key"
SuffixFrontendAuthForwardTrustForwardHeader = SuffixFrontendAuthForward + ".trustForwardHeader"
SuffixFrontendAuthHeaderField = SuffixFrontendAuth + ".headerField"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendHeaders = "frontend.headers."
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
SuffixFrontendResponseHeaders = SuffixFrontendHeaders + "customResponseHeaders"
SuffixFrontendHeadersAllowedHosts = SuffixFrontendHeaders + "allowedHosts"
SuffixFrontendHeadersHostsProxyHeaders = SuffixFrontendHeaders + "hostsProxyHeaders"
SuffixFrontendHeadersSSLForceHost = SuffixFrontendHeaders + "SSLForceHost"
SuffixFrontendHeadersSSLRedirect = SuffixFrontendHeaders + "SSLRedirect"
SuffixFrontendHeadersSSLTemporaryRedirect = SuffixFrontendHeaders + "SSLTemporaryRedirect"
SuffixFrontendHeadersSSLHost = SuffixFrontendHeaders + "SSLHost"
SuffixFrontendHeadersSSLProxyHeaders = SuffixFrontendHeaders + "SSLProxyHeaders"
SuffixFrontendHeadersSTSSeconds = SuffixFrontendHeaders + "STSSeconds"
SuffixFrontendHeadersSTSIncludeSubdomains = SuffixFrontendHeaders + "STSIncludeSubdomains"
SuffixFrontendHeadersSTSPreload = SuffixFrontendHeaders + "STSPreload"
SuffixFrontendHeadersForceSTSHeader = SuffixFrontendHeaders + "forceSTSHeader"
SuffixFrontendHeadersFrameDeny = SuffixFrontendHeaders + "frameDeny"
SuffixFrontendHeadersCustomFrameOptionsValue = SuffixFrontendHeaders + "customFrameOptionsValue"
SuffixFrontendHeadersContentTypeNosniff = SuffixFrontendHeaders + "contentTypeNosniff"
SuffixFrontendHeadersBrowserXSSFilter = SuffixFrontendHeaders + "browserXSSFilter"
SuffixFrontendHeadersCustomBrowserXSSValue = SuffixFrontendHeaders + "customBrowserXSSValue"
SuffixFrontendHeadersContentSecurityPolicy = SuffixFrontendHeaders + "contentSecurityPolicy"
SuffixFrontendHeadersPublicKey = SuffixFrontendHeaders + "publicKey"
SuffixFrontendHeadersReferrerPolicy = SuffixFrontendHeaders + "referrerPolicy"
SuffixFrontendHeadersIsDevelopment = SuffixFrontendHeaders + "isDevelopment"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPassTLSClientCert = "frontend.passTLSClientCert"
SuffixFrontendPassTLSClientCertPem = SuffixFrontendPassTLSClientCert + ".pem"
SuffixFrontendPassTLSClientCertInfos = SuffixFrontendPassTLSClientCert + ".infos"
SuffixFrontendPassTLSClientCertInfosIssuer = SuffixFrontendPassTLSClientCertInfos + ".issuer"
SuffixFrontendPassTLSClientCertInfosIssuerCommonName = SuffixFrontendPassTLSClientCertInfosIssuer + ".commonName"
SuffixFrontendPassTLSClientCertInfosIssuerCountry = SuffixFrontendPassTLSClientCertInfosIssuer + ".country"
SuffixFrontendPassTLSClientCertInfosIssuerDomainComponent = SuffixFrontendPassTLSClientCertInfosIssuer + ".domainComponent"
SuffixFrontendPassTLSClientCertInfosIssuerLocality = SuffixFrontendPassTLSClientCertInfosIssuer + ".locality"
SuffixFrontendPassTLSClientCertInfosIssuerOrganization = SuffixFrontendPassTLSClientCertInfosIssuer + ".organization"
SuffixFrontendPassTLSClientCertInfosIssuerProvince = SuffixFrontendPassTLSClientCertInfosIssuer + ".province"
SuffixFrontendPassTLSClientCertInfosIssuerSerialNumber = SuffixFrontendPassTLSClientCertInfosIssuer + ".serialNumber"
SuffixFrontendPassTLSClientCertInfosSubject = SuffixFrontendPassTLSClientCertInfos + ".subject"
SuffixFrontendPassTLSClientCertInfosNotAfter = SuffixFrontendPassTLSClientCertInfos + ".notAfter"
SuffixFrontendPassTLSClientCertInfosNotBefore = SuffixFrontendPassTLSClientCertInfos + ".notBefore"
SuffixFrontendPassTLSClientCertInfosSans = SuffixFrontendPassTLSClientCertInfos + ".sans"
SuffixFrontendPassTLSClientCertInfosSubjectCommonName = SuffixFrontendPassTLSClientCertInfosSubject + ".commonName"
SuffixFrontendPassTLSClientCertInfosSubjectCountry = SuffixFrontendPassTLSClientCertInfosSubject + ".country"
SuffixFrontendPassTLSClientCertInfosSubjectDomainComponent = SuffixFrontendPassTLSClientCertInfosSubject + ".domainComponent"
SuffixFrontendPassTLSClientCertInfosSubjectLocality = SuffixFrontendPassTLSClientCertInfosSubject + ".locality"
SuffixFrontendPassTLSClientCertInfosSubjectOrganization = SuffixFrontendPassTLSClientCertInfosSubject + ".organization"
SuffixFrontendPassTLSClientCertInfosSubjectProvince = SuffixFrontendPassTLSClientCertInfosSubject + ".province"
SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber = SuffixFrontendPassTLSClientCertInfosSubject + ".serialNumber"
SuffixFrontendPassTLSCert = "frontend.passTLSCert" // Deprecated
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRateLimitExtractorFunc = "frontend.rateLimit.extractorFunc"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
SuffixFrontendRule = "frontend.rule"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated
SuffixFrontendWhiteList = "frontend.whiteList."
SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange"
SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor"
TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable
TraefikPort = Prefix + SuffixPort
TraefikPortName = Prefix + SuffixPortName
TraefikPortIndex = Prefix + SuffixPortIndex
TraefikProtocol = Prefix + SuffixProtocol
TraefikTags = Prefix + SuffixTags
TraefikWeight = Prefix + SuffixWeight
TraefikBackend = Prefix + SuffixBackend
TraefikBackendID = Prefix + SuffixBackendID
TraefikBackendCircuitBreaker = Prefix + SuffixBackendCircuitBreaker
TraefikBackendCircuitBreakerExpression = Prefix + SuffixBackendCircuitBreakerExpression
TraefikBackendHealthCheckScheme = Prefix + SuffixBackendHealthCheckScheme
TraefikBackendHealthCheckPath = Prefix + SuffixBackendHealthCheckPath
TraefikBackendHealthCheckPort = Prefix + SuffixBackendHealthCheckPort
TraefikBackendHealthCheckInterval = Prefix + SuffixBackendHealthCheckInterval
TraefikBackendHealthCheckHostname = Prefix + SuffixBackendHealthCheckHostname
TraefikBackendHealthCheckHeaders = Prefix + SuffixBackendHealthCheckHeaders
TraefikBackendLoadBalancer = Prefix + SuffixBackendLoadBalancer
TraefikBackendLoadBalancerMethod = Prefix + SuffixBackendLoadBalancerMethod
TraefikBackendLoadBalancerSticky = Prefix + SuffixBackendLoadBalancerSticky
TraefikBackendLoadBalancerStickiness = Prefix + SuffixBackendLoadBalancerStickiness
TraefikBackendLoadBalancerStickinessCookieName = Prefix + SuffixBackendLoadBalancerStickinessCookieName
// FIXME
TraefikBackendLoadBalancerStickinessSecure = Prefix + SuffixBackendLoadBalancerStickinessSecure
TraefikBackendLoadBalancerStickinessHTTPOnly = Prefix + SuffixBackendLoadBalancerStickinessCHTTPOnly
TraefikBackendLoadBalancerStickinessSameSite = Prefix + SuffixBackendLoadBalancerStickinessSameSite
TraefikBackendMaxConnAmount = Prefix + SuffixBackendMaxConnAmount
TraefikBackendMaxConnExtractorFunc = Prefix + SuffixBackendMaxConnExtractorFunc
TraefikBackendBuffering = Prefix + SuffixBackendBuffering

View File

@@ -50,8 +50,13 @@ func GetRedirect(labels map[string]string) *types.Redirect {
if Has(labels, TraefikFrontendRedirectRegex) &&
Has(labels, TraefikFrontendRedirectReplacement) {
value, err := GetStringSafeValue(labels, TraefikFrontendRedirectRegex, "")
if err != nil {
log.Errorf("Invalid regex syntax: %s", value)
return nil
}
return &types.Redirect{
Regex: GetStringValue(labels, TraefikFrontendRedirectRegex, ""),
Regex: value,
Replacement: GetStringValue(labels, TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
@@ -421,8 +426,12 @@ func GetLoadBalancer(labels map[string]string) *types.LoadBalancer {
}
if GetBoolValue(labels, TraefikBackendLoadBalancerStickiness, false) {
cookieName := GetStringValue(labels, TraefikBackendLoadBalancerStickinessCookieName, DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
lb.Stickiness = &types.Stickiness{
CookieName: GetStringValue(labels, TraefikBackendLoadBalancerStickinessCookieName, DefaultBackendLoadbalancerStickinessCookieName),
Secure: GetBoolValue(labels, TraefikBackendLoadBalancerStickinessSecure, DefaultBackendLoadbalancerStickinessSecure),
HTTPOnly: GetBoolValue(labels, TraefikBackendLoadBalancerStickinessHTTPOnly, DefaultBackendLoadbalancerStickinessHTTPOnly),
SameSite: GetStringValue(labels, TraefikBackendLoadBalancerStickinessSameSite, DefaultBackendLoadbalancerStickinessSameSite),
}
}
return lb

View File

@@ -257,12 +257,18 @@ func TestGetLoadBalancer(t *testing.T) {
TraefikBackendLoadBalancerSticky: "true",
TraefikBackendLoadBalancerStickiness: "true",
TraefikBackendLoadBalancerStickinessCookieName: "foo",
TraefikBackendLoadBalancerStickinessSecure: "true",
TraefikBackendLoadBalancerStickinessHTTPOnly: "true",
TraefikBackendLoadBalancerStickinessSameSite: "none",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "foo",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
},
@@ -449,12 +455,19 @@ func TestGetRedirect(t *testing.T) {
labels map[string]string
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return nil when regex syntax is invalid",
labels: map[string]string{
TraefikFrontendRedirectRegex: `^https?://(test\.beta\.redacted\.org|redacted\.com)/(.*)`,
TraefikFrontendRedirectReplacement: "$1",
},
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
labels: map[string]string{
@@ -805,6 +818,7 @@ func TestGetAuth(t *testing.T) {
})
}
}
func TestGetPassTLSClientCert(t *testing.T) {
testCases := []struct {
desc string

View File

@@ -101,6 +101,9 @@ func TestBuildConfigurationSegments(t *testing.T) {
withLabel(label.TraefikBackendLoadBalancerSticky, "true"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
@@ -347,6 +350,9 @@ func TestBuildConfigurationSegments(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -369,6 +369,9 @@ func TestBuildConfiguration(t *testing.T) {
withLabel(label.TraefikBackendLoadBalancerSticky, "true"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
@@ -613,6 +616,9 @@ func TestBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -119,6 +119,9 @@ func TestBuildConfigurationSegments(t *testing.T) {
withLabel(label.TraefikBackendLoadBalancerSticky, "true"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
@@ -368,6 +371,9 @@ func TestBuildConfigurationSegments(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -325,6 +325,9 @@ func TestBuildConfiguration(t *testing.T) {
withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
@@ -572,6 +575,9 @@ func TestBuildConfiguration(t *testing.T) {
Method: "drr",
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -52,6 +52,9 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate",
label.TraefikBackendLoadBalancerStickinessSecure: "true",
label.TraefikBackendLoadBalancerStickinessHTTPOnly: "true",
label.TraefikBackendLoadBalancerStickinessSameSite: "none",
label.TraefikBackendMaxConnAmount: "666",
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
@@ -304,6 +307,9 @@ func TestProviderBuildConfiguration(t *testing.T) {
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
Secure: true,
HTTPOnly: true,
SameSite: "none",
},
},
MaxConn: &types.MaxConn{

View File

@@ -218,7 +218,10 @@ func NewServer(globalConfiguration configuration.GlobalConfiguration, provider p
log.Errorf("failed to create HTTP transport: %v", err)
}
server.defaultForwardingRoundTripper = transport
server.defaultForwardingRoundTripper, err = newSmartRoundTripper(transport)
if err != nil {
log.Errorf("Failed to create HTTP transport: %v", err)
}
server.tracingMiddleware = globalConfiguration.Tracing
if server.tracingMiddleware != nil && server.tracingMiddleware.Backend != "" {
@@ -497,6 +500,12 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TL
config.Certificates = []tls.Certificate{}
}
// workaround for users who used GODEBUG to activate TLS1.3
config.MaxVersion = tls.VersionTLS12
if strings.Contains(os.Getenv("GODEBUG"), "tls13=1") {
config.MaxVersion = tls.VersionTLS13
}
// Set the minimum TLS version if set in the config TOML
if minConst, exists := traefiktls.MinVersion[s.entryPoints[entryPointName].Configuration.TLS.MinVersion]; exists {
config.PreferServerCipherSuites = true
@@ -505,7 +514,7 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TL
// Set the list of CipherSuites if set in the config TOML
if s.entryPoints[entryPointName].Configuration.TLS.CipherSuites != nil {
// if our list of CipherSuites is defined in the entrypoint config, we can re-initilize the suites list as empty
// if our list of CipherSuites is defined in the entrypoint config, we can re-initialize the suites list as empty
config.CipherSuites = make([]uint16, 0)
for _, cipher := range s.entryPoints[entryPointName].Configuration.TLS.CipherSuites {
if cipherConst, exists := traefiktls.CipherSuites[cipher]; exists {

View File

@@ -237,11 +237,17 @@ func (s *Server) buildForwarder(entryPointName string, entryPoint *configuration
}
}
var tlsConfig *tls.Config
if smartRt, ok := roundTripper.(*smartRoundTripper); ok {
tlsConfig = smartRt.GetTLSClientConfig()
}
var fwd http.Handler
fwd, err = forward.New(
forward.Stream(true),
forward.PassHostHeader(frontend.PassHostHeader),
forward.RoundTripper(roundTripper),
forward.WebsocketTLSClientConfig(tlsConfig),
forward.Rewriter(rewriter),
forward.ResponseModifier(responseModifier),
forward.BufferPool(s.bufferPool),
@@ -303,7 +309,6 @@ func buildServerRoute(serverEntryPoint *serverEntryPoint, frontendName string, f
func (s *Server) preLoadConfiguration(configMsg types.ConfigMessage) {
providersThrottleDuration := time.Duration(s.globalConfiguration.ProvidersThrottleDuration)
s.defaultConfigurationValues(configMsg.Configuration)
currentConfigurations := s.currentConfigurations.Get().(types.Configurations)
if log.GetLevel() == logrus.DebugLevel {
jsonConf, _ := json.Marshal(configMsg.Configuration)
@@ -315,11 +320,6 @@ func (s *Server) preLoadConfiguration(configMsg types.ConfigMessage) {
return
}
if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
log.Infof("Skipping same configuration for provider %s", configMsg.ProviderName)
return
}
providerConfigUpdateCh, ok := s.providerConfigUpdateMap[configMsg.ProviderName]
if !ok {
providerConfigUpdateCh = make(chan types.ConfigMessage)
@@ -455,11 +455,17 @@ func (s *Server) throttleProviderConfigReload(throttle time.Duration, publish ch
}
})
var previousConfig types.ConfigMessage
for {
select {
case <-stop:
return
case nextConfig := <-in:
if reflect.DeepEqual(previousConfig, nextConfig) {
log.Infof("Skipping same configuration for provider %s", nextConfig.ProviderName)
continue
}
previousConfig = nextConfig
ring.In() <- nextConfig
}
}

View File

@@ -304,9 +304,11 @@ func TestThrottleProviderConfigReload(t *testing.T) {
}
}()
// publish 5 new configs, one new config each 10 milliseconds
// publish 5 new and different configs, one new config each 10 milliseconds
for i := 0; i < 5; i++ {
providerConfig <- types.ConfigMessage{}
providerConfig <- types.ConfigMessage{
ProviderName: fmt.Sprintf("test-%d", i),
}
time.Sleep(10 * time.Millisecond)
}

View File

@@ -123,13 +123,6 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back
rr, _ = roundrobin.New(fwd)
}
var stickySession *roundrobin.StickySession
var cookieName string
if stickiness := backend.LoadBalancer.Stickiness; stickiness != nil {
cookieName = cookie.GetName(stickiness.CookieName, backendName)
stickySession = roundrobin.NewStickySession(cookieName)
}
lbMethod, err := types.NewLoadBalancerMethod(backend.LoadBalancer)
if err != nil {
return nil, fmt.Errorf("error loading load balancer method '%+v' for frontend %s: %v", backend.LoadBalancer, frontendName, err)
@@ -141,8 +134,10 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back
case types.Drr:
log.Debug("Creating load-balancer drr")
if stickySession != nil {
log.Debugf("Sticky session with cookie %v", cookieName)
if backend.LoadBalancer.Stickiness != nil {
cookieName := cookie.GetName(backend.LoadBalancer.Stickiness.CookieName, backendName)
stickySession := newStickySession(cookieName, backend.LoadBalancer.Stickiness)
lb, err = roundrobin.NewRebalancer(rr, roundrobin.RebalancerStickySession(stickySession))
if err != nil {
@@ -157,16 +152,19 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back
case types.Wrr:
log.Debug("Creating load-balancer wrr")
if stickySession != nil {
log.Debugf("Sticky session with cookie %v", cookieName)
if backend.LoadBalancer.Stickiness != nil {
cookieName := cookie.GetName(backend.LoadBalancer.Stickiness.CookieName, backendName)
stickySession := newStickySession(cookieName, backend.LoadBalancer.Stickiness)
option := roundrobin.EnableStickySession(stickySession)
if s.accessLoggerMiddleware != nil {
lb, err = roundrobin.New(saveFrontend, roundrobin.EnableStickySession(stickySession))
lb, err = roundrobin.New(saveFrontend, option)
if err != nil {
return nil, err
}
} else {
lb, err = roundrobin.New(fwd, roundrobin.EnableStickySession(stickySession))
lb, err = roundrobin.New(fwd, option)
if err != nil {
return nil, err
}
@@ -185,6 +183,30 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back
return lb, nil
}
func newStickySession(cookieName string, stickiness *types.Stickiness) *roundrobin.StickySession {
log.Debugf("Sticky session with cookie %v", cookieName)
opts := roundrobin.CookieOptions{
HTTPOnly: stickiness.HTTPOnly,
Secure: stickiness.Secure,
SameSite: convertSameSite(stickiness.SameSite),
}
return roundrobin.NewStickySessionWithOptions(cookieName, opts)
}
func convertSameSite(sameSite string) http.SameSite {
switch sameSite {
case "none":
return http.SameSiteNoneMode
case "lax":
return http.SameSiteLaxMode
case "strict":
return http.SameSiteStrictMode
default:
return 0
}
}
func (s *Server) configureLBServers(lb healthcheck.BalancerHandler, backend *types.Backend, backendName string) error {
for name, srv := range backend.Servers {
u, err := url.Parse(srv.URL)
@@ -206,23 +228,29 @@ func (s *Server) configureLBServers(lb healthcheck.BalancerHandler, backend *typ
// getRoundTripper will either use server.defaultForwardingRoundTripper or create a new one
// given a custom TLS configuration is passed and the passTLSCert option is set to true.
func (s *Server) getRoundTripper(entryPointName string, passTLSCert bool, tls *traefiktls.TLS) (http.RoundTripper, error) {
if passTLSCert {
tlsConfig, err := createClientTLSConfig(entryPointName, tls)
if err != nil {
return nil, fmt.Errorf("failed to create TLSClientConfig: %v", err)
}
tlsConfig.InsecureSkipVerify = s.globalConfiguration.InsecureSkipVerify
transport, err := createHTTPTransport(s.globalConfiguration)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP transport: %v", err)
}
transport.TLSClientConfig = tlsConfig
return transport, nil
if !passTLSCert {
return s.defaultForwardingRoundTripper, nil
}
return s.defaultForwardingRoundTripper, nil
tlsConfig, err := createClientTLSConfig(entryPointName, tls)
if err != nil {
return nil, fmt.Errorf("failed to create TLSClientConfig: %v", err)
}
tlsConfig.InsecureSkipVerify = s.globalConfiguration.InsecureSkipVerify
transport, err := createHTTPTransport(s.globalConfiguration)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP transport: %v", err)
}
transport.TLSClientConfig = tlsConfig
smartTransport, err := newSmartRoundTripper(transport)
if err != nil {
return nil, err
}
return smartTransport, nil
}
// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings.
@@ -263,25 +291,21 @@ func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration)
transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
}
if globalConfiguration.InsecureSkipVerify {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if len(globalConfiguration.RootCAs) > 0 {
if globalConfiguration.InsecureSkipVerify || len(globalConfiguration.RootCAs) > 0 {
transport.TLSClientConfig = &tls.Config{
RootCAs: createRootCACertPool(globalConfiguration.RootCAs),
InsecureSkipVerify: globalConfiguration.InsecureSkipVerify,
RootCAs: createRootCACertPool(globalConfiguration.RootCAs),
}
}
err := http2.ConfigureTransport(transport)
if err != nil {
return nil, err
}
return transport, nil
}
func createRootCACertPool(rootCAs traefiktls.FilesOrContents) *x509.CertPool {
if len(rootCAs) == 0 {
return nil
}
roots := x509.NewCertPool()
for _, cert := range rootCAs {

View File

@@ -0,0 +1,43 @@
package server
import (
"crypto/tls"
"net/http"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
)
func newSmartRoundTripper(transport *http.Transport) (http.RoundTripper, error) {
transportHTTP1 := transport.Clone()
err := http2.ConfigureTransport(transport)
if err != nil {
return nil, err
}
return &smartRoundTripper{
http2: transport,
http: transportHTTP1,
}, nil
}
// smartRoundTripper implements RoundTrip while making sure that HTTP/2 is not used
// with protocols that start with a Connection Upgrade, such as SPDY or Websocket.
type smartRoundTripper struct {
http2 *http.Transport
http *http.Transport
}
func (m *smartRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// If we have a connection upgrade, we don't use HTTP2
if httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {
return m.http.RoundTrip(req)
}
return m.http2.RoundTrip(req)
}
func (m *smartRoundTripper) GetTLSClientConfig() *tls.Config {
return m.http2.TLSClientConfig
}

View File

@@ -24,6 +24,9 @@
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -23,6 +23,9 @@
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -22,6 +22,9 @@
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -19,6 +19,9 @@
{{if $backend.LoadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $backend.LoadBalancer.Stickiness.CookieName }}"
secure = {{ $backend.LoadBalancer.Stickiness.Secure }}
httpOnly = {{ $backend.LoadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $backend.LoadBalancer.Stickiness.SameSite }}"
{{end}}
{{if $backend.MaxConn }}

View File

@@ -22,6 +22,9 @@
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -25,6 +25,9 @@
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -25,6 +25,9 @@
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -24,6 +24,9 @@
{{if $loadBalancer.Stickiness }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
secure = {{ $loadBalancer.Stickiness.Secure }}
httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }}
sameSite = "{{ $loadBalancer.Stickiness.SameSite }}"
{{end}}
{{end}}

View File

@@ -25,32 +25,34 @@ var (
// CipherSuites Map of TLS CipherSuites from crypto/tls
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
CipherSuites = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
"TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV,
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
"TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV,
}
)
@@ -120,6 +122,7 @@ func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, erro
}
}
}
config.BuildNameToCertificate()
return config, nil
}

102
tls/certificate_test.go Normal file
View File

@@ -0,0 +1,102 @@
package tls
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCertificates_CreateTLSConfig(t *testing.T) {
var cert Certificates
certificate1, err := generateCertificate("test1.traefik.com")
require.NoError(t, err)
require.NotNil(t, certificate1)
cert = append(cert, *certificate1)
certificate2, err := generateCertificate("test2.traefik.com")
require.NoError(t, err)
require.NotNil(t, certificate2)
cert = append(cert, *certificate2)
config, err := cert.CreateTLSConfig("http")
require.NoError(t, err)
assert.Len(t, config.NameToCertificate, 2)
assert.Contains(t, config.NameToCertificate, "test1.traefik.com")
assert.Contains(t, config.NameToCertificate, "test2.traefik.com")
}
func generateCertificate(domain string) (*Certificate, error) {
certPEM, keyPEM, err := keyPair(domain, time.Time{})
if err != nil {
return nil, err
}
return &Certificate{
CertFile: FileOrContent(certPEM),
KeyFile: FileOrContent(keyPEM),
}, nil
}
// keyPair generates cert and key files
func keyPair(domain string, expiration time.Time) ([]byte, []byte, error) {
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
certPEM, err := PemCert(rsaPrivKey, domain, expiration)
if err != nil {
return nil, nil, err
}
return certPEM, keyPEM, nil
}
// PemCert generates PEM cert file
func PemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) {
derBytes, err := derCert(privKey, expiration, domain)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
}
func derCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
if expiration.IsZero() {
expiration = time.Now().Add(365 * (24 * time.Hour))
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: domain,
},
NotBefore: time.Now(),
NotAfter: expiration,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageDataEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{domain},
}
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
}

View File

@@ -52,6 +52,9 @@ type LoadBalancer struct {
// Stickiness holds sticky session configuration.
type Stickiness struct {
CookieName string `json:"cookieName,omitempty"`
Secure bool `json:"secure,omitempty"`
HTTPOnly bool `json:"httpOnly,omitempty"`
SameSite string `json:"sameSite,omitempty"`
}
// CircuitBreaker holds circuit breaker configuration.

View File

@@ -16,18 +16,18 @@ type DomainsService struct {
}
type DomainInfo struct {
DomainTotal int `json:"domain_total,omitempty"`
AllTotal int `json:"all_total,omitempty"`
MineTotal int `json:"mine_total,omitempty"`
ShareTotal string `json:"share_total,omitempty"`
VipTotal int `json:"vip_total,omitempty"`
IsMarkTotal int `json:"ismark_total,omitempty"`
PauseTotal int `json:"pause_total,omitempty"`
ErrorTotal int `json:"error_total,omitempty"`
LockTotal int `json:"lock_total,omitempty"`
SpamTotal int `json:"spam_total,omitempty"`
VipExpire int `json:"vip_expire,omitempty"`
ShareOutTotal int `json:"share_out_total,omitempty"`
DomainTotal json.Number `json:"domain_total,omitempty"`
AllTotal json.Number `json:"all_total,omitempty"`
MineTotal json.Number `json:"mine_total,omitempty"`
ShareTotal json.Number `json:"share_total,omitempty"`
VipTotal json.Number `json:"vip_total,omitempty"`
IsMarkTotal json.Number `json:"ismark_total,omitempty"`
PauseTotal json.Number `json:"pause_total,omitempty"`
ErrorTotal json.Number `json:"error_total,omitempty"`
LockTotal json.Number `json:"lock_total,omitempty"`
SpamTotal json.Number `json:"spam_total,omitempty"`
VipExpire json.Number `json:"vip_expire,omitempty"`
ShareOutTotal json.Number `json:"share_out_total,omitempty"`
}
type Domain struct {

View File

@@ -3,11 +3,26 @@ package roundrobin
import (
"net/http"
"net/url"
"time"
)
// CookieOptions has all the options one would like to set on the affinity cookie
type CookieOptions struct {
HTTPOnly bool
Secure bool
Path string
Domain string
Expires time.Time
MaxAge int
SameSite http.SameSite
}
// StickySession is a mixin for load balancers that implements layer 7 (http cookie) session affinity
type StickySession struct {
cookieName string
options CookieOptions
}
// NewStickySession creates a new StickySession
@@ -15,6 +30,12 @@ func NewStickySession(cookieName string) *StickySession {
return &StickySession{cookieName: cookieName}
}
// NewStickySessionWithOptions creates a new StickySession whilst allowing for options to
// shape its affinity cookie such as "httpOnly" or "secure"
func NewStickySessionWithOptions(cookieName string, options CookieOptions) *StickySession {
return &StickySession{cookieName: cookieName, options: options}
}
// GetBackend returns the backend URL stored in the sticky cookie, iff the backend is still in the valid list of servers.
func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.URL, bool, error) {
cookie, err := req.Cookie(s.cookieName)
@@ -39,7 +60,24 @@ func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.
// StickBackend creates and sets the cookie
func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) {
cookie := &http.Cookie{Name: s.cookieName, Value: backend.String(), Path: "/"}
opt := s.options
cp := "/"
if opt.Path != "" {
cp = opt.Path
}
cookie := &http.Cookie{
Name: s.cookieName,
Value: backend.String(),
Path: cp,
Domain: opt.Domain,
Expires: opt.Expires,
MaxAge: opt.MaxAge,
Secure: opt.Secure,
HttpOnly: opt.HTTPOnly,
SameSite: opt.SameSite,
}
http.SetCookie(*w, cookie)
}