Support auth-tls-secret and auth-tls-verify-client annotations

This commit is contained in:
Gina A.
2026-01-29 11:10:04 +01:00
committed by GitHub
parent 54fca86901
commit 1bc9569399
9 changed files with 486 additions and 45 deletions
@@ -268,7 +268,7 @@ The following annotations are organized by category for easier navigation.
### SSL/TLS ### SSL/TLS
| Annotation | Limitations / Notes | | Annotation | Limitations / Notes |
|-------------------------------------------------------|--------------------------------------------------------------------------------------------| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| <a id="opt-nginx-ingress-kubernetes-iossl-redirect" href="#opt-nginx-ingress-kubernetes-iossl-redirect" title="#opt-nginx-ingress-kubernetes-iossl-redirect">`nginx.ingress.kubernetes.io/ssl-redirect`</a> | Cannot opt-out per route if enabled globally. | | <a id="opt-nginx-ingress-kubernetes-iossl-redirect" href="#opt-nginx-ingress-kubernetes-iossl-redirect" title="#opt-nginx-ingress-kubernetes-iossl-redirect">`nginx.ingress.kubernetes.io/ssl-redirect`</a> | Cannot opt-out per route if enabled globally. |
| <a id="opt-nginx-ingress-kubernetes-ioforce-ssl-redirect" href="#opt-nginx-ingress-kubernetes-ioforce-ssl-redirect" title="#opt-nginx-ingress-kubernetes-ioforce-ssl-redirect">`nginx.ingress.kubernetes.io/force-ssl-redirect`</a> | Cannot opt-out per route if enabled globally. | | <a id="opt-nginx-ingress-kubernetes-ioforce-ssl-redirect" href="#opt-nginx-ingress-kubernetes-ioforce-ssl-redirect" title="#opt-nginx-ingress-kubernetes-ioforce-ssl-redirect">`nginx.ingress.kubernetes.io/force-ssl-redirect`</a> | Cannot opt-out per route if enabled globally. |
| <a id="opt-nginx-ingress-kubernetes-iossl-passthrough" href="#opt-nginx-ingress-kubernetes-iossl-passthrough" title="#opt-nginx-ingress-kubernetes-iossl-passthrough">`nginx.ingress.kubernetes.io/ssl-passthrough`</a> | Some differences in SNI/default backend handling. | | <a id="opt-nginx-ingress-kubernetes-iossl-passthrough" href="#opt-nginx-ingress-kubernetes-iossl-passthrough" title="#opt-nginx-ingress-kubernetes-iossl-passthrough">`nginx.ingress.kubernetes.io/ssl-passthrough`</a> | Some differences in SNI/default backend handling. |
@@ -276,6 +276,8 @@ The following annotations are organized by category for easier navigation.
| <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-name" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-name" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-name">`nginx.ingress.kubernetes.io/proxy-ssl-name`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-name" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-name" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-name">`nginx.ingress.kubernetes.io/proxy-ssl-name`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-verify" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-verify" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-verify">`nginx.ingress.kubernetes.io/proxy-ssl-verify`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-verify" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-verify" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-verify">`nginx.ingress.kubernetes.io/proxy-ssl-verify`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-secret" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-secret" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-secret">`nginx.ingress.kubernetes.io/proxy-ssl-secret`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-secret" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-secret" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-secret">`nginx.ingress.kubernetes.io/proxy-ssl-secret`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-secret" href="#opt-nginx-ingress-kubernetes-ioauth-tls-secret" title="#opt-nginx-ingress-kubernetes-ioauth-tls-secret">`nginx.ingress.kubernetes.io/auth-tls-secret`</a> | When validation fails, the rejection happens during the TLS handshake rather than returning a 400 Bad Request. |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-verify-client" href="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-client" title="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-client">`nginx.ingress.kubernetes.io/auth-tls-verify-client`</a> | When validation fails, the rejection happens during the TLS handshake rather than returning a 400 Bad Request. |
### Session Affinity ### Session Affinity
@@ -363,10 +365,8 @@ The following annotations are organized by category for easier navigation.
| Annotation | Notes | | Annotation | Notes |
|-----------------------------------------------------------------------------|------------------------------------------------------| |-----------------------------------------------------------------------------|------------------------------------------------------|
| <a id="opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior" href="#opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior" title="#opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior">`nginx.ingress.kubernetes.io/affinity-canary-behavior`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior" href="#opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior" title="#opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior">`nginx.ingress.kubernetes.io/affinity-canary-behavior`</a> | | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-secret" href="#opt-nginx-ingress-kubernetes-ioauth-tls-secret" title="#opt-nginx-ingress-kubernetes-ioauth-tls-secret">`nginx.ingress.kubernetes.io/auth-tls-secret`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-verify-depth" href="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-depth" title="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-depth">`nginx.ingress.kubernetes.io/auth-tls-verify-depth`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioauth-tls-verify-depth" href="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-depth" title="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-depth">`nginx.ingress.kubernetes.io/auth-tls-verify-depth`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-verify-client" href="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-client" title="#opt-nginx-ingress-kubernetes-ioauth-tls-verify-client">`nginx.ingress.kubernetes.io/auth-tls-verify-client`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-error-page" href="#opt-nginx-ingress-kubernetes-ioauth-tls-error-page" title="#opt-nginx-ingress-kubernetes-ioauth-tls-error-page">`nginx.ingress.kubernetes.io/auth-tls-error-page`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioauth-tls-error-page" href="#opt-nginx-ingress-kubernetes-ioauth-tls-error-page" title="#opt-nginx-ingress-kubernetes-ioauth-tls-error-page">`nginx.ingress.kubernetes.io/auth-tls-error-page`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-pass-certificate-to-upstream" href="#opt-nginx-ingress-kubernetes-ioauth-tls-pass-certificate-to-upstream" title="#opt-nginx-ingress-kubernetes-ioauth-tls-pass-certificate-to-upstream">`nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioauth-tls-pass-certificate-to-upstream" href="#opt-nginx-ingress-kubernetes-ioauth-tls-pass-certificate-to-upstream" title="#opt-nginx-ingress-kubernetes-ioauth-tls-pass-certificate-to-upstream">`nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-match-cn" href="#opt-nginx-ingress-kubernetes-ioauth-tls-match-cn" title="#opt-nginx-ingress-kubernetes-ioauth-tls-match-cn">`nginx.ingress.kubernetes.io/auth-tls-match-cn`</a> | | | <a id="opt-nginx-ingress-kubernetes-ioauth-tls-match-cn" href="#opt-nginx-ingress-kubernetes-ioauth-tls-match-cn" title="#opt-nginx-ingress-kubernetes-ioauth-tls-match-cn">`nginx.ingress.kubernetes.io/auth-tls-match-cn`</a> | |
@@ -19,6 +19,9 @@ type ingressConfig struct {
AuthSignin *string `annotation:"nginx.ingress.kubernetes.io/auth-signin"` AuthSignin *string `annotation:"nginx.ingress.kubernetes.io/auth-signin"`
AuthResponseHeaders *string `annotation:"nginx.ingress.kubernetes.io/auth-response-headers"` AuthResponseHeaders *string `annotation:"nginx.ingress.kubernetes.io/auth-response-headers"`
AuthTLSSecret *string `annotation:"nginx.ingress.kubernetes.io/auth-tls-secret"`
AuthTLSVerifyClient *string `annotation:"nginx.ingress.kubernetes.io/auth-tls-verify-client"`
ForceSSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/force-ssl-redirect"` ForceSSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/force-ssl-redirect"`
SSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/ssl-redirect"` SSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/ssl-redirect"`
@@ -0,0 +1,26 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-auth-tls-secret
namespace: default
annotations:
nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
spec:
ingressClassName: nginx
tls:
- hosts:
- auth-tls-secret.localhost
- secretName: whoami-tls
rules:
- host: auth-tls-secret.localhost
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: whoami
port:
number: 80
@@ -0,0 +1,27 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-auth-tls-verify-client
namespace: default
annotations:
nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
nginx.ingress.kubernetes.io/auth-tls-verify-client: "optional"
spec:
ingressClassName: nginx
tls:
- hosts:
- auth-tls-verify-client.localhost
- secretName: whoami-tls
rules:
- host: auth-tls-verify-client.localhost
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: whoami
port:
number: 80
@@ -7,3 +7,13 @@ metadata:
data: data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
tls.key: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t tls.key: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
---
kind: Secret
apiVersion: v1
metadata:
namespace: default
name: ca-secret
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
@@ -267,6 +267,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
ingresses := p.k8sClient.ListIngresses() ingresses := p.k8sClient.ListIngresses()
uniqCerts := make(map[string]*tls.CertAndStores) uniqCerts := make(map[string]*tls.CertAndStores)
tlsOptions := make(map[string]tls.Options)
for _, ingress := range ingresses { for _, ingress := range ingresses {
logger := log.Ctx(ctx).With().Str("ingress", ingress.Name).Str("namespace", ingress.Namespace).Logger() logger := log.Ctx(ctx).With().Str("ingress", ingress.Name).Str("namespace", ingress.Namespace).Logger()
ctxIngress := logger.WithContext(ctx) ctxIngress := logger.WithContext(ctx)
@@ -294,6 +295,23 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
} }
} }
var clientAuthTLSOptionName string
if ingressConfig.AuthTLSSecret != nil {
tlsOptName := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-" + *ingressConfig.AuthTLSSecret)
if _, exists := tlsOptions[tlsOptName]; !exists {
tlsOpt, err := p.buildClientAuthTLSOption(ingress.Namespace, ingressConfig)
if err != nil {
logger.Error().Err(err).Msg("Error configuring client auth TLS")
continue
}
tlsOptions[tlsOptName] = tlsOpt
}
clientAuthTLSOptionName = tlsOptName
}
namedServersTransport, err := p.buildServersTransport(ingress.Namespace, ingress.Name, ingressConfig) namedServersTransport, err := p.buildServersTransport(ingress.Namespace, ingress.Name, ingressConfig)
if err != nil { if err != nil {
logger.Error().Err(err).Msg("Ignoring Ingress cannot create proxy SSL configuration") logger.Error().Err(err).Msg("Ignoring Ingress cannot create proxy SSL configuration")
@@ -336,6 +354,9 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
Service: defaultBackendName, Service: defaultBackendName,
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
} }
if clientAuthTLSOptionName != "" {
rtTLS.TLS.Options = clientAuthTLSOptionName
}
if err := p.applyMiddlewares(ingress.Namespace, defaultBackendTLSName, "", ingressConfig, false, rtTLS, conf); err != nil { if err := p.applyMiddlewares(ingress.Namespace, defaultBackendTLSName, "", ingressConfig, false, rtTLS, conf); err != nil {
logger.Error().Err(err).Msg("Error applying middlewares") logger.Error().Err(err).Msg("Error applying middlewares")
@@ -427,6 +448,9 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
Service: key, Service: key,
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
} }
if clientAuthTLSOptionName != "" {
rtTLS.TLS.Options = clientAuthTLSOptionName
}
if err := p.applyMiddlewares(ingress.Namespace, key+"-tls", "", ingressConfig, false, rtTLS, conf); err != nil { if err := p.applyMiddlewares(ingress.Namespace, key+"-tls", "", ingressConfig, false, rtTLS, conf); err != nil {
logger.Error().Err(err).Msg("Error applying middlewares") logger.Error().Err(err).Msg("Error applying middlewares")
@@ -481,6 +505,10 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
} }
if hasTLS { if hasTLS {
rt.TLS = &dynamic.RouterTLSConfig{} rt.TLS = &dynamic.RouterTLSConfig{}
if clientAuthTLSOptionName != "" {
rt.TLS.Options = clientAuthTLSOptionName
}
} }
routerKey := provider.Normalize(fmt.Sprintf("%s-%s-rule-%d-path-%d", ingress.Namespace, ingress.Name, ri, pi)) routerKey := provider.Normalize(fmt.Sprintf("%s-%s-rule-%d-path-%d", ingress.Namespace, ingress.Name, ri, pi))
@@ -502,6 +530,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
conf.TLS = &dynamic.TLSConfiguration{ conf.TLS = &dynamic.TLSConfiguration{
Certificates: slices.Collect(maps.Values(uniqCerts)), Certificates: slices.Collect(maps.Values(uniqCerts)),
Options: tlsOptions,
} }
return conf return conf
@@ -1298,3 +1327,61 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
return eventsChanBuffered return eventsChanBuffered
} }
func (p *Provider) buildClientAuthTLSOption(ingressNamespace string, config ingressConfig) (tls.Options, error) {
secretParts := strings.SplitN(*config.AuthTLSSecret, "/", 2)
if len(secretParts) != 2 {
return tls.Options{}, errors.New("auth-tls-secret is not in a correct namespace/name format")
}
// Expected format: namespace/name.
secretNamespace := secretParts[0]
secretName := secretParts[1]
if secretNamespace == "" {
return tls.Options{}, errors.New("auth-tls-secret has empty namespace")
}
if secretName == "" {
return tls.Options{}, errors.New("auth-tls-secret has empty name")
}
// Cross-namespace secrets are not supported.
if secretNamespace != ingressNamespace {
return tls.Options{}, fmt.Errorf("cross-namespace auth-tls-secret is not supported: secret namespace %q does not match ingress namespace %q", secretNamespace, ingressNamespace)
}
blocks, err := p.certificateBlocks(secretNamespace, secretName)
if err != nil {
return tls.Options{}, fmt.Errorf("reading client certificate: %w", err)
}
if blocks.CA == nil {
return tls.Options{}, errors.New("secret does not contain a CA certificate")
}
// Default verifyClient value is "on" on ingress-nginx.
// on means that client certificate is required and must be signed by a trusted CA certificate.
clientAuthType := tls.RequireAndVerifyClientCert
if config.AuthTLSVerifyClient != nil {
switch *config.AuthTLSVerifyClient {
// off means that client certificate is not requested and no verification will be passed.
case "off":
clientAuthType = tls.NoClientCert
// optional means that the client certificate is requested, but not required.
// If the certificate is present, it needs to be verified.
case "optional":
clientAuthType = tls.VerifyClientCertIfGiven
// optional_no_ca means that the client certificate is requested, but does not require it to be signed by a trusted CA certificate.
case "optional_no_ca":
clientAuthType = tls.RequestClientCert
}
}
tlsOpt := tls.Options{}
tlsOpt.SetDefaults()
tlsOpt.ClientAuth = tls.ClientAuth{
CAFiles: []types.FileOrContent{*blocks.CA},
ClientAuthType: clientAuthType,
}
return tlsOpt, nil
}
@@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types" ptypes "github.com/traefik/paerser/types"
@@ -46,7 +47,9 @@ func TestLoadIngresses(t *testing.T) {
Services: map[string]*dynamic.Service{}, Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{}, ServersTransports: map[string]*dynamic.ServersTransport{},
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -105,7 +108,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -182,6 +187,7 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
Options: map[string]tls.Options{},
}, },
}, },
}, },
@@ -244,7 +250,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -305,7 +313,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -452,6 +462,7 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
Options: map[string]tls.Options{},
}, },
}, },
}, },
@@ -496,7 +507,9 @@ func TestLoadIngresses(t *testing.T) {
Services: map[string]*dynamic.Service{}, Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{}, ServersTransports: map[string]*dynamic.ServersTransport{},
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -561,7 +574,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -617,7 +632,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -681,7 +698,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -730,7 +749,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -789,7 +810,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -841,7 +864,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -901,7 +926,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -961,7 +988,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1013,7 +1042,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1066,7 +1097,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
ServersTransports: map[string]*dynamic.ServersTransport{}, ServersTransports: map[string]*dynamic.ServersTransport{},
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1125,7 +1158,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1184,7 +1219,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1243,7 +1280,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1296,7 +1335,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1357,7 +1398,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1418,7 +1461,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1479,7 +1524,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1540,7 +1587,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1601,7 +1650,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1662,7 +1713,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1723,7 +1776,9 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
}, },
}, },
{ {
@@ -1771,7 +1826,216 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}, },
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{},
},
},
},
{
desc: "Auth TLS secret",
paths: []string{
"services.yml",
"secrets.yml",
"ingressclasses.yml",
"ingresses/20-ingress-with-auth-tls-secret.yml",
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-ingress-with-auth-tls-secret-rule-0-path-0": {
Rule: "Host(`auth-tls-secret.localhost`) && Path(`/`)",
RuleSyntax: "default",
Service: "default-ingress-with-auth-tls-secret-whoami-80",
TLS: &dynamic.RouterTLSConfig{
Options: "default-ingress-with-auth-tls-secret-default-ca-secret",
},
},
"default-ingress-with-auth-tls-secret-rule-0-path-0-http": {
EntryPoints: []string{"web"},
Rule: "Host(`auth-tls-secret.localhost`) && Path(`/`)",
RuleSyntax: "default",
Middlewares: []string{"default-ingress-with-auth-tls-secret-rule-0-path-0-redirect-scheme"},
Service: "noop@internal",
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-ingress-with-auth-tls-secret-rule-0-path-0-redirect-scheme": {
RedirectScheme: &dynamic.RedirectScheme{
Scheme: "https",
ForcePermanentRedirect: true,
},
},
},
Services: map[string]*dynamic.Service{
"default-ingress-with-auth-tls-secret-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
Strategy: "wrr",
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: dynamic.DefaultFlushInterval,
},
ServersTransport: "default-ingress-with-auth-tls-secret",
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{
"default-ingress-with-auth-tls-secret": {
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
DialTimeout: ptypes.Duration(60 * time.Second),
},
},
},
},
TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{
{
Certificate: tls.Certificate{
CertFile: "-----BEGIN CERTIFICATE-----",
KeyFile: "-----BEGIN CERTIFICATE-----",
},
},
},
Options: map[string]tls.Options{
"default-ingress-with-auth-tls-secret-default-ca-secret": {
ClientAuth: tls.ClientAuth{
CAFiles: []types.FileOrContent{"-----BEGIN CERTIFICATE-----"},
ClientAuthType: "RequireAndVerifyClientCert",
},
CipherSuites: []string{
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
},
ALPNProtocols: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
},
},
},
},
},
{
desc: "Auth TLS verify client",
paths: []string{
"services.yml",
"secrets.yml",
"ingressclasses.yml",
"ingresses/21-ingress-with-auth-tls-verify-client.yml",
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-ingress-with-auth-tls-verify-client-rule-0-path-0": {
Rule: "Host(`auth-tls-verify-client.localhost`) && Path(`/`)",
RuleSyntax: "default",
Service: "default-ingress-with-auth-tls-verify-client-whoami-80",
TLS: &dynamic.RouterTLSConfig{
Options: "default-ingress-with-auth-tls-verify-client-default-ca-secret",
},
},
"default-ingress-with-auth-tls-verify-client-rule-0-path-0-http": {
EntryPoints: []string{"web"},
Rule: "Host(`auth-tls-verify-client.localhost`) && Path(`/`)",
RuleSyntax: "default",
Middlewares: []string{"default-ingress-with-auth-tls-verify-client-rule-0-path-0-redirect-scheme"},
Service: "noop@internal",
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-ingress-with-auth-tls-verify-client-rule-0-path-0-redirect-scheme": {
RedirectScheme: &dynamic.RedirectScheme{
Scheme: "https",
ForcePermanentRedirect: true,
},
},
},
Services: map[string]*dynamic.Service{
"default-ingress-with-auth-tls-verify-client-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
Strategy: "wrr",
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: dynamic.DefaultFlushInterval,
},
ServersTransport: "default-ingress-with-auth-tls-verify-client",
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{
"default-ingress-with-auth-tls-verify-client": {
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
DialTimeout: ptypes.Duration(60 * time.Second),
},
},
},
},
TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{
{
Certificate: tls.Certificate{
CertFile: "-----BEGIN CERTIFICATE-----",
KeyFile: "-----BEGIN CERTIFICATE-----",
},
},
},
Options: map[string]tls.Options{
"default-ingress-with-auth-tls-verify-client-default-ca-secret": {
ClientAuth: tls.ClientAuth{
CAFiles: []types.FileOrContent{"-----BEGIN CERTIFICATE-----"},
ClientAuthType: "VerifyClientCertIfGiven",
},
CipherSuites: []string{
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
},
ALPNProtocols: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
},
},
},
}, },
}, },
} }
+24
View File
@@ -4,6 +4,30 @@ import "github.com/traefik/traefik/v3/pkg/types"
const certificateHeader = "-----BEGIN CERTIFICATE-----\n" const certificateHeader = "-----BEGIN CERTIFICATE-----\n"
const (
// NoClientCert indicates that no client certificate should be requested
// during the handshake, and if any certificates are sent they will not
// be verified.
NoClientCert = "NoClientCert"
// RequestClientCert indicates that a client certificate should be requested
// during the handshake, but does not require that the client send any
// certificates.
RequestClientCert = "RequestClientCert"
// RequireAnyClientCert indicates that a client certificate should be requested
// during the handshake, and that at least one certificate is required to be
// sent by the client, but that certificate is not required to be valid.
RequireAnyClientCert = "RequireAnyClientCert"
// VerifyClientCertIfGiven indicates that a client certificate should be requested
// during the handshake, but does not require that the client sends a
// certificate. If the client does send a certificate it is required to be
// valid.
VerifyClientCertIfGiven = "VerifyClientCertIfGiven"
// RequireAndVerifyClientCert indicates that a client certificate should be requested
// during the handshake, and that at least one valid certificate is required
// to be sent by the client.
RequireAndVerifyClientCert = "RequireAndVerifyClientCert"
)
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// ClientAuth defines the parameters of the client authentication part of the TLS connection, if any. // ClientAuth defines the parameters of the client authentication part of the TLS connection, if any.
+5 -5
View File
@@ -460,15 +460,15 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) {
} }
switch clientAuthType { switch clientAuthType {
case "NoClientCert": case NoClientCert:
conf.ClientAuth = tls.NoClientCert conf.ClientAuth = tls.NoClientCert
case "RequestClientCert": case RequestClientCert:
conf.ClientAuth = tls.RequestClientCert conf.ClientAuth = tls.RequestClientCert
case "RequireAnyClientCert": case RequireAnyClientCert:
conf.ClientAuth = tls.RequireAnyClientCert conf.ClientAuth = tls.RequireAnyClientCert
case "VerifyClientCertIfGiven": case VerifyClientCertIfGiven:
conf.ClientAuth = tls.VerifyClientCertIfGiven conf.ClientAuth = tls.VerifyClientCertIfGiven
case "RequireAndVerifyClientCert": case RequireAndVerifyClientCert:
conf.ClientAuth = tls.RequireAndVerifyClientCert conf.ClientAuth = tls.RequireAndVerifyClientCert
default: default:
return nil, fmt.Errorf("unknown client auth type %q", clientAuthType) return nil, fmt.Errorf("unknown client auth type %q", clientAuthType)