From 60ff50a675ebb44c76aa5dd8ab2ce8597becc12f Mon Sep 17 00:00:00 2001 From: valerauko Date: Fri, 10 Sep 2021 21:58:13 +0900 Subject: [PATCH] Add HTTP3Config --- docs/content/migration/v2.md | 9 ++ .../reference/static-configuration/cli-ref.md | 9 +- .../reference/static-configuration/env-ref.md | 9 +- .../reference/static-configuration/file.toml | 3 +- .../reference/static-configuration/file.yaml | 3 +- docs/content/routing/entrypoints.md | 53 +++++++-- pkg/config/static/entrypoints.go | 7 +- pkg/config/static/static_config.go | 7 +- pkg/server/server_entrypoint_tcp_http3.go | 25 ++++- .../server_entrypoint_tcp_http3_test.go | 105 ++++++++++++++++++ 10 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 pkg/server/server_entrypoint_tcp_http3_test.go diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 778abc3d6..1298354b2 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -415,3 +415,12 @@ For more advanced use cases, you can use either the [RedirectScheme middleware]( Following up on the deprecation started [previously](#x509-commonname-deprecation), as the `x509ignoreCN=0` value for the `GODEBUG` is [deprecated in Go 1.17](https://tip.golang.org/doc/go1.17#crypto/x509), the legacy behavior related to the CommonName field can not be enabled at all anymore. + +## v2.5 to v2.6 + +### HTTP3 + +Traefik v2.6 introduces the `AdvertisedPort` option, +which allows advertising, in the `Alt-Svc` header, a UDP port different from the one on which Traefik is actually listening (the EntryPoint's port). +By doing so, it introduces a new configuration structure `http3`, which replaces the `enableHTTP3` option (which therefore doesn't exist anymore). +To enable HTTP3 on an EntryPoint, please check out the [HTTP3 configuration](../routing/entrypoints.md#http3) documentation. diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index e5068eb57..5b9a2b87b 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -102,9 +102,6 @@ Entry points definition. (Default: ```false```) `--entrypoints..address`: Entry point address. -`--entrypoints..enablehttp3`: -Enable HTTP3. (Default: ```false```) - `--entrypoints..forwardedheaders.insecure`: Trust all forwarded headers. (Default: ```false```) @@ -147,6 +144,12 @@ Subject alternative names. `--entrypoints..http.tls.options`: Default TLS options for the routers linked to the entry point. +`--entrypoints..http3`: +HTTP3 configuration. (Default: ```false```) + +`--entrypoints..http3.advertisedport`: +UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) + `--entrypoints..proxyprotocol`: Proxy-Protocol configuration. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 525f65f66..d955cebbf 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -102,9 +102,6 @@ Entry points definition. (Default: ```false```) `TRAEFIK_ENTRYPOINTS__ADDRESS`: Entry point address. -`TRAEFIK_ENTRYPOINTS__ENABLEHTTP3`: -Enable HTTP3. (Default: ```false```) - `TRAEFIK_ENTRYPOINTS__FORWARDEDHEADERS_INSECURE`: Trust all forwarded headers. (Default: ```false```) @@ -114,6 +111,12 @@ Trust only forwarded headers from selected IPs. `TRAEFIK_ENTRYPOINTS__HTTP`: HTTP configuration. +`TRAEFIK_ENTRYPOINTS__HTTP3`: +HTTP3 configuration. (Default: ```false```) + +`TRAEFIK_ENTRYPOINTS__HTTP3_ADVERTISEDPORT`: +UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) + `TRAEFIK_ENTRYPOINTS__HTTP_MIDDLEWARES`: Default middlewares for the routers linked to the entry point. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 1e17963cf..fed9cca5e 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -14,7 +14,6 @@ [entryPoints] [entryPoints.EntryPoint0] address = "foobar" - enableHTTP3 = true [entryPoints.EntryPoint0.transport] [entryPoints.EntryPoint0.transport.lifeCycle] requestAcceptGraceTimeout = 42 @@ -31,6 +30,8 @@ trustedIPs = ["foobar", "foobar"] [entryPoints.EntryPoint0.udp] timeout = 42 + [entryPoints.EntryPoint0.http3] + advertisedPort = 42 [entryPoints.EntryPoint0.http] middlewares = ["foobar", "foobar"] [entryPoints.EntryPoint0.http.redirections] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index acd4e8793..9b34d1bac 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -32,7 +32,8 @@ entryPoints: trustedIPs: - foobar - foobar - enableHTTP3: true + http3: + advertisedPort: 42 udp: timeout: 42 http: diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 69f917143..38c3a4461 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -100,7 +100,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. entryPoints: name: address: ":8888" # same as ":8888/tcp" - enableHTTP3: true + http3: + advertisedPort: 8888 transport: lifeCycle: requestAcceptGraceTimeout: 42 @@ -126,7 +127,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. [entryPoints] [entryPoints.name] address = ":8888" # same as ":8888/tcp" - enableHTTP3 = true + [entryPoints.name.http3] + advertisedPort = 8888 [entryPoints.name.transport] [entryPoints.name.transport.lifeCycle] requestAcceptGraceTimeout = 42 @@ -146,7 +148,7 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 # same as :8888/tcp - --entryPoints.name.http3=true + --entryPoints.name.http3.advertisedport=8888 --entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42 --entryPoints.name.transport.lifeCycle.graceTimeOut=42 --entryPoints.name.transport.respondingTimeouts.readTimeout=42 @@ -221,9 +223,11 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go. -### EnableHTTP3 +### HTTP3 -`enableHTTP3` defines that you want to enable HTTP3 on this `address`. +#### `http3` + +`http3` enables HTTP3 protocol on the entryPoint. You can only enable HTTP3 on a TCP entrypoint. Enabling HTTP3 will automatically add the correct headers for the connection upgrade to HTTP3. @@ -240,22 +244,51 @@ Enabling HTTP3 will automatically add the correct headers for the connection upg ```yaml tab="File (YAML)" experimental: http3: true - + entryPoints: name: - enableHTTP3: true + http3: {} ``` ```toml tab="File (TOML)" [experimental] http3 = true - [entryPoints.name] - enableHTTP3 = true + [entryPoints.name.http3] ``` ```bash tab="CLI" - --experimental.http3=true --entrypoints.name.enablehttp3=true + --experimental.http3=true --entrypoints.name.http3 + ``` + +#### `advertisedPort` + +`http3.advertisedPort` defines which UDP port to advertise as the HTTP3 authority. +It defaults to the entrypoint's address port. +It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. + +!!! info "http3.advertisedPort" + + ```yaml tab="File (YAML)" + experimental: + http3: true + + entryPoints: + name: + http3: + advertisedPort: 443 + ``` + + ```toml tab="File (TOML)" + [experimental] + http3 = true + + [entryPoints.name.http3] + advertisedPort = 443 + ``` + + ```bash tab="CLI" + --experimental.http3=true --entrypoints.name.http3.advertisedport=443 ``` ### Forwarded Headers diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 823a14e10..8b9c72d2a 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -16,7 +16,7 @@ type EntryPoint struct { ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"` HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` - EnableHTTP3 bool `description:"Enable HTTP3." json:"enableHTTP3,omitempty" toml:"enableHTTP3,omitempty" yaml:"enableHTTP3,omitempty" export:"true"` + HTTP3 *HTTP3Config `description:"HTTP3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"` } @@ -72,6 +72,11 @@ type RedirectEntryPoint struct { Priority int `description:"Priority of the generated router." json:"priority,omitempty" toml:"priority,omitempty" yaml:"priority,omitempty" export:"true"` } +// HTTP3Config is the HTTP3 configuration of an entry point. +type HTTP3Config struct { + AdvertisedPort int32 `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"` +} + // SetDefaults sets the default values. func (r *RedirectEntryPoint) SetDefaults() { r.Scheme = "https" diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index dacabfcc1..a3a8ba547 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -249,8 +249,11 @@ func (c *Configuration) SetEffectiveConfiguration() { } if c.Experimental == nil || !c.Experimental.HTTP3 { - for _, ep := range c.EntryPoints { - ep.EnableHTTP3 = false + for epName, ep := range c.EntryPoints { + if ep.HTTP3 != nil { + ep.HTTP3 = nil + log.WithoutContext().Debugf("Disabling HTTP3 configuration for entryPoint %q: HTTP3 is disabled in the experimental configuration section", epName) + } } } diff --git a/pkg/server/server_entrypoint_tcp_http3.go b/pkg/server/server_entrypoint_tcp_http3.go index 2c4a28fab..326661fe6 100644 --- a/pkg/server/server_entrypoint_tcp_http3.go +++ b/pkg/server/server_entrypoint_tcp_http3.go @@ -26,7 +26,7 @@ type http3server struct { } func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, httpsServer *httpServer) (*http3server, error) { - if !configuration.EnableHTTP3 { + if configuration.HTTP3 == nil { return nil, nil } @@ -56,8 +56,10 @@ func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, https previousHandler := httpsServer.Server.(*http.Server).Handler + setQuicHeaders := getQuicHeadersSetter(configuration) + httpsServer.Server.(*http.Server).Handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - err := h3.Server.SetQuicHeaders(rw.Header()) + err := setQuicHeaders(rw.Header()) if err != nil { log.FromContext(ctx).Errorf("failed to set HTTP3 headers: %v", err) } @@ -68,6 +70,25 @@ func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, https return h3, nil } +// TODO: rewrite if at some point `port` become an exported field of http3.Server. +func getQuicHeadersSetter(configuration *static.EntryPoint) func(header http.Header) error { + advertisedAddress := configuration.GetAddress() + if configuration.HTTP3.AdvertisedPort != 0 { + advertisedAddress = fmt.Sprintf(`:%d`, configuration.HTTP3.AdvertisedPort) + } + + // if `QuickConfig` of h3.server happens to be configured, + // it should also be configured identically in the headerServer + headerServer := &http3.Server{ + Server: &http.Server{ + Addr: advertisedAddress, + }, + } + + // set quic headers with the "header" http3 server instance + return headerServer.SetQuicHeaders +} + func (e *http3server) Start() error { return e.Serve(e.http3conn) } diff --git a/pkg/server/server_entrypoint_tcp_http3_test.go b/pkg/server/server_entrypoint_tcp_http3_test.go new file mode 100644 index 000000000..480bdb713 --- /dev/null +++ b/pkg/server/server_entrypoint_tcp_http3_test.go @@ -0,0 +1,105 @@ +package server + +import ( + "bufio" + "context" + "crypto/tls" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/config/static" + "github.com/traefik/traefik/v2/pkg/tcp" + traefiktls "github.com/traefik/traefik/v2/pkg/tls" +) + +// 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. +// 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 +var ( + localhostCert = traefiktls.FileOrContent(`-----BEGIN CERTIFICATE----- +MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 +iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul +rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA +AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 +tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs +h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM +fblo6RBxUQ== +-----END CERTIFICATE-----`) + + // LocalhostKey is the private key for localhostCert. + localhostKey = traefiktls.FileOrContent(`-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB +l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX +qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo +f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== +-----END RSA PRIVATE KEY-----`) +) + +func TestHTTP3AdvertisedPort(t *testing.T) { + certContent, err := localhostCert.Read() + require.NoError(t, err) + + keyContent, err := localhostKey.Read() + require.NoError(t, err) + + tlsCert, err := tls.X509KeyPair(certContent, keyContent) + require.NoError(t, err) + + epConfig := &static.EntryPointsTransport{} + epConfig.SetDefaults() + + entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + Address: "127.0.0.1:8090", + Transport: epConfig, + ForwardedHeaders: &static.ForwardedHeaders{}, + HTTP3: &static.HTTP3Config{ + AdvertisedPort: 8080, + }, + }) + require.NoError(t, err) + + router := &tcp.Router{} + router.AddRouteHTTPTLS("*", &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + }) + router.HTTPSHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + }), nil) + + go entryPoint.Start(context.Background()) + entryPoint.SwitchRouter(router) + + conn, err := tls.Dial("tcp", "127.0.0.1:8090", &tls.Config{ + InsecureSkipVerify: true, + }) + require.NoError(t, err) + + request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8090", nil) + require.NoError(t, err) + + err = request.Write(conn) + require.NoError(t, err) + + r, err := http.ReadResponse(bufio.NewReader(conn), nil) + require.NoError(t, err) + + assert.NotContains(t, r.Header.Get("Alt-Svc"), ":8090") + assert.Contains(t, r.Header.Get("Alt-Svc"), ":8080") +}