Makes ALPN protocols configurable

This commit is contained in:
Romain 2021-08-20 18:20:06 +02:00 committed by GitHub
parent fa53f7ec85
commit 2644c1f598
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 216 additions and 15 deletions

View file

@ -399,6 +399,47 @@ spec:
preferServerCipherSuites: true preferServerCipherSuites: true
``` ```
### ALPN Protocols
_Optional, Default="h2, http/1.1, acme-tls/1"_
This option allows to specify the list of supported application level protocols for the TLS handshake,
in order of preference.
If the client supports ALPN, the selected protocol will be one from this list,
and the connection will fail if there is no mutually supported protocol.
```yaml tab="File (YAML)"
# Dynamic configuration
tls:
options:
default:
alpnProtocols:
- http/1.1
- h2
```
```toml tab="File (TOML)"
# Dynamic configuration
[tls.options]
[tls.options.default]
alpnProtocols = ["http/1.1", "h2"]
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
name: default
namespace: default
spec:
alpnProtocols:
- http/1.1
- h2
```
### Client Authentication (mTLS) ### Client Authentication (mTLS)
Traefik supports mutual authentication, through the `clientAuth` section. Traefik supports mutual authentication, through the `clientAuth` section.

View file

@ -421,6 +421,7 @@
curvePreferences = ["foobar", "foobar"] curvePreferences = ["foobar", "foobar"]
sniStrict = true sniStrict = true
preferServerCipherSuites = true preferServerCipherSuites = true
alpnProtocols = ["foobar", "foobar"]
[tls.options.Options0.clientAuth] [tls.options.Options0.clientAuth]
caFiles = ["foobar", "foobar"] caFiles = ["foobar", "foobar"]
clientAuthType = "foobar" clientAuthType = "foobar"
@ -431,6 +432,7 @@
curvePreferences = ["foobar", "foobar"] curvePreferences = ["foobar", "foobar"]
sniStrict = true sniStrict = true
preferServerCipherSuites = true preferServerCipherSuites = true
alpnProtocols = ["foobar", "foobar"]
[tls.options.Options1.clientAuth] [tls.options.Options1.clientAuth]
caFiles = ["foobar", "foobar"] caFiles = ["foobar", "foobar"]
clientAuthType = "foobar" clientAuthType = "foobar"

View file

@ -470,6 +470,9 @@ tls:
clientAuthType: foobar clientAuthType: foobar
sniStrict: true sniStrict: true
preferServerCipherSuites: true preferServerCipherSuites: true
alpnProtocols:
- foobar
- foobar
Options1: Options1:
minVersion: foobar minVersion: foobar
maxVersion: foobar maxVersion: foobar
@ -486,6 +489,9 @@ tls:
clientAuthType: foobar clientAuthType: foobar
sniStrict: true sniStrict: true
preferServerCipherSuites: true preferServerCipherSuites: true
alpnProtocols:
- foobar
- foobar
stores: stores:
Store0: Store0:
defaultCertificate: defaultCertificate:

View file

@ -194,6 +194,9 @@ spec:
clientAuthType: RequireAndVerifyClientCert clientAuthType: RequireAndVerifyClientCert
sniStrict: true sniStrict: true
preferServerCipherSuites: true preferServerCipherSuites: true
alpnProtocols:
- foobar
- foobar
--- ---
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1

View file

@ -274,6 +274,8 @@
| `traefik/tls/certificates/1/keyFile` | `foobar` | | `traefik/tls/certificates/1/keyFile` | `foobar` |
| `traefik/tls/certificates/1/stores/0` | `foobar` | | `traefik/tls/certificates/1/stores/0` | `foobar` |
| `traefik/tls/certificates/1/stores/1` | `foobar` | | `traefik/tls/certificates/1/stores/1` | `foobar` |
| `traefik/tls/options/Options0/alpnProtocols/0` | `foobar` |
| `traefik/tls/options/Options0/alpnProtocols/1` | `foobar` |
| `traefik/tls/options/Options0/cipherSuites/0` | `foobar` | | `traefik/tls/options/Options0/cipherSuites/0` | `foobar` |
| `traefik/tls/options/Options0/cipherSuites/1` | `foobar` | | `traefik/tls/options/Options0/cipherSuites/1` | `foobar` |
| `traefik/tls/options/Options0/clientAuth/caFiles/0` | `foobar` | | `traefik/tls/options/Options0/clientAuth/caFiles/0` | `foobar` |
@ -285,6 +287,8 @@
| `traefik/tls/options/Options0/minVersion` | `foobar` | | `traefik/tls/options/Options0/minVersion` | `foobar` |
| `traefik/tls/options/Options0/preferServerCipherSuites` | `true` | | `traefik/tls/options/Options0/preferServerCipherSuites` | `true` |
| `traefik/tls/options/Options0/sniStrict` | `true` | | `traefik/tls/options/Options0/sniStrict` | `true` |
| `traefik/tls/options/Options1/alpnProtocols/0` | `foobar` |
| `traefik/tls/options/Options1/alpnProtocols/1` | `foobar` |
| `traefik/tls/options/Options1/cipherSuites/0` | `foobar` | | `traefik/tls/options/Options1/cipherSuites/0` | `foobar` |
| `traefik/tls/options/Options1/cipherSuites/1` | `foobar` | | `traefik/tls/options/Options1/cipherSuites/1` | `foobar` |
| `traefik/tls/options/Options1/clientAuth/caFiles/0` | `foobar` | | `traefik/tls/options/Options1/clientAuth/caFiles/0` | `foobar` |

View file

@ -36,6 +36,10 @@ spec:
spec: spec:
description: TLSOptionSpec configures TLS for an entry point. description: TLSOptionSpec configures TLS for an entry point.
properties: properties:
alpnProtocols:
items:
type: string
type: array
cipherSuites: cipherSuites:
items: items:
type: string type: string

View file

@ -1506,6 +1506,8 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
- secret-ca2 - secret-ca2
clientAuthType: VerifyClientCertIfGiven # [7] clientAuthType: VerifyClientCertIfGiven # [7]
sniStrict: true # [8] sniStrict: true # [8]
alpnProtocols: # [9]
- foobar
``` ```
| Ref | Attribute | Purpose | | Ref | Attribute | Purpose |
@ -1518,6 +1520,7 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
| [6] | `clientAuth.secretNames` | list of names of the referenced Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) (in TLSOption namespace). The secret must contain a certificate under either a `tls.ca` or a `ca.crt` key. | | [6] | `clientAuth.secretNames` | list of names of the referenced Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) (in TLSOption namespace). The secret must contain a certificate under either a `tls.ca` or a `ca.crt` key. |
| [7] | `clientAuth.clientAuthType` | defines the client authentication type to apply. The available values are: `NoClientCert`, `RequestClientCert`, `VerifyClientCertIfGiven` and `RequireAndVerifyClientCert` | | [7] | `clientAuth.clientAuthType` | defines the client authentication type to apply. The available values are: `NoClientCert`, `RequestClientCert`, `VerifyClientCertIfGiven` and `RequireAndVerifyClientCert` |
| [8] | `sniStrict` | if `true`, Traefik won't allow connections from clients connections that do not specify a server_name extension | | [8] | `sniStrict` | if `true`, Traefik won't allow connections from clients connections that do not specify a server_name extension |
| [9] | `alpnProtocols` | List of supported [application level protocols](../../https/tls.md#alpn-protocols) for the TLS handshake, in order of preference. |
!!! info "CA Secret" !!! info "CA Secret"

View file

@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.4.1 controller-gen.kubebuilder.io/version: v0.5.0
creationTimestamp: null creationTimestamp: null
name: ingressroutes.traefik.containo.us name: ingressroutes.traefik.containo.us
spec: spec:
@ -202,7 +202,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.4.1 controller-gen.kubebuilder.io/version: v0.5.0
creationTimestamp: null creationTimestamp: null
name: ingressroutetcps.traefik.containo.us name: ingressroutetcps.traefik.containo.us
spec: spec:
@ -362,7 +362,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.4.1 controller-gen.kubebuilder.io/version: v0.5.0
creationTimestamp: null creationTimestamp: null
name: ingressrouteudps.traefik.containo.us name: ingressrouteudps.traefik.containo.us
spec: spec:
@ -1208,6 +1208,10 @@ spec:
spec: spec:
description: TLSOptionSpec configures TLS for an entry point. description: TLSOptionSpec configures TLS for an entry point.
properties: properties:
alpnProtocols:
items:
type: string
type: array
cipherSuites: cipherSuites:
items: items:
type: string type: string

View file

@ -703,6 +703,12 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
id = tlsOption.Name id = tlsOption.Name
nsDefault = append(nsDefault, tlsOption.Namespace) nsDefault = append(nsDefault, tlsOption.Namespace)
} }
alpnProtocols := tls.DefaultTLSOptions.ALPNProtocols
if len(tlsOption.Spec.ALPNProtocols) > 0 {
alpnProtocols = tlsOption.Spec.ALPNProtocols
}
tlsOptions[id] = tls.Options{ tlsOptions[id] = tls.Options{
MinVersion: tlsOption.Spec.MinVersion, MinVersion: tlsOption.Spec.MinVersion,
MaxVersion: tlsOption.Spec.MaxVersion, MaxVersion: tlsOption.Spec.MaxVersion,
@ -714,6 +720,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
}, },
SniStrict: tlsOption.Spec.SniStrict, SniStrict: tlsOption.Spec.SniStrict,
PreferServerCipherSuites: tlsOption.Spec.PreferServerCipherSuites, PreferServerCipherSuites: tlsOption.Spec.PreferServerCipherSuites,
ALPNProtocols: alpnProtocols,
} }
} }

View file

@ -616,6 +616,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
}, },
SniStrict: true, SniStrict: true,
PreferServerCipherSuites: true, PreferServerCipherSuites: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -678,6 +683,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
ClientAuthType: "VerifyClientCertIfGiven", ClientAuthType: "VerifyClientCertIfGiven",
}, },
SniStrict: true, SniStrict: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -739,6 +749,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
ClientAuthType: "VerifyClientCertIfGiven", ClientAuthType: "VerifyClientCertIfGiven",
}, },
SniStrict: true, SniStrict: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -789,6 +804,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default-foo": { "default-foo": {
MinVersion: "VersionTLS12", MinVersion: "VersionTLS12",
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -839,6 +859,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default-foo": { "default-foo": {
MinVersion: "VersionTLS12", MinVersion: "VersionTLS12",
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -2539,6 +2564,11 @@ func TestLoadIngressRoutes(t *testing.T) {
}, },
SniStrict: true, SniStrict: true,
PreferServerCipherSuites: true, PreferServerCipherSuites: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -2648,6 +2678,11 @@ func TestLoadIngressRoutes(t *testing.T) {
}, },
SniStrict: true, SniStrict: true,
PreferServerCipherSuites: true, PreferServerCipherSuites: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -2716,6 +2751,11 @@ func TestLoadIngressRoutes(t *testing.T) {
ClientAuthType: "VerifyClientCertIfGiven", ClientAuthType: "VerifyClientCertIfGiven",
}, },
SniStrict: true, SniStrict: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -2779,6 +2819,11 @@ func TestLoadIngressRoutes(t *testing.T) {
ClientAuthType: "VerifyClientCertIfGiven", ClientAuthType: "VerifyClientCertIfGiven",
}, },
SniStrict: true, SniStrict: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -2831,6 +2876,11 @@ func TestLoadIngressRoutes(t *testing.T) {
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default-foo": { "default-foo": {
MinVersion: "VersionTLS12", MinVersion: "VersionTLS12",
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },
@ -2883,6 +2933,11 @@ func TestLoadIngressRoutes(t *testing.T) {
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default-foo": { "default-foo": {
MinVersion: "VersionTLS12", MinVersion: "VersionTLS12",
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
}, },

View file

@ -27,6 +27,7 @@ type TLSOptionSpec struct {
ClientAuth ClientAuth `json:"clientAuth,omitempty"` ClientAuth ClientAuth `json:"clientAuth,omitempty"`
SniStrict bool `json:"sniStrict,omitempty"` SniStrict bool `json:"sniStrict,omitempty"`
PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty"` PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty"`
ALPNProtocols []string `json:"alpnProtocols,omitempty"`
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View file

@ -1327,6 +1327,11 @@ func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) {
copy(*out, *in) copy(*out, *in)
} }
in.ClientAuth.DeepCopyInto(&out.ClientAuth) in.ClientAuth.DeepCopyInto(&out.ClientAuth)
if in.ALPNProtocols != nil {
in, out := &in.ALPNProtocols, &out.ALPNProtocols
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View file

@ -846,6 +846,11 @@ func Test_buildConfiguration(t *testing.T) {
ClientAuthType: "foobar", ClientAuthType: "foobar",
}, },
SniStrict: true, SniStrict: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
"Options1": { "Options1": {
MinVersion: "foobar", MinVersion: "foobar",
@ -866,6 +871,11 @@ func Test_buildConfiguration(t *testing.T) {
ClientAuthType: "foobar", ClientAuthType: "foobar",
}, },
SniStrict: true, SniStrict: true,
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
}, },
}, },
Stores: map[string]tls.Store{ Stores: map[string]tls.Store{

View file

@ -182,7 +182,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
desc: "Nil returns an empty configuration", desc: "Nil returns an empty configuration",
given: nil, given: nil,
expected: map[string]tls.Options{ expected: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
}, },
}, },
{ {
@ -199,7 +205,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
}, },
}, },
expected: map[string]tls.Options{ expected: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
"foo@provider-1": { "foo@provider-1": {
MinVersion: "VersionTLS12", MinVersion: "VersionTLS12",
}, },
@ -228,7 +240,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
}, },
}, },
expected: map[string]tls.Options{ expected: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
"foo@provider-1": { "foo@provider-1": {
MinVersion: "VersionTLS13", MinVersion: "VersionTLS13",
}, },
@ -334,7 +352,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
}, },
}, },
expected: map[string]tls.Options{ expected: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
"foo@provider-1": { "foo@provider-1": {
MinVersion: "VersionTLS12", MinVersion: "VersionTLS12",
}, },

View file

@ -76,7 +76,13 @@ func TestNewConfigurationWatcher(t *testing.T) {
}, },
TLS: &dynamic.TLSConfiguration{ TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
}, },
Stores: map[string]tls.Store{}, Stores: map[string]tls.Store{},
}, },
@ -236,7 +242,13 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
}, },
TLS: &dynamic.TLSConfiguration{ TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
}, },
Stores: map[string]tls.Store{}, Stores: map[string]tls.Store{},
}, },
@ -292,7 +304,13 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
}, },
TLS: &dynamic.TLSConfiguration{ TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{ Options: map[string]tls.Options{
"default": {}, "default": {
ALPNProtocols: []string{
"h2",
"http/1.1",
"acme-tls/1",
},
},
}, },
Stores: map[string]tls.Store{}, Stores: map[string]tls.Store{},
}, },

View file

@ -23,6 +23,13 @@ type Options struct {
ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"` ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"`
SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"` SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"`
PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty" toml:"preferServerCipherSuites,omitempty" yaml:"preferServerCipherSuites,omitempty" export:"true"` PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty" toml:"preferServerCipherSuites,omitempty" yaml:"preferServerCipherSuites,omitempty" export:"true"`
ALPNProtocols []string `json:"alpnProtocols,omitempty" toml:"alpnProtocols,omitempty" yaml:"alpnProtocols,omitempty" export:"true"`
}
// SetDefaults sets the default values for an Options struct.
func (o *Options) SetDefaults() {
// ensure http2 enabled
o.ALPNProtocols = DefaultTLSOptions.ALPNProtocols
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View file

@ -24,7 +24,10 @@ const (
) )
// DefaultTLSOptions the default TLS options. // DefaultTLSOptions the default TLS options.
var DefaultTLSOptions = Options{} var DefaultTLSOptions = Options{
// ensure http2 enabled
ALPNProtocols: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
}
// Manager is the TLS option/store/configuration factory. // Manager is the TLS option/store/configuration factory.
type Manager struct { type Manager struct {
@ -230,10 +233,9 @@ func buildCertificateStore(ctx context.Context, tlsStore Store, storename string
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI. // creates a TLS config that allows terminating HTTPS for multiple domains using SNI.
func buildTLSConfig(tlsOption Options) (*tls.Config, error) { func buildTLSConfig(tlsOption Options) (*tls.Config, error) {
conf := &tls.Config{} conf := &tls.Config{
NextProtos: tlsOption.ALPNProtocols,
// ensure http2 enabled }
conf.NextProtos = []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol}
if len(tlsOption.ClientAuth.CAFiles) > 0 { if len(tlsOption.ClientAuth.CAFiles) > 0 {
pool := x509.NewCertPool() pool := x509.NewCertPool()

View file

@ -85,6 +85,11 @@ func (in *Options) DeepCopyInto(out *Options) {
copy(*out, *in) copy(*out, *in)
} }
in.ClientAuth.DeepCopyInto(&out.ClientAuth) in.ClientAuth.DeepCopyInto(&out.ClientAuth)
if in.ALPNProtocols != nil {
in, out := &in.ALPNProtocols, &out.ALPNProtocols
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }