Merge branch 'v1.7' into master
This commit is contained in:
commit
dad0e75121
38 changed files with 1171 additions and 235 deletions
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -1,5 +1,34 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.7.0-rc3](https://github.com/containous/traefik/tree/v1.7.0-rc3) (2018-08-01)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc2...v1.7.0-rc3)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
- **[consul,etcd,tls]** Improve TLS integration tests ([#3679](https://github.com/containous/traefik/pull/3679) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[k8s]** Add possibility to set a protocol ([#3648](https://github.com/containous/traefik/pull/3648) by [SantoDE](https://github.com/SantoDE))
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme]** Fix acme account deletion without provider change ([#3664](https://github.com/containous/traefik/pull/3664) by [zyclonite](https://github.com/zyclonite))
|
||||||
|
- **[acme]** Update lego ([#3659](https://github.com/containous/traefik/pull/3659) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[acme]** Fix ACME certificate for wildcard and root domains ([#3675](https://github.com/containous/traefik/pull/3675) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[api]** Remove TLS in API ([#3665](https://github.com/containous/traefik/pull/3665) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[docker]** Uses both binded HostIP and HostPort when useBindPortIP=true ([#3638](https://github.com/containous/traefik/pull/3638) by [geraldcroes](https://github.com/geraldcroes))
|
||||||
|
- **[k8s]** Fix Rewrite-target regex ([#3699](https://github.com/containous/traefik/pull/3699) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[middleware]** Correct Entrypoint Redirect with Stripped or Added Path ([#3631](https://github.com/containous/traefik/pull/3631) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[tracing]** Added default configuration for DataDog APM Tracer ([#3655](https://github.com/containous/traefik/pull/3655) by [aantono](https://github.com/aantono))
|
||||||
|
- **[tracing]** Added support for Trace name truncation for traces ([#3689](https://github.com/containous/traefik/pull/3689) by [aantono](https://github.com/aantono))
|
||||||
|
- **[websocket]** Handle shutdown of Hijacked connections ([#3636](https://github.com/containous/traefik/pull/3636) by [Juliens](https://github.com/Juliens))
|
||||||
|
- H2C: Remove buggy line in init to make verbose switch working ([#3701](https://github.com/containous/traefik/pull/3701) by [dduportal](https://github.com/dduportal))
|
||||||
|
- Updating oxy dependency ([#3700](https://github.com/containous/traefik/pull/3700) by [crholm](https://github.com/crholm))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme]** Update Namecheap status ([#3604](https://github.com/containous/traefik/pull/3604) by [stoinov](https://github.com/stoinov))
|
||||||
|
- **[acme]** Fix some DNS provider link ([#3639](https://github.com/containous/traefik/pull/3639) by [ldez](https://github.com/ldez))
|
||||||
|
- **[docker]** Fix style in examples/quickstart ([#3705](https://github.com/containous/traefik/pull/3705) by [korigod](https://github.com/korigod))
|
||||||
|
- **[k8s]** Add traefik prefix to k8s annotations ([#3682](https://github.com/containous/traefik/pull/3682) by [zifeo](https://github.com/zifeo))
|
||||||
|
- **[middleware,tracing]** Fix missing tracing backend in documentation ([#3706](https://github.com/containous/traefik/pull/3706) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Replace unrendered emoji ([#3690](https://github.com/containous/traefik/pull/3690) by [korigod](https://github.com/korigod))
|
||||||
|
|
||||||
## [v1.7.0-rc2](https://github.com/containous/traefik/tree/v1.7.0-rc2) (2018-07-17)
|
## [v1.7.0-rc2](https://github.com/containous/traefik/tree/v1.7.0-rc2) (2018-07-17)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc1...v1.7.0-rc2)
|
[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc1...v1.7.0-rc2)
|
||||||
|
|
||||||
|
|
2
Gopkg.lock
generated
2
Gopkg.lock
generated
|
@ -1279,7 +1279,7 @@
|
||||||
"roundrobin",
|
"roundrobin",
|
||||||
"utils"
|
"utils"
|
||||||
]
|
]
|
||||||
revision = "a3ed5f65204f4ffccbb56d58cec466cdb7ab730b"
|
revision = "fb889e801a26e7e18ef36322ac72a07157f8cc1f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/vulcand/predicate"
|
name = "github.com/vulcand/predicate"
|
||||||
|
|
18
acme/acme.go
18
acme/acme.go
|
@ -9,6 +9,7 @@ import (
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -183,7 +184,8 @@ func (a *ACME) leadershipListener(elected bool) error {
|
||||||
account := object.(*Account)
|
account := object.(*Account)
|
||||||
account.Init()
|
account.Init()
|
||||||
// Reset Account values if caServer changed, thus registration URI can be updated
|
// Reset Account values if caServer changed, thus registration URI can be updated
|
||||||
if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) {
|
if account != nil && account.Registration != nil && !isAccountMatchingCaServer(account.Registration.URI, a.CAServer) {
|
||||||
|
log.Info("Account URI does not match the current CAServer. The account will be reset")
|
||||||
account.reset()
|
account.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +232,20 @@ func (a *ACME) leadershipListener(elected bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAccountMatchingCaServer(accountURI string, serverURI string) bool {
|
||||||
|
aru, err := url.Parse(accountURI)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Unable to parse account.Registration URL : %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cau, err := url.Parse(serverURI)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Unable to parse CAServer URL : %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return cau.Hostname() == aru.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||||
account := a.store.Get().(*Account)
|
account := a.store.Get().(*Account)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/containous/traefik/configuration"
|
"github.com/containous/traefik/configuration"
|
||||||
"github.com/containous/traefik/middlewares/accesslog"
|
"github.com/containous/traefik/middlewares/accesslog"
|
||||||
"github.com/containous/traefik/middlewares/tracing"
|
"github.com/containous/traefik/middlewares/tracing"
|
||||||
|
"github.com/containous/traefik/middlewares/tracing/datadog"
|
||||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||||
"github.com/containous/traefik/ping"
|
"github.com/containous/traefik/ping"
|
||||||
|
@ -192,6 +193,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultTracing := tracing.Tracing{
|
defaultTracing := tracing.Tracing{
|
||||||
Backend: "jaeger",
|
Backend: "jaeger",
|
||||||
ServiceName: "traefik",
|
ServiceName: "traefik",
|
||||||
|
SpanNameLimit: 0,
|
||||||
Jaeger: &jaeger.Config{
|
Jaeger: &jaeger.Config{
|
||||||
SamplingServerURL: "http://localhost:5778/sampling",
|
SamplingServerURL: "http://localhost:5778/sampling",
|
||||||
SamplingType: "const",
|
SamplingType: "const",
|
||||||
|
@ -206,6 +208,11 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
ID128Bit: true,
|
ID128Bit: true,
|
||||||
Debug: false,
|
Debug: false,
|
||||||
},
|
},
|
||||||
|
DataDog: &datadog.Config{
|
||||||
|
LocalAgentHostPort: "localhost:8126",
|
||||||
|
GlobalTag: "",
|
||||||
|
Debug: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// default LifeCycle
|
// default LifeCycle
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/containous/traefik/api"
|
"github.com/containous/traefik/api"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/middlewares/tracing"
|
"github.com/containous/traefik/middlewares/tracing"
|
||||||
|
"github.com/containous/traefik/middlewares/tracing/datadog"
|
||||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||||
"github.com/containous/traefik/ping"
|
"github.com/containous/traefik/ping"
|
||||||
|
@ -197,6 +198,10 @@ func (gc *GlobalConfiguration) initTracing() {
|
||||||
log.Warn("Zipkin configuration will be ignored")
|
log.Warn("Zipkin configuration will be ignored")
|
||||||
gc.Tracing.Zipkin = nil
|
gc.Tracing.Zipkin = nil
|
||||||
}
|
}
|
||||||
|
if gc.Tracing.DataDog != nil {
|
||||||
|
log.Warn("DataDog configuration will be ignored")
|
||||||
|
gc.Tracing.DataDog = nil
|
||||||
|
}
|
||||||
case zipkin.Name:
|
case zipkin.Name:
|
||||||
if gc.Tracing.Zipkin == nil {
|
if gc.Tracing.Zipkin == nil {
|
||||||
gc.Tracing.Zipkin = &zipkin.Config{
|
gc.Tracing.Zipkin = &zipkin.Config{
|
||||||
|
@ -210,6 +215,26 @@ func (gc *GlobalConfiguration) initTracing() {
|
||||||
log.Warn("Jaeger configuration will be ignored")
|
log.Warn("Jaeger configuration will be ignored")
|
||||||
gc.Tracing.Jaeger = nil
|
gc.Tracing.Jaeger = nil
|
||||||
}
|
}
|
||||||
|
if gc.Tracing.DataDog != nil {
|
||||||
|
log.Warn("DataDog configuration will be ignored")
|
||||||
|
gc.Tracing.DataDog = nil
|
||||||
|
}
|
||||||
|
case datadog.Name:
|
||||||
|
if gc.Tracing.DataDog == nil {
|
||||||
|
gc.Tracing.DataDog = &datadog.Config{
|
||||||
|
LocalAgentHostPort: "localhost:8126",
|
||||||
|
GlobalTag: "",
|
||||||
|
Debug: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gc.Tracing.Zipkin != nil {
|
||||||
|
log.Warn("Zipkin configuration will be ignored")
|
||||||
|
gc.Tracing.Zipkin = nil
|
||||||
|
}
|
||||||
|
if gc.Tracing.Jaeger != nil {
|
||||||
|
log.Warn("Jaeger configuration will be ignored")
|
||||||
|
gc.Tracing.Jaeger = nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Warnf("Unknown tracer %q", gc.Tracing.Backend)
|
log.Warnf("Unknown tracer %q", gc.Tracing.Backend)
|
||||||
return
|
return
|
||||||
|
|
|
@ -159,7 +159,8 @@ The following general annotations are applicable on the Ingress object:
|
||||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (6). |
|
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (6). |
|
||||||
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||||
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
||||||
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5) |
|
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5)
|
||||||
|
| `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods.
|
||||||
|
|
||||||
|
|
||||||
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
||||||
|
@ -253,7 +254,7 @@ The following annotations are applicable on the Service object associated with a
|
||||||
| `traefik.ingress.kubernetes.io/affinity: "true"` | Enable backend sticky sessions. |
|
| `traefik.ingress.kubernetes.io/affinity: "true"` | Enable backend sticky sessions. |
|
||||||
| `traefik.ingress.kubernetes.io/circuit-breaker-expression: <expression>` | Set the circuit breaker expression for the backend. |
|
| `traefik.ingress.kubernetes.io/circuit-breaker-expression: <expression>` | Set the circuit breaker expression for the backend. |
|
||||||
| `traefik.ingress.kubernetes.io/load-balancer-method: drr` | Override the default `wrr` load balancer algorithm. |
|
| `traefik.ingress.kubernetes.io/load-balancer-method: drr` | Override the default `wrr` load balancer algorithm. |
|
||||||
| `traefik.ingress.kubernetes.io/max-conn-amount: 10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
| `traefik.ingress.kubernetes.io/max-conn-amount: "10"` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||||
| `traefik.ingress.kubernetes.io/max-conn-extractor-func: client.ip` | Set 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.ingress.kubernetes.io/max-conn-extractor-func: client.ip` | Set 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.ingress.kubernetes.io/session-cookie-name: <NAME>` | Manually set the cookie name for sticky sessions. |
|
| `traefik.ingress.kubernetes.io/session-cookie-name: <NAME>` | Manually set the cookie name for sticky sessions. |
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ Tracing system allows developers to visualize call flows in there infrastructure
|
||||||
|
|
||||||
We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing.
|
We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing.
|
||||||
|
|
||||||
Træfik supports two backends: Jaeger and Zipkin.
|
Træfik supports three tracing backends: Jaeger, Zipkin and DataDog.
|
||||||
|
|
||||||
## Jaeger
|
## Jaeger
|
||||||
|
|
||||||
|
@ -23,6 +23,13 @@ Træfik supports two backends: Jaeger and Zipkin.
|
||||||
#
|
#
|
||||||
serviceName = "traefik"
|
serviceName = "traefik"
|
||||||
|
|
||||||
|
# Span name limit allows for name truncation in case of very long Frontend/Backend names
|
||||||
|
# This can prevent certain tracing providers to drop traces that exceed their length limits
|
||||||
|
#
|
||||||
|
# Default: 0 - no truncation will occur
|
||||||
|
#
|
||||||
|
spanNameLimit = 0
|
||||||
|
|
||||||
[tracing.jaeger]
|
[tracing.jaeger]
|
||||||
# Sampling Server URL is the address of jaeger-agent's HTTP sampling server
|
# Sampling Server URL is the address of jaeger-agent's HTTP sampling server
|
||||||
#
|
#
|
||||||
|
@ -85,6 +92,13 @@ Træfik supports two backends: Jaeger and Zipkin.
|
||||||
#
|
#
|
||||||
serviceName = "traefik"
|
serviceName = "traefik"
|
||||||
|
|
||||||
|
# Span name limit allows for name truncation in case of very long Frontend/Backend names
|
||||||
|
# This can prevent certain tracing providers to drop traces that exceed their length limits
|
||||||
|
#
|
||||||
|
# Default: 0 - no truncation will occur
|
||||||
|
#
|
||||||
|
spanNameLimit = 150
|
||||||
|
|
||||||
[tracing.zipkin]
|
[tracing.zipkin]
|
||||||
# Zipking HTTP endpoint used to send data
|
# Zipking HTTP endpoint used to send data
|
||||||
#
|
#
|
||||||
|
@ -128,6 +142,13 @@ Træfik supports two backends: Jaeger and Zipkin.
|
||||||
#
|
#
|
||||||
serviceName = "traefik"
|
serviceName = "traefik"
|
||||||
|
|
||||||
|
# Span name limit allows for name truncation in case of very long Frontend/Backend names
|
||||||
|
# This can prevent certain tracing providers to drop traces that exceed their length limits
|
||||||
|
#
|
||||||
|
# Default: 0 - no truncation will occur
|
||||||
|
#
|
||||||
|
spanNameLimit = 100
|
||||||
|
|
||||||
[tracing.datadog]
|
[tracing.datadog]
|
||||||
# Local Agent Host Port instructs reporter to send spans to datadog-tracing-agent at this address
|
# Local Agent Host Port instructs reporter to send spans to datadog-tracing-agent at this address
|
||||||
#
|
#
|
||||||
|
|
|
@ -44,7 +44,7 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t
|
||||||
- Keeps access logs (JSON, CLF)
|
- Keeps access logs (JSON, CLF)
|
||||||
- Fast
|
- Fast
|
||||||
- Exposes a Rest API
|
- Exposes a Rest API
|
||||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
- Packaged as a single binary file (made with ❤️ with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||||
|
|
||||||
|
|
||||||
## Supported Providers
|
## Supported Providers
|
||||||
|
|
|
@ -453,8 +453,8 @@ kubectl create secret generic mysecret --from-file auth --namespace=monitoring
|
||||||
|
|
||||||
C. Attach the following annotations to the Ingress object:
|
C. Attach the following annotations to the Ingress object:
|
||||||
|
|
||||||
- `ingress.kubernetes.io/auth-type: "basic"`
|
- `traefik.ingress.kubernetes.io/auth-type: "basic"`
|
||||||
- `ingress.kubernetes.io/auth-secret: "mysecret"`
|
- `traefik.ingress.kubernetes.io/auth-secret: "mysecret"`
|
||||||
|
|
||||||
They specify basic authentication and reference the Secret `mysecret` containing the credentials.
|
They specify basic authentication and reference the Secret `mysecret` containing the credentials.
|
||||||
|
|
||||||
|
@ -468,8 +468,8 @@ metadata:
|
||||||
namespace: monitoring
|
namespace: monitoring
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: traefik
|
kubernetes.io/ingress.class: traefik
|
||||||
ingress.kubernetes.io/auth-type: "basic"
|
traefik.ingress.kubernetes.io/auth-type: "basic"
|
||||||
ingress.kubernetes.io/auth-secret: "mysecret"
|
traefik.ingress.kubernetes.io/auth-secret: "mysecret"
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- host: dashboard.prometheus.example.com
|
- host: dashboard.prometheus.example.com
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
#The reverse proxy service (Træfik)
|
# The reverse proxy service (Træfik)
|
||||||
reverse-proxy:
|
reverse-proxy:
|
||||||
image: traefik #The official Traefik docker image
|
image: traefik # The official Traefik docker image
|
||||||
command: --api --docker #Enables the web UI and tells Træfik to listen to docker
|
command: --api --docker # Enables the web UI and tells Træfik to listen to docker
|
||||||
ports:
|
ports:
|
||||||
- "80:80" #The HTTP port
|
- "80:80" # The HTTP port
|
||||||
- "8080:8080" #The Web UI (enabled by --api)
|
- "8080:8080" # The Web UI (enabled by --api)
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock #So that Traefik can listen to the Docker events
|
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
|
||||||
|
|
||||||
#A container that exposes a simple API
|
# A container that exposes a simple API
|
||||||
whoami:
|
whoami:
|
||||||
image: emilevauge/whoami #A container that exposes an API to show it's IP address
|
image: emilevauge/whoami # A container that exposes an API to show it's IP address
|
||||||
labels:
|
labels:
|
||||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
|
@ -38,7 +38,6 @@ func init() {
|
||||||
if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") {
|
if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") {
|
||||||
http2VerboseLogs = true
|
http2VerboseLogs = true
|
||||||
}
|
}
|
||||||
http2VerboseLogs = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server implements net.Handler and enables h2c. Users who want h2c just need
|
// Server implements net.Handler and enables h2c. Users who want h2c just need
|
||||||
|
|
|
@ -79,7 +79,7 @@ func setupPebbleRootCA() (*http.Transport, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AcmeSuite) SetUpSuite(c *check.C) {
|
func (s *AcmeSuite) SetUpSuite(c *check.C) {
|
||||||
s.createComposeProject(c, "peddle")
|
s.createComposeProject(c, "pebble")
|
||||||
s.composeProject.Start(c)
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
s.fakeDNSServer = startFakeDNSServer()
|
s.fakeDNSServer = startFakeDNSServer()
|
||||||
|
@ -91,7 +91,7 @@ func (s *AcmeSuite) SetUpSuite(c *check.C) {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for peddle
|
// wait for pebble
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, s.getAcmeURL(), nil)
|
req := testhelpers.MustNewRequest(http.MethodGet, s.getAcmeURL(), nil)
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
|
|
|
@ -585,21 +585,14 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
})
|
})
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// wait for traefik
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7hG"))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client := &http.Client{Transport: tr1}
|
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
var resp *http.Response
|
|
||||||
resp, err = client.Do(req)
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.com")
|
|
||||||
|
|
||||||
// now we configure the second keypair in consul and the request for host "snitest.org" will use the second keypair
|
// now we configure the second keypair in consul and the request for host "snitest.org" will use the second keypair
|
||||||
for key, value := range tlsconfigure2 {
|
for key, value := range tlsconfigure2 {
|
||||||
|
@ -614,18 +607,12 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
})
|
})
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// waiting for traefik to pull configuration
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r"))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client = &http.Client{Transport: tr2}
|
|
||||||
req.Host = tr2.TLSClientConfig.ServerName
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.org")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -532,21 +532,14 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
// wait for Træfik
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client := &http.Client{Transport: tr1}
|
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
var resp *http.Response
|
|
||||||
resp, err = client.Do(req)
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.com")
|
|
||||||
|
|
||||||
// now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair
|
// now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair
|
||||||
|
|
||||||
|
@ -562,20 +555,14 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
})
|
})
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// waiting for Træfik to pull configuration
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r"))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client = &http.Client{Transport: tr2}
|
|
||||||
req.Host = tr2.TLSClientConfig.ServerName
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.org")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||||
|
@ -646,21 +633,14 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
// wait for Træfik
|
|
||||||
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client := &http.Client{Transport: tr1}
|
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
var resp *http.Response
|
|
||||||
resp, err = client.Do(req)
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.com")
|
|
||||||
|
|
||||||
// now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair
|
// now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair
|
||||||
for key := range tlsconfigure1 {
|
for key := range tlsconfigure1 {
|
||||||
|
@ -668,18 +648,12 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// waiting for Træfik to pull configuration
|
|
||||||
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 30*time.Second, try.BodyNotContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client = &http.Client{Transport: tr1}
|
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("TRAEFIK DEFAULT CERT"))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "TRAEFIK DEFAULT CERT")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -548,21 +548,14 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
// wait for Træfik
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client := &http.Client{Transport: tr1}
|
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
var resp *http.Response
|
|
||||||
resp, err = client.Do(req)
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.com")
|
|
||||||
|
|
||||||
// now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair
|
// now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair
|
||||||
|
|
||||||
|
@ -578,18 +571,12 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
})
|
})
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// waiting for Træfik to pull configuration
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r"))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
client = &http.Client{Transport: tr2}
|
|
||||||
req.Host = tr2.TLSClientConfig.ServerName
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
c.Assert(cn, checker.Equals, "snitest.org")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
[backends]
|
[backends]
|
||||||
[backends.backend2]
|
[backends.backend2]
|
||||||
[backends.backend2.servers.server1]
|
[backends.backend2.servers.server1]
|
||||||
url = "http://172.17.0.2:80"
|
url = "http://172.17.0.123:80"
|
||||||
weight = 1
|
weight = 1
|
||||||
|
|
||||||
[frontends]
|
[frontends]
|
||||||
|
|
36
integration/fixtures/https/https_redirect.toml
Normal file
36
integration/fixtures/https/https_redirect.toml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":8888"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":8443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
[api]
|
||||||
|
|
||||||
|
[file]
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "http://127.0.0.1:80"
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "Host: example.com; PathPrefixStrip: /api"
|
||||||
|
[frontends.frontend2]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "Host: test.com; AddPrefix: /foo"
|
||||||
|
[frontends.frontend3]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend3.routes.test_1]
|
||||||
|
rule = "Host: foo.com; PathPrefixStripRegex: /{id:[a-z]+}"
|
|
@ -3,7 +3,6 @@ package integration
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -66,7 +65,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
// wait for Traefik
|
// wait for Traefik
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.org"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:snitest.org"))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
backend1 := startTestServer("9010", http.StatusNoContent)
|
backend1 := startTestServer("9010", http.StatusNoContent)
|
||||||
|
@ -92,27 +91,23 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Transport: tr1}
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = "snitest.com"
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", "snitest.com")
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err := client.Do(req)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
// Expected a 204 (from backend1)
|
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent)
|
|
||||||
|
|
||||||
client = &http.Client{Transport: tr2}
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = "snitest.org"
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", "snitest.org")
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
// Expected a 205 (from backend2)
|
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of
|
// TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of
|
||||||
|
@ -561,28 +556,25 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) {
|
||||||
err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent))
|
err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
client := &http.Client{Transport: tr1}
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err := client.Do(req)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
// snitest.org certificate must be used yet
|
|
||||||
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, check.Equals, tr1.TLSClientConfig.ServerName)
|
|
||||||
// Expected a 204 (from backend1)
|
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent)
|
|
||||||
|
|
||||||
client = &http.Client{Transport: tr2}
|
// snitest.org certificate must be used yet && Expected a 204 (from backend1)
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = tr2.TLSClientConfig.ServerName
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
resp, err = client.Do(req)
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
|
// snitest.com certificate does not exist, default certificate has to be used && Expected a 205 (from backend2)
|
||||||
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNoContent))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
// snitest.com certificate does not exist, default certificate has to be used
|
|
||||||
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Not(check.Equals), tr2.TLSClientConfig.ServerName)
|
|
||||||
// Expected a 205 (from backend2)
|
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWithSNIDynamicConfigRouteWithChange involves a client sending HTTPS requests with
|
// TestWithSNIDynamicConfigRouteWithChange involves a client sending HTTPS requests with
|
||||||
|
@ -633,57 +625,26 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
|
||||||
err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent))
|
err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Change certificates configuration file content
|
||||||
|
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https")
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
client := &http.Client{Transport: tr1}
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = tr1.TLSClientConfig.ServerName
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
// Change certificates configuration file content
|
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNotFound))
|
||||||
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https")
|
c.Assert(err, checker.IsNil)
|
||||||
var resp *http.Response
|
|
||||||
err = try.Do(30*time.Second, func() error {
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
|
||||||
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
|
||||||
req.Close = true
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
if cn != tr1.TLSClientConfig.ServerName {
|
|
||||||
return fmt.Errorf("domain %s found in place of %s", cn, tr1.TLSClientConfig.ServerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
|
||||||
client = &http.Client{Transport: tr2}
|
|
||||||
req.Host = tr2.TLSClientConfig.ServerName
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
err = try.Do(60*time.Second, func() error {
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNotFound))
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
|
||||||
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
|
||||||
req.Close = true
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
if cn == tr2.TLSClientConfig.ServerName {
|
|
||||||
return fmt.Errorf("domain %s found in place of default one", tr2.TLSClientConfig.ServerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion involves a client sending HTTPS requests with
|
// TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion involves a client sending HTTPS requests with
|
||||||
|
@ -725,53 +686,19 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
client := &http.Client{Transport: tr2}
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = tr2.TLSClientConfig.ServerName
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
var resp *http.Response
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent))
|
||||||
err = try.Do(30*time.Second, func() error {
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
|
||||||
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
|
||||||
req.Close = true
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
if cn != tr2.TLSClientConfig.ServerName {
|
|
||||||
return fmt.Errorf("domain %s found in place of %s", cn, tr2.TLSClientConfig.ServerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent)
|
|
||||||
// Change certificates configuration file content
|
// Change certificates configuration file content
|
||||||
modifyCertificateConfFileContent(c, "", dynamicConfFileName, "https02")
|
modifyCertificateConfFileContent(c, "", dynamicConfFileName, "https02")
|
||||||
|
|
||||||
err = try.Do(60*time.Second, func() error {
|
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNotFound))
|
||||||
resp, err = client.Do(req)
|
|
||||||
|
|
||||||
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
|
||||||
req.Close = true
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
|
||||||
if cn == tr2.TLSClientConfig.ServerName {
|
|
||||||
return fmt.Errorf("domain %s found instead of the default one", tr2.TLSClientConfig.ServerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
|
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
|
||||||
|
@ -804,3 +731,134 @@ func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName, en
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathModification(c *check.C) {
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_redirect.toml"))
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// wait for Traefik
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host: example.com"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
host string
|
||||||
|
sourceURL string
|
||||||
|
expectedURL string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Stripped URL redirect",
|
||||||
|
host: "example.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api",
|
||||||
|
expectedURL: "https://example.com:8443/api",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL with trailing slash redirect",
|
||||||
|
host: "example.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/",
|
||||||
|
expectedURL: "https://example.com:8443/api/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL with double trailing slash redirect",
|
||||||
|
host: "example.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api//",
|
||||||
|
expectedURL: "https://example.com:8443/api//",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL with path redirect",
|
||||||
|
host: "example.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/bacon",
|
||||||
|
expectedURL: "https://example.com:8443/api/bacon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL with path and trailing slash redirect",
|
||||||
|
host: "example.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/bacon/",
|
||||||
|
expectedURL: "https://example.com:8443/api/bacon/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL with path and double trailing slash redirect",
|
||||||
|
host: "example.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/bacon//",
|
||||||
|
expectedURL: "https://example.com:8443/api/bacon//",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Root Path with redirect",
|
||||||
|
host: "test.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/",
|
||||||
|
expectedURL: "https://test.com:8443/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Root Path with double trailing slash redirect",
|
||||||
|
host: "test.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888//",
|
||||||
|
expectedURL: "https://test.com:8443//",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "AddPrefix with redirect",
|
||||||
|
host: "test.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/wtf",
|
||||||
|
expectedURL: "https://test.com:8443/wtf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "AddPrefix with trailing slash redirect",
|
||||||
|
host: "test.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/wtf/",
|
||||||
|
expectedURL: "https://test.com:8443/wtf/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "AddPrefix with matching path segment redirect",
|
||||||
|
host: "test.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/wtf/foo",
|
||||||
|
expectedURL: "https://test.com:8443/wtf/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL Regex redirect",
|
||||||
|
host: "foo.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api",
|
||||||
|
expectedURL: "https://foo.com:8443/api",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL Regex with trailing slash redirect",
|
||||||
|
host: "foo.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/",
|
||||||
|
expectedURL: "https://foo.com:8443/api/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL Regex with path redirect",
|
||||||
|
host: "foo.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/bacon",
|
||||||
|
expectedURL: "https://foo.com:8443/api/bacon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Stripped URL Regex with path and trailing slash redirect",
|
||||||
|
host: "foo.com",
|
||||||
|
sourceURL: "http://127.0.0.1:8888/api/bacon/",
|
||||||
|
expectedURL: "https://foo.com:8443/api/bacon/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", test.sourceURL, nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = test.host
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
location := resp.Header.Get("Location")
|
||||||
|
c.Assert(location, checker.Equals, test.expectedURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pebble:
|
pebble:
|
||||||
image: ldez/pebble
|
image: letsencrypt/pebble:2018-07-27
|
||||||
command: --dnsserver ${DOCKER_HOST_IP}:5053
|
command: pebble --dnsserver ${DOCKER_HOST_IP}:5053
|
||||||
ports:
|
ports:
|
||||||
- 14000:14000
|
- 14000:14000
|
||||||
environment:
|
environment:
|
|
@ -88,6 +88,31 @@ func HasBody() ResponseCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCn returns a retry condition function.
|
||||||
|
// The condition returns an error if the cn is not correct.
|
||||||
|
func HasCn(cn string) ResponseCondition {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
if res.TLS == nil {
|
||||||
|
return errors.New("response doesn't have TLS")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.TLS.PeerCertificates) == 0 {
|
||||||
|
return errors.New("response TLS doesn't have peer certificates")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.TLS.PeerCertificates[0] == nil {
|
||||||
|
return errors.New("first peer certificate is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
commonName := res.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
|
if cn != commonName {
|
||||||
|
return fmt.Errorf("common name don't match: %s != %s", cn, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StatusCodeIs returns a retry condition function.
|
// StatusCodeIs returns a retry condition function.
|
||||||
// The condition returns an error if the given response's status code is not the
|
// The condition returns an error if the given response's status code is not the
|
||||||
// given HTTP status code.
|
// given HTTP status code.
|
||||||
|
|
|
@ -31,7 +31,7 @@ func Sleep(d time.Duration) {
|
||||||
// response body needs to be closed or not. Callers are expected to close on
|
// response body needs to be closed or not. Callers are expected to close on
|
||||||
// their own if the function returns a nil error.
|
// their own if the function returns a nil error.
|
||||||
func Response(req *http.Request, timeout time.Duration) (*http.Response, error) {
|
func Response(req *http.Request, timeout time.Duration) (*http.Response, error) {
|
||||||
return doTryRequest(req, timeout)
|
return doTryRequest(req, timeout, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResponseUntilStatusCode is like Request, but returns the response for further
|
// ResponseUntilStatusCode is like Request, but returns the response for further
|
||||||
|
@ -40,7 +40,7 @@ func Response(req *http.Request, timeout time.Duration) (*http.Response, error)
|
||||||
// response body needs to be closed or not. Callers are expected to close on
|
// response body needs to be closed or not. Callers are expected to close on
|
||||||
// their own if the function returns a nil error.
|
// their own if the function returns a nil error.
|
||||||
func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCode int) (*http.Response, error) {
|
func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCode int) (*http.Response, error) {
|
||||||
return doTryRequest(req, timeout, StatusCodeIs(statusCode))
|
return doTryRequest(req, timeout, nil, StatusCodeIs(statusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRequest is like Do, but runs a request against the given URL and applies
|
// GetRequest is like Do, but runs a request against the given URL and applies
|
||||||
|
@ -48,7 +48,7 @@ func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCod
|
||||||
// ResponseCondition may be nil, in which case only the request against the URL must
|
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||||
// succeed.
|
// succeed.
|
||||||
func GetRequest(url string, timeout time.Duration, conditions ...ResponseCondition) error {
|
func GetRequest(url string, timeout time.Duration, conditions ...ResponseCondition) error {
|
||||||
resp, err := doTryGet(url, timeout, conditions...)
|
resp, err := doTryGet(url, timeout, nil, conditions...)
|
||||||
|
|
||||||
if resp != nil && resp.Body != nil {
|
if resp != nil && resp.Body != nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
@ -62,7 +62,21 @@ func GetRequest(url string, timeout time.Duration, conditions ...ResponseConditi
|
||||||
// ResponseCondition may be nil, in which case only the request against the URL must
|
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||||
// succeed.
|
// succeed.
|
||||||
func Request(req *http.Request, timeout time.Duration, conditions ...ResponseCondition) error {
|
func Request(req *http.Request, timeout time.Duration, conditions ...ResponseCondition) error {
|
||||||
resp, err := doTryRequest(req, timeout, conditions...)
|
resp, err := doTryRequest(req, timeout, nil, conditions...)
|
||||||
|
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestWithTransport is like Do, but runs a request against the given URL and applies
|
||||||
|
// the condition on the response.
|
||||||
|
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||||
|
// succeed.
|
||||||
|
func RequestWithTransport(req *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) error {
|
||||||
|
resp, err := doTryRequest(req, timeout, transport, conditions...)
|
||||||
|
|
||||||
if resp != nil && resp.Body != nil {
|
if resp != nil && resp.Body != nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
@ -112,24 +126,27 @@ func Do(timeout time.Duration, operation DoCondition) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTryGet(url string, timeout time.Duration, conditions ...ResponseCondition) (*http.Response, error) {
|
func doTryGet(url string, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) {
|
||||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return doTryRequest(req, timeout, conditions...)
|
return doTryRequest(req, timeout, transport, conditions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTryRequest(request *http.Request, timeout time.Duration, conditions ...ResponseCondition) (*http.Response, error) {
|
func doTryRequest(request *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) {
|
||||||
return doRequest(Do, timeout, request, conditions...)
|
return doRequest(Do, timeout, request, transport, conditions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRequest(action timedAction, timeout time.Duration, request *http.Request, conditions ...ResponseCondition) (*http.Response, error) {
|
func doRequest(action timedAction, timeout time.Duration, request *http.Request, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
return resp, action(timeout, func() error {
|
return resp, action(timeout, func() error {
|
||||||
var err error
|
var err error
|
||||||
client := http.DefaultClient
|
client := http.DefaultClient
|
||||||
|
if transport != nil {
|
||||||
|
client.Transport = transport
|
||||||
|
}
|
||||||
|
|
||||||
resp, err = client.Do(request)
|
resp, err = client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,12 +11,21 @@ type AddPrefix struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type key string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AddPrefixKey is the key within the request context used to
|
||||||
|
// store the added prefix
|
||||||
|
AddPrefixKey key = "AddPrefix"
|
||||||
|
)
|
||||||
|
|
||||||
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
r.URL.Path = s.Prefix + r.URL.Path
|
r.URL.Path = s.Prefix + r.URL.Path
|
||||||
if r.URL.RawPath != "" {
|
if r.URL.RawPath != "" {
|
||||||
r.URL.RawPath = s.Prefix + r.URL.RawPath
|
r.URL.RawPath = s.Prefix + r.URL.RawPath
|
||||||
}
|
}
|
||||||
r.RequestURI = r.URL.RequestURI()
|
r.RequestURI = r.URL.RequestURI()
|
||||||
|
r = r.WithContext(context.WithValue(r.Context(), AddPrefixKey, s.Prefix))
|
||||||
s.Handler.ServeHTTP(w, r)
|
s.Handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/containous/traefik/configuration"
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
@ -85,6 +86,30 @@ func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if stripPrefix, stripPrefixOk := req.Context().Value(middlewares.StripPrefixKey).(string); stripPrefixOk {
|
||||||
|
if len(stripPrefix) > 0 {
|
||||||
|
tempPath := parsedURL.Path
|
||||||
|
parsedURL.Path = stripPrefix
|
||||||
|
if len(tempPath) > 0 && tempPath != "/" {
|
||||||
|
parsedURL.Path = stripPrefix + tempPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if trailingSlash, trailingSlashOk := req.Context().Value(middlewares.StripPrefixSlashKey).(bool); trailingSlashOk {
|
||||||
|
if trailingSlash {
|
||||||
|
if !strings.HasSuffix(parsedURL.Path, "/") {
|
||||||
|
parsedURL.Path = fmt.Sprintf("%s/", parsedURL.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addPrefix, addPrefixOk := req.Context().Value(middlewares.AddPrefixKey).(string); addPrefixOk {
|
||||||
|
if len(addPrefix) > 0 {
|
||||||
|
parsedURL.Path = strings.Replace(parsedURL.Path, addPrefix, "", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if newURL != oldURL {
|
if newURL != oldURL {
|
||||||
handler := &moveHandler{location: parsedURL, permanent: h.permanent}
|
handler := &moveHandler{location: parsedURL, permanent: h.permanent}
|
||||||
handler.ServeHTTP(rw, req)
|
handler.ServeHTTP(rw, req)
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ForwardedPrefixHeader is the default header to set prefix
|
const (
|
||||||
const ForwardedPrefixHeader = "X-Forwarded-Prefix"
|
// StripPrefixKey is the key within the request context used to
|
||||||
|
// store the stripped prefix
|
||||||
|
StripPrefixKey key = "StripPrefix"
|
||||||
|
// StripPrefixSlashKey is the key within the request context used to
|
||||||
|
// store the stripped slash
|
||||||
|
StripPrefixSlashKey key = "StripPrefixSlash"
|
||||||
|
// ForwardedPrefixHeader is the default header to set prefix
|
||||||
|
ForwardedPrefixHeader = "X-Forwarded-Prefix"
|
||||||
|
)
|
||||||
|
|
||||||
// StripPrefix is a middleware used to strip prefix from an URL request
|
// StripPrefix is a middleware used to strip prefix from an URL request
|
||||||
type StripPrefix struct {
|
type StripPrefix struct {
|
||||||
|
@ -17,18 +26,21 @@ type StripPrefix struct {
|
||||||
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, prefix := range s.Prefixes {
|
for _, prefix := range s.Prefixes {
|
||||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||||
|
trailingSlash := r.URL.Path == prefix+"/"
|
||||||
r.URL.Path = stripPrefix(r.URL.Path, prefix)
|
r.URL.Path = stripPrefix(r.URL.Path, prefix)
|
||||||
if r.URL.RawPath != "" {
|
if r.URL.RawPath != "" {
|
||||||
r.URL.RawPath = stripPrefix(r.URL.RawPath, prefix)
|
r.URL.RawPath = stripPrefix(r.URL.RawPath, prefix)
|
||||||
}
|
}
|
||||||
s.serveRequest(w, r, strings.TrimSpace(prefix))
|
s.serveRequest(w, r, strings.TrimSpace(prefix), trailingSlash)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefix string) {
|
func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefix string, trailingSlash bool) {
|
||||||
|
r = r.WithContext(context.WithValue(r.Context(), StripPrefixSlashKey, trailingSlash))
|
||||||
|
r = r.WithContext(context.WithValue(r.Context(), StripPrefixKey, prefix))
|
||||||
r.Header.Add(ForwardedPrefixHeader, prefix)
|
r.Header.Add(ForwardedPrefixHeader, prefix)
|
||||||
r.RequestURI = r.URL.RequestURI()
|
r.RequestURI = r.URL.RequestURI()
|
||||||
s.Handler.ServeHTTP(w, r)
|
s.Handler.ServeHTTP(w, r)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containous/mux"
|
"github.com/containous/mux"
|
||||||
|
@ -39,10 +40,13 @@ func (s *StripPrefixRegex) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trailingSlash := r.URL.Path == prefix.Path+"/"
|
||||||
r.URL.Path = r.URL.Path[len(prefix.Path):]
|
r.URL.Path = r.URL.Path[len(prefix.Path):]
|
||||||
if r.URL.RawPath != "" {
|
if r.URL.RawPath != "" {
|
||||||
r.URL.RawPath = r.URL.RawPath[len(prefix.Path):]
|
r.URL.RawPath = r.URL.RawPath[len(prefix.Path):]
|
||||||
}
|
}
|
||||||
|
r = r.WithContext(context.WithValue(r.Context(), StripPrefixSlashKey, trailingSlash))
|
||||||
|
r = r.WithContext(context.WithValue(r.Context(), StripPrefixKey, prefix.Path))
|
||||||
r.Header.Add(ForwardedPrefixHeader, prefix.Path)
|
r.Header.Add(ForwardedPrefixHeader, prefix.Path)
|
||||||
r.RequestURI = r.URL.RequestURI()
|
r.RequestURI = r.URL.RequestURI()
|
||||||
s.Handler.ServeHTTP(w, r)
|
s.Handler.ServeHTTP(w, r)
|
||||||
|
|
|
@ -22,12 +22,10 @@ func (t *Tracing) NewEntryPoint(name string) negroni.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
opNameFunc := func(r *http.Request) string {
|
opNameFunc := generateEntryPointSpanName
|
||||||
return fmt.Sprintf("Entrypoint %s %s", e.entryPoint, r.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, _ := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
|
ctx, _ := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
|
||||||
span := e.StartSpan(opNameFunc(r), ext.RPCServerOption(ctx))
|
span := e.StartSpan(opNameFunc(r, e.entryPoint, e.SpanNameLimit), ext.RPCServerOption(ctx))
|
||||||
ext.Component.Set(span, e.ServiceName)
|
ext.Component.Set(span, e.ServiceName)
|
||||||
LogRequest(span, r)
|
LogRequest(span, r)
|
||||||
ext.SpanKindRPCServer.Set(span)
|
ext.SpanKindRPCServer.Set(span)
|
||||||
|
@ -40,3 +38,20 @@ func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
|
||||||
LogResponseCode(span, recorder.Status())
|
LogResponseCode(span, recorder.Status())
|
||||||
span.Finish()
|
span.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateEntryPointSpanName will return a Span name of an appropriate lenth based on the 'spanLimit' argument. If needed, it will be truncated, but will not be less than 24 characters.
|
||||||
|
func generateEntryPointSpanName(r *http.Request, entryPoint string, spanLimit int) string {
|
||||||
|
name := fmt.Sprintf("Entrypoint %s %s", entryPoint, r.Host)
|
||||||
|
|
||||||
|
if spanLimit > 0 && len(name) > spanLimit {
|
||||||
|
if spanLimit < EntryPointMaxLengthNumber {
|
||||||
|
log.Warnf("SpanNameLimit is set to be less than required static number of characters, defaulting to %d + 3", EntryPointMaxLengthNumber)
|
||||||
|
spanLimit = EntryPointMaxLengthNumber + 3
|
||||||
|
}
|
||||||
|
hash := computeHash(name)
|
||||||
|
limit := (spanLimit - EntryPointMaxLengthNumber) / 2
|
||||||
|
name = fmt.Sprintf("Entrypoint %s %s %s", truncateString(entryPoint, limit), truncateString(r.Host, limit), hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
70
middlewares/tracing/entrypoint_test.go
Normal file
70
middlewares/tracing/entrypoint_test.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEntryPointMiddlewareServeHTTP(t *testing.T) {
|
||||||
|
expectedTags := map[string]interface{}{
|
||||||
|
"span.kind": ext.SpanKindRPCServerEnum,
|
||||||
|
"http.method": "GET",
|
||||||
|
"component": "",
|
||||||
|
"http.url": "http://www.test.com",
|
||||||
|
"http.host": "www.test.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
entryPoint string
|
||||||
|
tracing *Tracing
|
||||||
|
expectedTags map[string]interface{}
|
||||||
|
expectedName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no truncation test",
|
||||||
|
entryPoint: "test",
|
||||||
|
tracing: &Tracing{
|
||||||
|
SpanNameLimit: 0,
|
||||||
|
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||||
|
},
|
||||||
|
expectedTags: expectedTags,
|
||||||
|
expectedName: "Entrypoint test www.test.com",
|
||||||
|
}, {
|
||||||
|
desc: "basic test",
|
||||||
|
entryPoint: "test",
|
||||||
|
tracing: &Tracing{
|
||||||
|
SpanNameLimit: 25,
|
||||||
|
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||||
|
},
|
||||||
|
expectedTags: expectedTags,
|
||||||
|
expectedName: "Entrypoint te... ww... 39b97e58",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
e := &entryPointMiddleware{
|
||||||
|
entryPoint: test.entryPoint,
|
||||||
|
Tracing: test.tracing,
|
||||||
|
}
|
||||||
|
|
||||||
|
next := func(http.ResponseWriter, *http.Request) {
|
||||||
|
span := test.tracing.tracer.(*MockTracer).Span
|
||||||
|
|
||||||
|
actual := span.Tags
|
||||||
|
assert.Equal(t, test.expectedTags, actual)
|
||||||
|
assert.Equal(t, test.expectedName, span.OpName)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "http://www.test.com", nil), next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ func (t *Tracing) NewForwarderMiddleware(frontend, backend string) negroni.Handl
|
||||||
Tracing: t,
|
Tracing: t,
|
||||||
frontend: frontend,
|
frontend: frontend,
|
||||||
backend: backend,
|
backend: backend,
|
||||||
opName: fmt.Sprintf("forward %s/%s", frontend, backend),
|
opName: generateForwardSpanName(frontend, backend, t.SpanNameLimit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,3 +44,20 @@ func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
|
||||||
|
|
||||||
LogResponseCode(span, recorder.Status())
|
LogResponseCode(span, recorder.Status())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateForwardSpanName will return a Span name of an appropriate lenth based on the 'spanLimit' argument. If needed, it will be truncated, but will not be less than 21 characters
|
||||||
|
func generateForwardSpanName(frontend, backend string, spanLimit int) string {
|
||||||
|
name := fmt.Sprintf("forward %s/%s", frontend, backend)
|
||||||
|
|
||||||
|
if spanLimit > 0 && len(name) > spanLimit {
|
||||||
|
if spanLimit < ForwardMaxLengthNumber {
|
||||||
|
log.Warnf("SpanNameLimit is set to be less than required static number of characters, defaulting to %d + 3", ForwardMaxLengthNumber)
|
||||||
|
spanLimit = ForwardMaxLengthNumber + 3
|
||||||
|
}
|
||||||
|
hash := computeHash(name)
|
||||||
|
limit := (spanLimit - ForwardMaxLengthNumber) / 2
|
||||||
|
name = fmt.Sprintf("forward %s/%s/%s", truncateString(frontend, limit), truncateString(backend, limit), hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
93
middlewares/tracing/forwarder_test.go
Normal file
93
middlewares/tracing/forwarder_test.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTracingNewForwarderMiddleware(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
tracer *Tracing
|
||||||
|
frontend string
|
||||||
|
backend string
|
||||||
|
expected *forwarderMiddleware
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Simple Forward Tracer without truncation and hashing",
|
||||||
|
tracer: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service.domain.tld",
|
||||||
|
backend: "some-service.domain.tld",
|
||||||
|
expected: &forwarderMiddleware{
|
||||||
|
Tracing: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service.domain.tld",
|
||||||
|
backend: "some-service.domain.tld",
|
||||||
|
opName: "forward some-service.domain.tld/some-service.domain.tld",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
desc: "Simple Forward Tracer with truncation and hashing",
|
||||||
|
tracer: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service-100.slug.namespace.environment.domain.tld",
|
||||||
|
backend: "some-service-100.slug.namespace.environment.domain.tld",
|
||||||
|
expected: &forwarderMiddleware{
|
||||||
|
Tracing: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service-100.slug.namespace.environment.domain.tld",
|
||||||
|
backend: "some-service-100.slug.namespace.environment.domain.tld",
|
||||||
|
opName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Exactly 101 chars",
|
||||||
|
tracer: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service1.namespace.environment.domain.tld",
|
||||||
|
backend: "some-service1.namespace.environment.domain.tld",
|
||||||
|
expected: &forwarderMiddleware{
|
||||||
|
Tracing: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service1.namespace.environment.domain.tld",
|
||||||
|
backend: "some-service1.namespace.environment.domain.tld",
|
||||||
|
opName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "More than 101 chars",
|
||||||
|
tracer: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service1.frontend.namespace.environment.domain.tld",
|
||||||
|
backend: "some-service1.backend.namespace.environment.domain.tld",
|
||||||
|
expected: &forwarderMiddleware{
|
||||||
|
Tracing: &Tracing{
|
||||||
|
SpanNameLimit: 101,
|
||||||
|
},
|
||||||
|
frontend: "some-service1.frontend.namespace.environment.domain.tld",
|
||||||
|
backend: "some-service1.backend.namespace.environment.domain.tld",
|
||||||
|
opName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual := test.tracer.NewForwarderMiddleware(test.frontend, test.backend)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
assert.True(t, len(test.expected.opName) <= test.tracer.SpanNameLimit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package tracing
|
package tracing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,10 +14,20 @@ import (
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ForwardMaxLengthNumber defines the number of static characters in the Forwarding Span Trace name : 8 chars for 'forward ' + 8 chars for hash + 2 chars for '_'.
|
||||||
|
const ForwardMaxLengthNumber = 18
|
||||||
|
|
||||||
|
// EntryPointMaxLengthNumber defines the number of static characters in the Entrypoint Span Trace name : 11 chars for 'Entrypoint ' + 8 chars for hash + 2 chars for '_'.
|
||||||
|
const EntryPointMaxLengthNumber = 21
|
||||||
|
|
||||||
|
// TraceNameHashLength defines the number of characters to use from the head of the generated hash.
|
||||||
|
const TraceNameHashLength = 8
|
||||||
|
|
||||||
// Tracing middleware
|
// Tracing middleware
|
||||||
type Tracing struct {
|
type Tracing struct {
|
||||||
Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"`
|
Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"`
|
||||||
ServiceName string `description:"Set the name for this service" export:"true"`
|
ServiceName string `description:"Set the name for this service" export:"true"`
|
||||||
|
SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)" export:"true"`
|
||||||
Jaeger *jaeger.Config `description:"Settings for jaeger"`
|
Jaeger *jaeger.Config `description:"Settings for jaeger"`
|
||||||
Zipkin *zipkin.Config `description:"Settings for zipkin"`
|
Zipkin *zipkin.Config `description:"Settings for zipkin"`
|
||||||
DataDog *datadog.Config `description:"Settings for DataDog"`
|
DataDog *datadog.Config `description:"Settings for DataDog"`
|
||||||
|
@ -147,16 +158,40 @@ func SetError(r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErrorAndDebugLog flags the span associated with this request as in error and create a debug log
|
// SetErrorAndDebugLog flags the span associated with this request as in error and create a debug log.
|
||||||
func SetErrorAndDebugLog(r *http.Request, format string, args ...interface{}) {
|
func SetErrorAndDebugLog(r *http.Request, format string, args ...interface{}) {
|
||||||
SetError(r)
|
SetError(r)
|
||||||
log.Debugf(format, args...)
|
log.Debugf(format, args...)
|
||||||
LogEventf(r, format, args...)
|
LogEventf(r, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErrorAndWarnLog flags the span associated with this request as in error and create a debug log
|
// SetErrorAndWarnLog flags the span associated with this request as in error and create a debug log.
|
||||||
func SetErrorAndWarnLog(r *http.Request, format string, args ...interface{}) {
|
func SetErrorAndWarnLog(r *http.Request, format string, args ...interface{}) {
|
||||||
SetError(r)
|
SetError(r)
|
||||||
log.Warnf(format, args...)
|
log.Warnf(format, args...)
|
||||||
LogEventf(r, format, args...)
|
LogEventf(r, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// truncateString reduces the length of the 'str' argument to 'num' - 3 and adds a '...' suffix to the tail.
|
||||||
|
func truncateString(str string, num int) string {
|
||||||
|
text := str
|
||||||
|
if len(str) > num {
|
||||||
|
if num > 3 {
|
||||||
|
num -= 3
|
||||||
|
}
|
||||||
|
text = str[0:num] + "..."
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeHash returns the first TraceNameHashLength character of the sha256 hash for 'name' argument.
|
||||||
|
func computeHash(name string) string {
|
||||||
|
data := []byte(name)
|
||||||
|
hash := sha256.New()
|
||||||
|
if _, err := hash.Write(data); err != nil {
|
||||||
|
// Impossible case
|
||||||
|
log.Errorf("Fail to create Span name hash for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", hash.Sum(nil))[:TraceNameHashLength]
|
||||||
|
}
|
||||||
|
|
133
middlewares/tracing/tracing_test.go
Normal file
133
middlewares/tracing/tracing_test.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
|
"github.com/opentracing/opentracing-go/log"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockTracer struct {
|
||||||
|
Span *MockSpan
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockSpan struct {
|
||||||
|
OpName string
|
||||||
|
Tags map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockSpanContext struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockSpanContext:
|
||||||
|
func (n MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
|
||||||
|
|
||||||
|
// MockSpan:
|
||||||
|
func (n MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} }
|
||||||
|
func (n MockSpan) SetBaggageItem(key, val string) opentracing.Span {
|
||||||
|
return MockSpan{Tags: make(map[string]interface{})}
|
||||||
|
}
|
||||||
|
func (n MockSpan) BaggageItem(key string) string { return "" }
|
||||||
|
func (n MockSpan) SetTag(key string, value interface{}) opentracing.Span {
|
||||||
|
n.Tags[key] = value
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
func (n MockSpan) LogFields(fields ...log.Field) {}
|
||||||
|
func (n MockSpan) LogKV(keyVals ...interface{}) {}
|
||||||
|
func (n MockSpan) Finish() {}
|
||||||
|
func (n MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {}
|
||||||
|
func (n MockSpan) SetOperationName(operationName string) opentracing.Span { return n }
|
||||||
|
func (n MockSpan) Tracer() opentracing.Tracer { return MockTracer{} }
|
||||||
|
func (n MockSpan) LogEvent(event string) {}
|
||||||
|
func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {}
|
||||||
|
func (n MockSpan) Log(data opentracing.LogData) {}
|
||||||
|
func (n MockSpan) Reset() {
|
||||||
|
n.Tags = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartSpan belongs to the Tracer interface.
|
||||||
|
func (n MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
|
||||||
|
n.Span.OpName = operationName
|
||||||
|
return n.Span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject belongs to the Tracer interface.
|
||||||
|
func (n MockTracer) Inject(sp opentracing.SpanContext, format interface{}, carrier interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract belongs to the Tracer interface.
|
||||||
|
func (n MockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
|
||||||
|
return nil, opentracing.ErrSpanContextNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncateString(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
text string
|
||||||
|
limit int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "short text less than limit 10",
|
||||||
|
text: "short",
|
||||||
|
limit: 10,
|
||||||
|
expected: "short",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "basic truncate with limit 10",
|
||||||
|
text: "some very long pice of text",
|
||||||
|
limit: 10,
|
||||||
|
expected: "some ve...",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "truncate long FQDN to 39 chars",
|
||||||
|
text: "some-service-100.slug.namespace.environment.domain.tld",
|
||||||
|
limit: 39,
|
||||||
|
expected: "some-service-100.slug.namespace.envi...",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual := truncateString(test.text, test.limit)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
assert.True(t, len(actual) <= test.limit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeHash(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
text string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "hashing",
|
||||||
|
text: "some very long pice of text",
|
||||||
|
expected: "0258ea1c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "short text less than limit 10",
|
||||||
|
text: "short",
|
||||||
|
expected: "f9b0078b",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual := computeHash(test.text)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/flaeg/parse"
|
"github.com/containous/flaeg/parse"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/rules"
|
"github.com/containous/traefik/rules"
|
||||||
|
@ -73,6 +75,8 @@ type Certificate struct {
|
||||||
type DNSChallenge struct {
|
type DNSChallenge struct {
|
||||||
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
|
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
|
||||||
DelayBeforeCheck parse.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
DelayBeforeCheck parse.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
||||||
|
preCheckTimeout time.Duration
|
||||||
|
preCheckInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPChallenge contains HTTP challenge Configuration
|
// HTTPChallenge contains HTTP challenge Configuration
|
||||||
|
@ -130,7 +134,8 @@ func (p *Provider) Init(_ types.Constraints) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset Account if caServer changed, thus registration URI can be updated
|
// Reset Account if caServer changed, thus registration URI can be updated
|
||||||
if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) {
|
if p.account != nil && p.account.Registration != nil && !isAccountMatchingCaServer(p.account.Registration.URI, p.CAServer) {
|
||||||
|
log.Info("Account URI does not match the current CAServer. The account will be reset")
|
||||||
p.account = nil
|
p.account = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +147,20 @@ func (p *Provider) Init(_ types.Constraints) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAccountMatchingCaServer(accountURI string, serverURI string) bool {
|
||||||
|
aru, err := url.Parse(accountURI)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Unable to parse account.Registration URL : %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cau, err := url.Parse(serverURI)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Unable to parse CAServer URL : %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return cau.Hostname() == aru.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
// Provide allows the file provider to provide configurations to traefik
|
// Provide allows the file provider to provide configurations to traefik
|
||||||
// using the given Configuration channel.
|
// using the given Configuration channel.
|
||||||
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||||
|
@ -246,6 +265,16 @@ func (p *Provider) getClient() (*acme.Client, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same default values than LEGO
|
||||||
|
p.DNSChallenge.preCheckTimeout = 60 * time.Second
|
||||||
|
p.DNSChallenge.preCheckInterval = 2 * time.Second
|
||||||
|
|
||||||
|
// Set the precheck timeout into the DNSChallenge provider
|
||||||
|
if challengeProviderTimeout, ok := provider.(acme.ChallengeProviderTimeout); ok {
|
||||||
|
p.DNSChallenge.preCheckTimeout, p.DNSChallenge.preCheckInterval = challengeProviderTimeout.Timeout()
|
||||||
|
}
|
||||||
|
|
||||||
} else if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
|
} else if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
|
||||||
log.Debug("Using HTTP Challenge provider.")
|
log.Debug("Using HTTP Challenge provider.")
|
||||||
|
|
||||||
|
@ -345,13 +374,20 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||||
return nil, fmt.Errorf("cannot get ACME client %v", err)
|
return nil, fmt.Errorf("cannot get ACME client %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var certificate *acme.CertificateResource
|
||||||
bundle := true
|
bundle := true
|
||||||
|
if p.useCertificateWithRetry(uncheckedDomains) {
|
||||||
certificate, err := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple)
|
certificate, err = obtainCertificateWithRetry(domains, client, p.DNSChallenge.preCheckTimeout, p.DNSChallenge.preCheckInterval, bundle)
|
||||||
if err != nil {
|
} else {
|
||||||
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
|
certificate, err = client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to generate a certificate for the domains %v: %v", uncheckedDomains, err)
|
||||||
|
}
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, fmt.Errorf("domains %v do not generate a certificate", uncheckedDomains)
|
||||||
|
}
|
||||||
if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 {
|
if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 {
|
||||||
return nil, fmt.Errorf("domains %v generate certificate with no value: %v", uncheckedDomains, certificate)
|
return nil, fmt.Errorf("domains %v generate certificate with no value: %v", uncheckedDomains, certificate)
|
||||||
}
|
}
|
||||||
|
@ -368,6 +404,60 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||||
return certificate, nil
|
return certificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) useCertificateWithRetry(domains []string) bool {
|
||||||
|
// Check if we can use the retry mechanism only if we use the DNS Challenge and if is there are at least 2 domains to check
|
||||||
|
if p.DNSChallenge != nil && len(domains) > 1 {
|
||||||
|
rootDomain := ""
|
||||||
|
for _, searchWildcardDomain := range domains {
|
||||||
|
// Search a wildcard domain if not already found
|
||||||
|
if len(rootDomain) == 0 && strings.HasPrefix(searchWildcardDomain, "*.") {
|
||||||
|
rootDomain = strings.TrimPrefix(searchWildcardDomain, "*.")
|
||||||
|
if len(rootDomain) > 0 {
|
||||||
|
// Look for a root domain which matches the wildcard domain
|
||||||
|
for _, searchRootDomain := range domains {
|
||||||
|
if rootDomain == searchRootDomain {
|
||||||
|
// If the domains list contains a wildcard domain and its root domain, we can use the retry mechanism to obtain the certificate
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There is only one wildcard domain in the slice, if its root domain has not been found, the retry mechanism does not have to be used
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func obtainCertificateWithRetry(domains []string, client *acme.Client, timeout, interval time.Duration, bundle bool) (*acme.CertificateResource, error) {
|
||||||
|
var certificate *acme.CertificateResource
|
||||||
|
var err error
|
||||||
|
|
||||||
|
operation := func() error {
|
||||||
|
certificate, err = client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Error obtaining certificate retrying in %s", time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a retry backOff to let LEGO tries twice to obtain a certificate for both wildcard and root domain
|
||||||
|
ebo := backoff.NewExponentialBackOff()
|
||||||
|
ebo.MaxElapsedTime = 2 * timeout
|
||||||
|
ebo.MaxInterval = interval
|
||||||
|
rbo := backoff.WithMaxRetries(ebo, 2)
|
||||||
|
|
||||||
|
err = backoff.RetryNotify(safe.OperationWithRecover(operation), rbo, notify)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error obtaining certificate: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
func dnsOverrideDelay(delay parse.Duration) error {
|
func dnsOverrideDelay(delay parse.Duration) error {
|
||||||
if delay == 0 {
|
if delay == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -429,3 +429,136 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsAccountMatchingCaServer(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
accountURI string
|
||||||
|
serverURI string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "acme staging with matching account",
|
||||||
|
accountURI: "https://acme-staging-v02.api.letsencrypt.org/acme/acct/1234567",
|
||||||
|
serverURI: "https://acme-staging-v02.api.letsencrypt.org/acme/directory",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "acme production with matching account",
|
||||||
|
accountURI: "https://acme-v02.api.letsencrypt.org/acme/acct/1234567",
|
||||||
|
serverURI: "https://acme-v02.api.letsencrypt.org/acme/directory",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "http only acme with matching account",
|
||||||
|
accountURI: "http://acme.api.letsencrypt.org/acme/acct/1234567",
|
||||||
|
serverURI: "http://acme.api.letsencrypt.org/acme/directory",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "different subdomains for account and server",
|
||||||
|
accountURI: "https://test1.example.org/acme/acct/1234567",
|
||||||
|
serverURI: "https://test2.example.org/acme/directory",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "different domains for account and server",
|
||||||
|
accountURI: "https://test.example1.org/acme/acct/1234567",
|
||||||
|
serverURI: "https://test.example2.org/acme/directory",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "different tld for account and server",
|
||||||
|
accountURI: "https://test.example.com/acme/acct/1234567",
|
||||||
|
serverURI: "https://test.example.org/acme/directory",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "malformed account url",
|
||||||
|
accountURI: "//|\\/test.example.com/acme/acct/1234567",
|
||||||
|
serverURI: "https://test.example.com/acme/directory",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "malformed server url",
|
||||||
|
accountURI: "https://test.example.com/acme/acct/1234567",
|
||||||
|
serverURI: "//|\\/test.example.com/acme/directory",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "malformed server and account url",
|
||||||
|
accountURI: "//|\\/test.example.com/acme/acct/1234567",
|
||||||
|
serverURI: "//|\\/test.example.com/acme/directory",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
result := isAccountMatchingCaServer(test.accountURI, test.serverURI)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseBackOffToObtainCertificate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
domains []string
|
||||||
|
dnsChallenge *DNSChallenge
|
||||||
|
expectedResponse bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "only one single domain",
|
||||||
|
domains: []string{"acme.wtf"},
|
||||||
|
dnsChallenge: &DNSChallenge{},
|
||||||
|
expectedResponse: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "only one wildcard domain",
|
||||||
|
domains: []string{"*.acme.wtf"},
|
||||||
|
dnsChallenge: &DNSChallenge{},
|
||||||
|
expectedResponse: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wildcard domain with no root domain",
|
||||||
|
domains: []string{"*.acme.wtf", "foo.acme.wtf", "bar.acme.wtf", "foo.bar"},
|
||||||
|
dnsChallenge: &DNSChallenge{},
|
||||||
|
expectedResponse: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wildcard and root domain",
|
||||||
|
domains: []string{"*.acme.wtf", "foo.acme.wtf", "bar.acme.wtf", "acme.wtf"},
|
||||||
|
dnsChallenge: &DNSChallenge{},
|
||||||
|
expectedResponse: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wildcard and root domain but no DNS challenge",
|
||||||
|
domains: []string{"*.acme.wtf", "acme.wtf"},
|
||||||
|
dnsChallenge: nil,
|
||||||
|
expectedResponse: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "two wildcard domains (must never happen)",
|
||||||
|
domains: []string{"*.acme.wtf", "*.bar.foo"},
|
||||||
|
dnsChallenge: nil,
|
||||||
|
expectedResponse: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}}
|
||||||
|
|
||||||
|
actualResponse := acmeProvider.useCertificateWithRetry(test.domains)
|
||||||
|
assert.Equal(t, test.expectedResponse, actualResponse, "unexpected response to use backOff")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ const (
|
||||||
annotationKubernetesPublicKey = "ingress.kubernetes.io/public-key"
|
annotationKubernetesPublicKey = "ingress.kubernetes.io/public-key"
|
||||||
annotationKubernetesReferrerPolicy = "ingress.kubernetes.io/referrer-policy"
|
annotationKubernetesReferrerPolicy = "ingress.kubernetes.io/referrer-policy"
|
||||||
annotationKubernetesIsDevelopment = "ingress.kubernetes.io/is-development"
|
annotationKubernetesIsDevelopment = "ingress.kubernetes.io/is-development"
|
||||||
|
annotationKubernetesProtocol = "ingress.kubernetes.io/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO [breaking] remove label support
|
// TODO [breaking] remove label support
|
||||||
|
|
|
@ -43,6 +43,8 @@ const (
|
||||||
traefikDefaultIngressClass = "traefik"
|
traefikDefaultIngressClass = "traefik"
|
||||||
defaultBackendName = "global-default-backend"
|
defaultBackendName = "global-default-backend"
|
||||||
defaultFrontendName = "global-default-frontend"
|
defaultFrontendName = "global-default-frontend"
|
||||||
|
allowedProtocolHTTPS = "https"
|
||||||
|
allowedProtocolH2C = "h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IngressEndpoint holds the endpoint information for the Kubernetes provider
|
// IngressEndpoint holds the endpoint information for the Kubernetes provider
|
||||||
|
@ -312,6 +314,16 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protocol = getStringValue(i.Annotations, annotationKubernetesProtocol, protocol)
|
||||||
|
switch protocol {
|
||||||
|
case allowedProtocolHTTPS:
|
||||||
|
case allowedProtocolH2C:
|
||||||
|
case label.DefaultProtocol:
|
||||||
|
default:
|
||||||
|
log.Errorf("Invalid protocol %s/%s specified for Ingress %s - skipping", annotationKubernetesProtocol, i.Namespace, i.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if service.Spec.Type == "ExternalName" {
|
if service.Spec.Type == "ExternalName" {
|
||||||
url := protocol + "://" + service.Spec.ExternalName
|
url := protocol + "://" + service.Spec.ExternalName
|
||||||
if port.Port != 443 && port.Port != 80 {
|
if port.Port != 443 && port.Port != 80 {
|
||||||
|
@ -535,7 +547,7 @@ func getRuleForPath(pa extensionsv1beta1.HTTPIngressPath, i *extensionsv1beta1.I
|
||||||
if ruleType == ruleTypeReplacePath {
|
if ruleType == ruleTypeReplacePath {
|
||||||
return "", fmt.Errorf("rewrite-target must not be used together with annotation %q", annotationKubernetesRuleType)
|
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, "/"))
|
rewriteTargetRule := fmt.Sprintf("ReplacePathRegex: ^%s(.*) %s$1", pa.Path, strings.TrimRight(rewriteTarget, "/"))
|
||||||
rules = append(rules, rewriteTargetRule)
|
rules = append(rules, rewriteTargetRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1222,6 +1222,41 @@ rateset:
|
||||||
iPaths(onePath(iPath("/customheaders"), iBackend("service1", intstr.FromInt(80))))),
|
iPaths(onePath(iPath("/customheaders"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iAnnotation(annotationKubernetesProtocol, "h2c"),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("protocol"),
|
||||||
|
iPaths(onePath(iPath("/valid"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iAnnotation(annotationKubernetesProtocol, "foobar"),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("protocol"),
|
||||||
|
iPaths(onePath(iPath("/notvalid"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iAnnotation(annotationKubernetesProtocol, "http"),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("protocol"),
|
||||||
|
iPaths(onePath(iPath("/missmatch"), iBackend("serviceHTTPS", intstr.FromInt(443))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("protocol"),
|
||||||
|
iPaths(onePath(iPath("/noAnnotation"), iBackend("serviceHTTPS", intstr.FromInt(443))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
services := []*corev1.Service{
|
services := []*corev1.Service{
|
||||||
|
@ -1243,6 +1278,16 @@ rateset:
|
||||||
clusterIP("10.0.0.2"),
|
clusterIP("10.0.0.2"),
|
||||||
sPorts(sPort(802, ""))),
|
sPorts(sPort(802, ""))),
|
||||||
),
|
),
|
||||||
|
buildService(
|
||||||
|
sName("serviceHTTPS"),
|
||||||
|
sNamespace("testing"),
|
||||||
|
sUID("2"),
|
||||||
|
sSpec(
|
||||||
|
clusterIP("10.0.0.3"),
|
||||||
|
sType("ExternalName"),
|
||||||
|
sExternalName("example.com"),
|
||||||
|
sPorts(sPort(443, "https"))),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets := []*corev1.Secret{
|
secrets := []*corev1.Secret{
|
||||||
|
@ -1350,6 +1395,28 @@ rateset:
|
||||||
servers(),
|
servers(),
|
||||||
lbMethod("wrr"),
|
lbMethod("wrr"),
|
||||||
),
|
),
|
||||||
|
backend("protocol/valid",
|
||||||
|
servers(
|
||||||
|
server("h2c://example.com", weight(1)),
|
||||||
|
server("h2c://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
backend("protocol/notvalid",
|
||||||
|
servers(),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
backend("protocol/missmatch",
|
||||||
|
servers(
|
||||||
|
server("http://example.com", weight(1)),
|
||||||
|
server("http://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
backend("protocol/noAnnotation",
|
||||||
|
servers(
|
||||||
|
server("https://example.com", weight(1)),
|
||||||
|
server("https://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
@ -1408,7 +1475,7 @@ rateset:
|
||||||
frontend("rewrite/api",
|
frontend("rewrite/api",
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/api", "PathPrefix:/api;ReplacePathRegex: ^/api/(.*) /$1"),
|
route("/api", "PathPrefix:/api;ReplacePathRegex: ^/api(.*) $1"),
|
||||||
route("rewrite", "Host:rewrite")),
|
route("rewrite", "Host:rewrite")),
|
||||||
),
|
),
|
||||||
frontend("error-pages/errorpages",
|
frontend("error-pages/errorpages",
|
||||||
|
@ -1481,6 +1548,34 @@ rateset:
|
||||||
route("root", "Host:root"),
|
route("root", "Host:root"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
frontend("protocol/valid",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/valid", "PathPrefix:/valid"),
|
||||||
|
route("protocol", "Host:protocol"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
frontend("protocol/notvalid",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/notvalid", "PathPrefix:/notvalid"),
|
||||||
|
route("protocol", "Host:protocol"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
frontend("protocol/missmatch",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/missmatch", "PathPrefix:/missmatch"),
|
||||||
|
route("protocol", "Host:protocol"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
frontend("protocol/noAnnotation",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/noAnnotation", "PathPrefix:/noAnnotation"),
|
||||||
|
route("protocol", "Host:protocol"),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -253,7 +253,7 @@ type Configurations map[string]*Configuration
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
Backends map[string]*Backend `json:"backends,omitempty"`
|
Backends map[string]*Backend `json:"backends,omitempty"`
|
||||||
Frontends map[string]*Frontend `json:"frontends,omitempty"`
|
Frontends map[string]*Frontend `json:"frontends,omitempty"`
|
||||||
TLS []*traefiktls.Configuration `json:"tls,omitempty"`
|
TLS []*traefiktls.Configuration `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigMessage hold configuration information exchanged between parts of traefik.
|
// ConfigMessage hold configuration information exchanged between parts of traefik.
|
||||||
|
|
9
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
9
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
|
@ -395,6 +395,15 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
|
||||||
errClient := make(chan error, 1)
|
errClient := make(chan error, 1)
|
||||||
errBackend := make(chan error, 1)
|
errBackend := make(chan error, 1)
|
||||||
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
|
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
|
||||||
|
|
||||||
|
src.SetPingHandler(func(data string) error {
|
||||||
|
return dst.WriteMessage(websocket.PingMessage, []byte(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
src.SetPongHandler(func(data string) error {
|
||||||
|
return dst.WriteMessage(websocket.PongMessage, []byte(data))
|
||||||
|
})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
msgType, msg, err := src.ReadMessage()
|
msgType, msg, err := src.ReadMessage()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue