Merge current v2.6 into master

This commit is contained in:
Tom Moulard 2022-02-15 09:09:16 +01:00
commit a79868fadc
No known key found for this signature in database
GPG key ID: 521ABE0C1A0DEAF6
22 changed files with 566 additions and 221 deletions

View file

@ -1,3 +1,20 @@
## [v2.6.1](https://github.com/traefik/traefik/tree/v2.6.1) (2022-02-14)
[All Commits](https://github.com/traefik/traefik/compare/v2.6.0...v2.6.1)
**Bug fixes:**
- **[acme]** Add domain to HTTP challenge errors ([#8740](https://github.com/traefik/traefik/pull/8740) by [ldez](https://github.com/ldez))
- **[metrics]** Fix metrics bucket key high cardinality ([#8761](https://github.com/traefik/traefik/pull/8761) by [tomMoulard](https://github.com/tomMoulard))
- **[middleware,tls]** Use CNAME for SNI check on host header ([#8773](https://github.com/traefik/traefik/pull/8773) by [ldez](https://github.com/ldez))
- **[middleware,tracing]** Rename Datadog span tags ([#8323](https://github.com/traefik/traefik/pull/8323) by [luckielordie](https://github.com/luckielordie))
- **[tls]** Apply the same approach as the rules system on the TLS configuration choice ([#8764](https://github.com/traefik/traefik/pull/8764) by [ldez](https://github.com/ldez))
**Documentation:**
- **[acme]** Add Hurricane Electric to acme documentation ([#8746](https://github.com/traefik/traefik/pull/8746) by [vladshub](https://github.com/vladshub))
- **[acme]** Clarify that ACME challenge is mandatory ([#8739](https://github.com/traefik/traefik/pull/8739) by [mpl](https://github.com/mpl))
- **[http3]** Explain a bit more around enabling HTTP3 ([#8731](https://github.com/traefik/traefik/pull/8731) by [SantoDE](https://github.com/SantoDE))
- **[metrics]** Fix mixups in metrics documentation ([#8752](https://github.com/traefik/traefik/pull/8752) by [tomMoulard](https://github.com/tomMoulard))
- **[middleware,k8s/crd]** Fix Kubernetes TCP examples ([#8759](https://github.com/traefik/traefik/pull/8759) by [sylr](https://github.com/sylr))
## [v2.6.0](https://github.com/traefik/traefik/tree/v2.6.0) (2022-01-24) ## [v2.6.0](https://github.com/traefik/traefik/tree/v2.6.0) (2022-01-24)
[All Commits](https://github.com/traefik/traefik/compare/v2.5.0-rc1...v2.6.0) [All Commits](https://github.com/traefik/traefik/compare/v2.5.0-rc1...v2.6.0)

View file

@ -190,7 +190,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
// Entrypoints // Entrypoints
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints) serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints, staticConfiguration.HostResolver)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -14,7 +14,7 @@ labels:
```yaml tab="Kubernetes" ```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1
kind: Middleware kind: MiddlewareTCP
metadata: metadata:
name: test-inflightconn name: test-inflightconn
spec: spec:

View file

@ -36,7 +36,7 @@ spec:
--- ---
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1
kind: Middleware kind: MiddlewareTCP
metadata: metadata:
name: foo-ip-whitelist name: foo-ip-whitelist
spec: spec:
@ -47,7 +47,7 @@ spec:
--- ---
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute kind: IngressRouteTCP
metadata: metadata:
name: ingressroute name: ingressroute
spec: spec:

View file

@ -438,3 +438,17 @@ To enable HTTP/3 on an EntryPoint, please check out the [HTTP/3 configuration](.
In `v2.6`, the [Kubernetes Gateway API provider](../providers/kubernetes-gateway.md) now only supports the version [v1alpha2](https://gateway-api.sigs.k8s.io/v1alpha2/guides/getting-started/) of the specification and In `v2.6`, the [Kubernetes Gateway API provider](../providers/kubernetes-gateway.md) now only supports the version [v1alpha2](https://gateway-api.sigs.k8s.io/v1alpha2/guides/getting-started/) of the specification and
[route namespaces](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.RouteNamespaces) selectors, which requires Traefik to fetch and watch the cluster namespaces. [route namespaces](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.RouteNamespaces) selectors, which requires Traefik to fetch and watch the cluster namespaces.
Therefore, the [RBAC](../reference/dynamic-configuration/kubernetes-gateway.md#rbac) and [CRD](../reference/dynamic-configuration/kubernetes-gateway.md#definitions) definitions must be updated. Therefore, the [RBAC](../reference/dynamic-configuration/kubernetes-gateway.md#rbac) and [CRD](../reference/dynamic-configuration/kubernetes-gateway.md#definitions) definitions must be updated.
## v2.6.0 to v2.6.1
### Metrics
In `v2.6.1`, the metrics system does not support any more custom HTTP method verbs to prevent potential metrics cardinality overhead.
In consequence, for metrics having the method label,
if the HTTP method verb of a request is not one defined in the set of common methods for [`HTTP/1.1`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
or the [`PRI`](https://datatracker.ietf.org/doc/html/rfc7540#section-11.6) verb (for `HTTP/2`),
the value for the method label becomes `EXTENSION_METHOD`, instead of the request's one.
### Tracing
In `v2.6.1`, the Datadog tags added to a span changed from `service.name` to `traefik.service.name` and from `router.name` to `traefik.router.name`.

View file

@ -62,7 +62,7 @@ traefik_config_last_reload_success
The expiration date of certificates. The expiration date of certificates.
Available labels: `cn`, `sans`, `serial`. [Labels](#labels): `cn`, `sans`, `serial`.
```dd tab="Datadog" ```dd tab="Datadog"
tls.certs.notAfterTimestamp tls.certs.notAfterTimestamp
@ -94,7 +94,7 @@ traefik_tls_certs_not_after
The total count of HTTP requests received by an entrypoint. The total count of HTTP requests received by an entrypoint.
Available labels: `code`, `method`, `protocol`, `entrypoint`. [Labels](#labels): `code`, `method`, `protocol`, `entrypoint`.
```dd tab="Datadog" ```dd tab="Datadog"
entrypoint.request.total entrypoint.request.total
@ -117,7 +117,7 @@ traefik_entrypoint_requests_total
The total count of HTTPS requests received by an entrypoint. The total count of HTTPS requests received by an entrypoint.
Available labels: `tls_version`, `tls_cipher`, `entrypoint`. [Labels](#labels): `tls_version`, `tls_cipher`, `entrypoint`.
```dd tab="Datadog" ```dd tab="Datadog"
entrypoint.request.tls.total entrypoint.request.tls.total
@ -140,7 +140,7 @@ traefik_entrypoint_requests_tls_total
Request processing duration histogram on an entrypoint. Request processing duration histogram on an entrypoint.
Available labels: `code`, `method`, `protocol`, `entrypoint`. [Labels](#labels): `code`, `method`, `protocol`, `entrypoint`.
```dd tab="Datadog" ```dd tab="Datadog"
entrypoint.request.duration entrypoint.request.duration
@ -163,7 +163,7 @@ traefik_entrypoint_request_duration_seconds
The current count of open connections on an entrypoint. The current count of open connections on an entrypoint.
Available labels: `method`, `protocol`, `entrypoint`. [Labels](#labels): `method`, `protocol`, `entrypoint`.
```dd tab="Datadog" ```dd tab="Datadog"
entrypoint.connections.open entrypoint.connections.open
@ -195,7 +195,7 @@ traefik_entrypoint_open_connections
The total count of HTTP requests handled by a router. The total count of HTTP requests handled by a router.
Available labels: `code`, `method`, `protocol`, `router`, `service`. [Labels](#labels): `code`, `method`, `protocol`, `router`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
router.request.total router.request.total
@ -218,7 +218,7 @@ traefik_router_requests_total
The total count of HTTPS requests handled by a router. The total count of HTTPS requests handled by a router.
Available labels: `tls_version`, `tls_cipher`, `router`, `service`. [Labels](#labels): `tls_version`, `tls_cipher`, `router`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
router.request.tls.total router.request.tls.total
@ -241,7 +241,7 @@ traefik_router_requests_tls_total
Request processing duration histogram on a router. Request processing duration histogram on a router.
Available labels: `code`, `method`, `protocol`, `router`, `service`. [Labels](#labels): `code`, `method`, `protocol`, `router`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
router.request.duration router.request.duration
@ -264,7 +264,7 @@ traefik_router_request_duration_seconds
The current count of open connections on a router. The current count of open connections on a router.
Available labels: `method`, `protocol`, `router`, `service`. [Labels](#labels): `method`, `protocol`, `router`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
router.connections.open router.connections.open
@ -298,7 +298,7 @@ traefik_router_open_connections
The total count of HTTP requests processed on a service. The total count of HTTP requests processed on a service.
Available labels: `code`, `method`, `protocol`, `service`. [Labels](#labels): `code`, `method`, `protocol`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
service.request.total service.request.total
@ -321,7 +321,7 @@ traefik_service_requests_total
The total count of HTTPS requests processed on a service. The total count of HTTPS requests processed on a service.
Available labels: `tls_version`, `tls_cipher`, `service`. [Labels](#labels): `tls_version`, `tls_cipher`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
router.service.tls.total router.service.tls.total
@ -344,7 +344,7 @@ traefik_service_requests_tls_total
Request processing duration histogram on a service. Request processing duration histogram on a service.
Available labels: `code`, `method`, `protocol`, `service`. [Labels](#labels): `code`, `method`, `protocol`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
service.request.duration service.request.duration
@ -367,7 +367,7 @@ traefik_service_request_duration_seconds
The current count of open connections on a service. The current count of open connections on a service.
Available labels: `method`, `protocol`, `service`. [Labels](#labels): `method`, `protocol`, `service`.
```dd tab="Datadog" ```dd tab="Datadog"
service.connections.open service.connections.open
@ -390,7 +390,7 @@ traefik_service_open_connections
The count of requests retries on a service. The count of requests retries on a service.
Available labels: `service`. [Labels](#labels): `service`.
```dd tab="Datadog" ```dd tab="Datadog"
service.retries.total service.retries.total
@ -413,7 +413,7 @@ traefik_service_retries_total
Current service's server status, described by a gauge with a value of 0 for a down server or a value of 1 for an up server. Current service's server status, described by a gauge with a value of 0 for a down server or a value of 1 for an up server.
Available labels: `service`, `url`. [Labels](#labels): `service`, `url`.
```dd tab="Datadog" ```dd tab="Datadog"
service.server.up service.server.up
@ -431,3 +431,28 @@ traefik_service_server_up
# Default prefix: "traefik" # Default prefix: "traefik"
{prefix}.service.server.up {prefix}.service.server.up
``` ```
## Labels
Here is a comprehensive list of labels that are provided by the metrics:
| Label | Description | example |
|---------------|---------------------------------------|----------------------------|
| `cn` | Certificate Common Name | "example.com" |
| `code` | Request code | "200" |
| `entrypoint` | Entrypoint that handled the request | "example_entrypoint" |
| `method` | Request Method | "GET" |
| `protocol` | Request protocol | "http" |
| `router` | Router that handled the request | "example_router" |
| `sans` | Certificate Subject Alternative NameS | "example.com" |
| `serial` | Certificate Serial Number | "123..." |
| `service` | Service that handled the request | "example_service@provider" |
| `tls_cipher` | TLS cipher used for the request | "TLS_FALLBACK_SCSV" |
| `tls_version` | TLS version used for the request | "1.0" |
| `url` | Service server url | "http://example.com" |
!!! info "`method` label value"
If the HTTP method verb on a request is not one defined in the set of common methods for [`HTTP/1.1`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
or the [`PRI`](https://datatracker.ietf.org/doc/html/rfc7540#section-11.6) verb (for `HTTP/2`),
then the value for the method label becomes `EXTENSION_METHOD`.

View file

@ -9,18 +9,24 @@
# Use certificate in net/internal/testcert.go # Use certificate in net/internal/testcert.go
rootCAs = [ """ rootCAs = [ """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
fblo6RBxUQ== bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
-----END CERTIFICATE----- -----END CERTIFICATE-----
"""] """]

View file

@ -1,14 +1,20 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
fblo6RBxUQ== bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View file

@ -159,12 +159,27 @@ func containsHeader(req *http.Request, name, value string) bool {
return false return false
} }
// getMethod returns the request's method.
// It checks whether the method is a valid UTF-8 string.
// To restrict the (potentially infinite) number of accepted values for the method,
// and avoid unbounded memory issues,
// values that are not part of the set of HTTP verbs are replaced with EXTENSION_METHOD.
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
// https://datatracker.ietf.org/doc/html/rfc2616/#section-5.1.1.
func getMethod(r *http.Request) string { func getMethod(r *http.Request) string {
if !utf8.ValidString(r.Method) { if !utf8.ValidString(r.Method) {
log.Warnf("Invalid HTTP method encoding: %s", r.Method) log.WithoutContext().Warnf("Invalid HTTP method encoding: %s", r.Method)
return "NON_UTF8_HTTP_METHOD" return "NON_UTF8_HTTP_METHOD"
} }
return r.Method
switch r.Method {
case "HEAD", "GET", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", // https://datatracker.ietf.org/doc/html/rfc7231#section-4
"PATCH", // https://datatracker.ietf.org/doc/html/rfc5789#section-2
"PRI": // https://datatracker.ietf.org/doc/html/rfc7540#section-11.6
return r.Method
default:
return "EXTENSION_METHOD"
}
} }
type retryMetrics interface { type retryMetrics interface {

View file

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics"
@ -98,3 +99,33 @@ func TestCloseNotifier(t *testing.T) {
}) })
} }
} }
func Test_getMethod(t *testing.T) {
testCases := []struct {
method string
expected string
}{
{
method: http.MethodGet,
expected: http.MethodGet,
},
{
method: strings.ToLower(http.MethodGet),
expected: "EXTENSION_METHOD",
},
{
method: "THIS_IS_NOT_A_VALID_METHOD",
expected: "EXTENSION_METHOD",
},
}
for _, test := range testCases {
test := test
t.Run(test.method, func(t *testing.T) {
t.Parallel()
request := httptest.NewRequest(test.method, "http://example.com", nil)
assert.Equal(t, test.expected, getMethod(request))
})
}
}

View file

@ -0,0 +1,107 @@
package snicheck
import (
"net"
"net/http"
"strings"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
)
// SNICheck is an HTTP handler that checks whether the TLS configuration for the server name is the same as for the host header.
type SNICheck struct {
next http.Handler
tlsOptionsForHost map[string]string
}
// New creates a new SNICheck.
func New(tlsOptionsForHost map[string]string, next http.Handler) *SNICheck {
return &SNICheck{next: next, tlsOptionsForHost: tlsOptionsForHost}
}
func (s SNICheck) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if req.TLS == nil {
s.next.ServeHTTP(rw, req)
return
}
host := getHost(req)
serverName := strings.TrimSpace(req.TLS.ServerName)
// Domain Fronting
if !strings.EqualFold(host, serverName) {
tlsOptionHeader := findTLSOptionName(s.tlsOptionsForHost, host, true)
tlsOptionSNI := findTLSOptionName(s.tlsOptionsForHost, serverName, false)
if tlsOptionHeader != tlsOptionSNI {
log.WithoutContext().
WithField("host", host).
WithField("req.Host", req.Host).
WithField("req.TLS.ServerName", req.TLS.ServerName).
Debugf("TLS options difference: SNI:%s, Header:%s", tlsOptionSNI, tlsOptionHeader)
http.Error(rw, http.StatusText(http.StatusMisdirectedRequest), http.StatusMisdirectedRequest)
return
}
}
s.next.ServeHTTP(rw, req)
}
func getHost(req *http.Request) string {
h := requestdecorator.GetCNAMEFlatten(req.Context())
if h != "" {
return h
}
h = requestdecorator.GetCanonizedHost(req.Context())
if h != "" {
return h
}
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
}
return strings.TrimSpace(host)
}
func findTLSOptionName(tlsOptionsForHost map[string]string, host string, fqdn bool) string {
name := findTLSOptName(tlsOptionsForHost, host, fqdn)
if name != "" {
return name
}
name = findTLSOptName(tlsOptionsForHost, strings.ToLower(host), fqdn)
if name != "" {
return name
}
return traefiktls.DefaultTLSConfigName
}
func findTLSOptName(tlsOptionsForHost map[string]string, host string, fqdn bool) string {
if tlsOptions, ok := tlsOptionsForHost[host]; ok {
return tlsOptions
}
if !fqdn {
return ""
}
if last := len(host) - 1; last >= 0 && host[last] == '.' {
if tlsOptions, ok := tlsOptionsForHost[host[:last]]; ok {
return tlsOptions
}
return ""
}
if tlsOptions, ok := tlsOptionsForHost[host+"."]; ok {
return tlsOptions
}
return ""
}

View file

@ -0,0 +1,60 @@
package snicheck
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSNICheck_ServeHTTP(t *testing.T) {
testCases := []struct {
desc string
tlsOptionsForHost map[string]string
host string
expected int
}{
{
desc: "no TLS options",
expected: http.StatusOK,
},
{
desc: "with TLS options",
tlsOptionsForHost: map[string]string{
"example.com": "foo",
},
expected: http.StatusOK,
},
{
desc: "server name and host doesn't have the same TLS configuration",
tlsOptionsForHost: map[string]string{
"example.com": "foo",
},
host: "example.com",
expected: http.StatusMisdirectedRequest,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
sniCheck := New(test.tlsOptionsForHost, next)
req := httptest.NewRequest(http.MethodGet, "https://localhost", nil)
if test.host != "" {
req.Host = test.host
}
recorder := httptest.NewRecorder()
sniCheck.ServeHTTP(recorder, req)
assert.Equal(t, test.expected, recorder.Code)
})
}
}

View file

@ -43,8 +43,8 @@ func (f *forwarderMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Reques
span, req, finish := tr.StartSpanf(req, ext.SpanKindRPCClientEnum, "forward", opParts, "/") span, req, finish := tr.StartSpanf(req, ext.SpanKindRPCClientEnum, "forward", opParts, "/")
defer finish() defer finish()
span.SetTag("service.name", f.service) span.SetTag("traefik.service.name", f.service)
span.SetTag("router.name", f.router) span.SetTag("traefik.router.name", f.router)
ext.HTTPMethod.Set(span, req.Method) ext.HTTPMethod.Set(span, req.Method)
ext.HTTPUrl.Set(span, req.URL.String()) ext.HTTPUrl.Set(span, req.URL.String())
span.SetTag("http.host", req.Host) span.SetTag("http.host", req.Host)

View file

@ -36,12 +36,12 @@ func TestNewForwarder(t *testing.T) {
router: "some-service.domain.tld", router: "some-service.domain.tld",
expected: expected{ expected: expected{
Tags: map[string]interface{}{ Tags: map[string]interface{}{
"http.host": "www.test.com", "http.host": "www.test.com",
"http.method": "GET", "http.method": "GET",
"http.url": "http://www.test.com/toto", "http.url": "http://www.test.com/toto",
"service.name": "some-service.domain.tld", "traefik.service.name": "some-service.domain.tld",
"router.name": "some-service.domain.tld", "traefik.router.name": "some-service.domain.tld",
"span.kind": ext.SpanKindRPCClientEnum, "span.kind": ext.SpanKindRPCClientEnum,
}, },
OperationName: "forward some-service.domain.tld/some-service.domain.tld", OperationName: "forward some-service.domain.tld/some-service.domain.tld",
}, },
@ -56,12 +56,12 @@ func TestNewForwarder(t *testing.T) {
router: "some-service-100.slug.namespace.environment.domain.tld", router: "some-service-100.slug.namespace.environment.domain.tld",
expected: expected{ expected: expected{
Tags: map[string]interface{}{ Tags: map[string]interface{}{
"http.host": "www.test.com", "http.host": "www.test.com",
"http.method": "GET", "http.method": "GET",
"http.url": "http://www.test.com/toto", "http.url": "http://www.test.com/toto",
"service.name": "some-service-100.slug.namespace.environment.domain.tld", "traefik.service.name": "some-service-100.slug.namespace.environment.domain.tld",
"router.name": "some-service-100.slug.namespace.environment.domain.tld", "traefik.router.name": "some-service-100.slug.namespace.environment.domain.tld",
"span.kind": ext.SpanKindRPCClientEnum, "span.kind": ext.SpanKindRPCClientEnum,
}, },
OperationName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48", OperationName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48",
}, },
@ -76,12 +76,12 @@ func TestNewForwarder(t *testing.T) {
router: "some-service1.namespace.environment.domain.tld", router: "some-service1.namespace.environment.domain.tld",
expected: expected{ expected: expected{
Tags: map[string]interface{}{ Tags: map[string]interface{}{
"http.host": "www.test.com", "http.host": "www.test.com",
"http.method": "GET", "http.method": "GET",
"http.url": "http://www.test.com/toto", "http.url": "http://www.test.com/toto",
"service.name": "some-service1.namespace.environment.domain.tld", "traefik.service.name": "some-service1.namespace.environment.domain.tld",
"router.name": "some-service1.namespace.environment.domain.tld", "traefik.router.name": "some-service1.namespace.environment.domain.tld",
"span.kind": ext.SpanKindRPCClientEnum, "span.kind": ext.SpanKindRPCClientEnum,
}, },
OperationName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld", OperationName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld",
}, },
@ -96,12 +96,12 @@ func TestNewForwarder(t *testing.T) {
router: "some-service1.backend.namespace.environment.domain.tld", router: "some-service1.backend.namespace.environment.domain.tld",
expected: expected{ expected: expected{
Tags: map[string]interface{}{ Tags: map[string]interface{}{
"http.host": "www.test.com", "http.host": "www.test.com",
"http.method": "GET", "http.method": "GET",
"http.url": "http://www.test.com/toto", "http.url": "http://www.test.com/toto",
"service.name": "some-service1.frontend.namespace.environment.domain.tld", "traefik.service.name": "some-service1.frontend.namespace.environment.domain.tld",
"router.name": "some-service1.backend.namespace.environment.domain.tld", "traefik.router.name": "some-service1.backend.namespace.environment.domain.tld",
"span.kind": ext.SpanKindRPCClientEnum, "span.kind": ext.SpanKindRPCClientEnum,
}, },
OperationName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23", OperationName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23",
}, },

View file

@ -9,7 +9,6 @@ import (
"github.com/traefik/traefik/v2/pkg/metrics" "github.com/traefik/traefik/v2/pkg/metrics"
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog" "github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
metricsmiddleware "github.com/traefik/traefik/v2/pkg/middlewares/metrics" metricsmiddleware "github.com/traefik/traefik/v2/pkg/middlewares/metrics"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
mTracing "github.com/traefik/traefik/v2/pkg/middlewares/tracing" mTracing "github.com/traefik/traefik/v2/pkg/middlewares/tracing"
"github.com/traefik/traefik/v2/pkg/tracing" "github.com/traefik/traefik/v2/pkg/tracing"
"github.com/traefik/traefik/v2/pkg/tracing/jaeger" "github.com/traefik/traefik/v2/pkg/tracing/jaeger"
@ -20,7 +19,6 @@ type ChainBuilder struct {
metricsRegistry metrics.Registry metricsRegistry metrics.Registry
accessLoggerMiddleware *accesslog.Handler accessLoggerMiddleware *accesslog.Handler
tracer *tracing.Tracing tracer *tracing.Tracing
requestDecorator *requestdecorator.RequestDecorator
} }
// NewChainBuilder Creates a new ChainBuilder. // NewChainBuilder Creates a new ChainBuilder.
@ -29,7 +27,6 @@ func NewChainBuilder(staticConfiguration static.Configuration, metricsRegistry m
metricsRegistry: metricsRegistry, metricsRegistry: metricsRegistry,
accessLoggerMiddleware: accessLoggerMiddleware, accessLoggerMiddleware: accessLoggerMiddleware,
tracer: setupTracing(staticConfiguration.Tracing), tracer: setupTracing(staticConfiguration.Tracing),
requestDecorator: requestdecorator.New(staticConfiguration.HostResolver),
} }
} }
@ -49,7 +46,7 @@ func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.C
chain = chain.Append(metricsmiddleware.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)) chain = chain.Append(metricsmiddleware.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName))
} }
return chain.Append(requestdecorator.WrapHandler(c.requestDecorator)) return chain
} }
// Close accessLogger and tracer. // Close accessLogger and tracer.

View file

@ -5,12 +5,11 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"strings"
"github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares/snicheck"
"github.com/traefik/traefik/v2/pkg/rules" "github.com/traefik/traefik/v2/pkg/rules"
"github.com/traefik/traefik/v2/pkg/server/provider" "github.com/traefik/traefik/v2/pkg/server/provider"
tcpservice "github.com/traefik/traefik/v2/pkg/server/service/tcp" tcpservice "github.com/traefik/traefik/v2/pkg/server/service/tcp"
@ -161,38 +160,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
} }
} }
sniCheck := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
if req.TLS == nil {
handlerHTTPS.ServeHTTP(rw, req)
return
}
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
}
host = strings.TrimSpace(host)
serverName := strings.TrimSpace(req.TLS.ServerName)
// Domain Fronting
if !strings.EqualFold(host, serverName) {
tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName)
tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host)
if tlsOptionHeader != tlsOptionSNI {
log.WithoutContext().
WithField("host", host).
WithField("req.Host", req.Host).
WithField("req.TLS.ServerName", req.TLS.ServerName).
Debugf("TLS options difference: SNI=%s, Header:%s", tlsOptionSNI, tlsOptionHeader)
http.Error(rw, http.StatusText(http.StatusMisdirectedRequest), http.StatusMisdirectedRequest)
return
}
}
handlerHTTPS.ServeHTTP(rw, req)
})
router.HTTPSHandler(sniCheck, defaultTLSConf) router.HTTPSHandler(sniCheck, defaultTLSConf)
@ -321,17 +289,3 @@ func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouter
return tcp.NewChain().Extend(*mHandler).Then(sHandler) return tcp.NewChain().Extend(*mHandler).Then(sHandler)
} }
func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string {
tlsOptions, ok := tlsOptionsForHost[host]
if ok {
return tlsOptions
}
tlsOptions, ok = tlsOptionsForHost[strings.ToLower(host)]
if ok {
return tlsOptions
}
return traefiktls.DefaultTLSConfigName
}

View file

@ -59,7 +59,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
"bar": { "bar": {
TCPRouter: &dynamic.TCPRouter{ TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "HostSNI(`foo.bar`)", Rule: "HostSNI(`foo.bar`)",
@ -136,7 +135,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
"bar": { "bar": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "Host(`bar.foo`) && PathPrefix(`/path`)", Rule: "Host(`bar.foo`) && PathPrefix(`/path`)",
@ -240,7 +238,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
"bar": { "bar": {
TCPRouter: &dynamic.TCPRouter{ TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "HostSNI(`foo.bar`)", Rule: "HostSNI(`foo.bar`)",
@ -340,9 +337,26 @@ func TestRuntimeConfiguration(t *testing.T) {
} }
func TestDomainFronting(t *testing.T) { func TestDomainFronting(t *testing.T) {
tlsOptionsBase := map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS10",
},
"host1@file": {
MinVersion: "VersionTLS12",
},
"host1@crd": {
MinVersion: "VersionTLS12",
},
}
entryPoints := []string{"web"}
tests := []struct { tests := []struct {
desc string desc string
routers map[string]*runtime.RouterInfo routers map[string]*runtime.RouterInfo
tlsOptions map[string]traefiktls.Options
host string
ServerName string
expectedStatus int expectedStatus int
}{ }{
{ {
@ -350,7 +364,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -359,12 +373,15 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusMisdirectedRequest, expectedStatus: http.StatusMisdirectedRequest,
}, },
{ {
@ -372,7 +389,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -381,7 +398,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -389,6 +406,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
}, },
{ {
@ -396,7 +416,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -405,7 +425,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`) && PathPrefix(`/foo`)", Rule: "Host(`host1.local`) && PathPrefix(`/foo`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "default", Options: "default",
@ -414,7 +434,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-3@file": { "router-3@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -422,6 +442,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusMisdirectedRequest, expectedStatus: http.StatusMisdirectedRequest,
}, },
{ {
@ -429,7 +452,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -438,7 +461,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`) && PathPrefix(`/bar`)", Rule: "Host(`host1.local`) && PathPrefix(`/bar`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -447,7 +470,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-3@file": { "router-3@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -455,6 +478,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
}, },
{ {
@ -462,7 +488,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -471,7 +497,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@crd": { "router-2@crd": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -479,6 +505,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusMisdirectedRequest, expectedStatus: http.StatusMisdirectedRequest,
}, },
{ {
@ -486,7 +515,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1@crd", Options: "host1@crd",
@ -495,7 +524,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@crd": { "router-2@crd": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1@crd", Options: "host1@crd",
@ -503,25 +532,63 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
}, },
{
desc: "Request is misdirected when server name is empty and the host name is an FQDN, but router's rule is not",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: entryPoints,
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1@file",
},
},
},
},
tlsOptions: map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS13",
},
"host1@file": {
MinVersion: "VersionTLS12",
},
},
host: "host1.local.",
expectedStatus: http.StatusMisdirectedRequest,
},
{
desc: "Request is misdirected when server name is empty and the host name is not FQDN, but router's rule is",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: entryPoints,
Rule: "Host(`host1.local.`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1@file",
},
},
},
},
tlsOptions: map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS13",
},
"host1@file": {
MinVersion: "VersionTLS12",
},
},
host: "host1.local",
expectedStatus: http.StatusMisdirectedRequest,
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
entryPoints := []string{"web"}
tlsOptions := map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS10",
},
"host1@file": {
MinVersion: "VersionTLS12",
},
"host1@crd": {
MinVersion: "VersionTLS12",
},
}
conf := &runtime.Configuration{ conf := &runtime.Configuration{
Routers: test.routers, Routers: test.routers,
} }
@ -529,7 +596,7 @@ func TestDomainFronting(t *testing.T) {
serviceManager := tcp.NewManager(conf) serviceManager := tcp.NewManager(conf)
tlsManager := traefiktls.NewManager() tlsManager := traefiktls.NewManager()
tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, tlsOptions, []*traefiktls.CertAndStores{}) tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{})
httpsHandler := map[string]http.Handler{ httpsHandler := map[string]http.Handler{
"web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}), "web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}),
@ -545,9 +612,9 @@ func TestDomainFronting(t *testing.T) {
require.True(t, ok) require.True(t, ok)
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Host = "host1.local" req.Host = test.host
req.TLS = &tls.ConnectionState{ req.TLS = &tls.ConnectionState{
ServerName: "host2.local", ServerName: test.ServerName,
} }
rw := httptest.NewRecorder() rw := httptest.NewRecorder()

View file

@ -11,6 +11,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/containous/alice"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/traefik/traefik/v2/pkg/config/static" "github.com/traefik/traefik/v2/pkg/config/static"
@ -18,9 +19,11 @@ import (
"github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares" "github.com/traefik/traefik/v2/pkg/middlewares"
"github.com/traefik/traefik/v2/pkg/middlewares/forwardedheaders" "github.com/traefik/traefik/v2/pkg/middlewares/forwardedheaders"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server/router" "github.com/traefik/traefik/v2/pkg/server/router"
"github.com/traefik/traefik/v2/pkg/tcp" "github.com/traefik/traefik/v2/pkg/tcp"
"github.com/traefik/traefik/v2/pkg/types"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
) )
@ -60,7 +63,7 @@ func (h *httpForwarder) Accept() (net.Conn, error) {
type TCPEntryPoints map[string]*TCPEntryPoint type TCPEntryPoints map[string]*TCPEntryPoint
// NewTCPEntryPoints creates a new TCPEntryPoints. // NewTCPEntryPoints creates a new TCPEntryPoints.
func NewTCPEntryPoints(entryPointsConfig static.EntryPoints) (TCPEntryPoints, error) { func NewTCPEntryPoints(entryPointsConfig static.EntryPoints, hostResolverConfig *types.HostResolverConfig) (TCPEntryPoints, error) {
serverEntryPointsTCP := make(TCPEntryPoints) serverEntryPointsTCP := make(TCPEntryPoints)
for entryPointName, config := range entryPointsConfig { for entryPointName, config := range entryPointsConfig {
protocol, err := config.GetProtocol() protocol, err := config.GetProtocol()
@ -74,7 +77,7 @@ func NewTCPEntryPoints(entryPointsConfig static.EntryPoints) (TCPEntryPoints, er
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName)) ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
serverEntryPointsTCP[entryPointName], err = NewTCPEntryPoint(ctx, config) serverEntryPointsTCP[entryPointName], err = NewTCPEntryPoint(ctx, config, hostResolverConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while building entryPoint %s: %w", entryPointName, err) return nil, fmt.Errorf("error while building entryPoint %s: %w", entryPointName, err)
} }
@ -130,7 +133,7 @@ type TCPEntryPoint struct {
} }
// NewTCPEntryPoint creates a new TCPEntryPoint. // NewTCPEntryPoint creates a new TCPEntryPoint.
func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*TCPEntryPoint, error) { func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint, hostResolverConfig *types.HostResolverConfig) (*TCPEntryPoint, error) {
tracker := newConnectionTracker() tracker := newConnectionTracker()
listener, err := buildListener(ctx, configuration) listener, err := buildListener(ctx, configuration)
@ -140,14 +143,16 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T
rt := &tcp.Router{} rt := &tcp.Router{}
httpServer, err := createHTTPServer(ctx, listener, configuration, true) reqDecorator := requestdecorator.New(hostResolverConfig)
httpServer, err := createHTTPServer(ctx, listener, configuration, true, reqDecorator)
if err != nil { if err != nil {
return nil, fmt.Errorf("error preparing httpServer: %w", err) return nil, fmt.Errorf("error preparing httpServer: %w", err)
} }
rt.HTTPForwarder(httpServer.Forwarder) rt.HTTPForwarder(httpServer.Forwarder)
httpsServer, err := createHTTPServer(ctx, listener, configuration, false) httpsServer, err := createHTTPServer(ctx, listener, configuration, false, reqDecorator)
if err != nil { if err != nil {
return nil, fmt.Errorf("error preparing httpsServer: %w", err) return nil, fmt.Errorf("error preparing httpsServer: %w", err)
} }
@ -500,16 +505,19 @@ type httpServer struct {
Switcher *middlewares.HTTPHandlerSwitcher Switcher *middlewares.HTTPHandlerSwitcher
} }
func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool) (*httpServer, error) { func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) {
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter()) httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
if err != nil {
return nil, err
}
var handler http.Handler var handler http.Handler
var err error
handler, err = forwardedheaders.NewXForwarded( handler, err = forwardedheaders.NewXForwarded(
configuration.ForwardedHeaders.Insecure, configuration.ForwardedHeaders.Insecure,
configuration.ForwardedHeaders.TrustedIPs, configuration.ForwardedHeaders.TrustedIPs,
httpSwitcher) next)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -17,38 +17,57 @@ import (
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs // LocalhostCert is a PEM-encoded TLS cert with SAN IPs
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT. // "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
// generated from src/crypto/tls: // generated from src/crypto/tls:
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var ( var (
localhostCert = traefiktls.FileOrContent(`-----BEGIN CERTIFICATE----- localhostCert = traefiktls.FileOrContent(`-----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
fblo6RBxUQ== bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
-----END CERTIFICATE-----`) -----END CERTIFICATE-----`)
// LocalhostKey is the private key for localhostCert. // LocalhostKey is the private key for localhostCert.
localhostKey = traefiktls.FileOrContent(`-----BEGIN RSA PRIVATE KEY----- localhostKey = traefiktls.FileOrContent(`-----BEGIN RSA PRIVATE KEY-----
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
ZboOWVe3icTy64BT3OQhmg==
-----END RSA PRIVATE KEY-----`) -----END RSA PRIVATE KEY-----`)
) )
@ -72,7 +91,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
HTTP3: &static.HTTP3Config{ HTTP3: &static.HTTP3Config{
AdvertisedPort: 8080, AdvertisedPort: 8080,
}, },
}) }, nil)
require.NoError(t, err) require.NoError(t, err)
router := &tcp.Router{} router := &tcp.Router{}

View file

@ -79,7 +79,7 @@ func testShutdown(t *testing.T, router *tcp.Router) {
Address: "127.0.0.1:0", Address: "127.0.0.1:0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
}) }, nil)
require.NoError(t, err) require.NoError(t, err)
conn, err := startEntrypoint(entryPoint, router) conn, err := startEntrypoint(entryPoint, router)
@ -162,7 +162,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
Address: ":0", Address: ":0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
}) }, nil)
require.NoError(t, err) require.NoError(t, err)
router := &tcp.Router{} router := &tcp.Router{}
@ -198,7 +198,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
Address: ":0", Address: ":0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
}) }, nil)
require.NoError(t, err) require.NoError(t, err)
router := &tcp.Router{} router := &tcp.Router{}

View file

@ -17,35 +17,54 @@ import (
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var ( var (
localhostCert = FileOrContent(`-----BEGIN CERTIFICATE----- localhostCert = FileOrContent(`-----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
fblo6RBxUQ== bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
-----END CERTIFICATE-----`) -----END CERTIFICATE-----`)
// LocalhostKey is the private key for localhostCert. // LocalhostKey is the private key for localhostCert.
localhostKey = FileOrContent(`-----BEGIN RSA PRIVATE KEY----- localhostKey = FileOrContent(`-----BEGIN RSA PRIVATE KEY-----
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
ZboOWVe3icTy64BT3OQhmg==
-----END RSA PRIVATE KEY-----`) -----END RSA PRIVATE KEY-----`)
) )

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file" OutputType = "file"
FileName = "traefik_changelog.md" FileName = "traefik_changelog.md"
# example new bugfix v2.5.7 # example new bugfix v2.6.1
CurrentRef = "v2.5" CurrentRef = "v2.6"
PreviousRef = "v2.5.6" PreviousRef = "v2.6.0"
BaseBranch = "v2.5" BaseBranch = "v2.6"
FutureCurrentRefName = "v2.5.7" FutureCurrentRefName = "v2.6.1"
ThresholdPreviousRef = 10 ThresholdPreviousRef = 10
ThresholdCurrentRef = 10 ThresholdCurrentRef = 10