mirror of
https://github.com/traefik/traefik
synced 2026-02-03 08:50:32 +00:00
Support auth-tls-secret and auth-tls-verify-client annotations
This commit is contained in:
@@ -268,7 +268,7 @@ The following annotations are organized by category for easier navigation.
|
||||
### SSL/TLS
|
||||
|
||||
| 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-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. |
|
||||
@@ -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-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-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
|
||||
|
||||
@@ -363,10 +365,8 @@ The following annotations are organized by category for easier navigation.
|
||||
|
||||
| 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-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-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-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-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> | |
|
||||
|
||||
@@ -19,6 +19,9 @@ type ingressConfig struct {
|
||||
AuthSignin *string `annotation:"nginx.ingress.kubernetes.io/auth-signin"`
|
||||
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"`
|
||||
SSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/ssl-redirect"`
|
||||
|
||||
|
||||
+26
@@ -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
|
||||
+27
@@ -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:
|
||||
tls.crt: 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()
|
||||
|
||||
uniqCerts := make(map[string]*tls.CertAndStores)
|
||||
tlsOptions := make(map[string]tls.Options)
|
||||
for _, ingress := range ingresses {
|
||||
logger := log.Ctx(ctx).With().Str("ingress", ingress.Name).Str("namespace", ingress.Namespace).Logger()
|
||||
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)
|
||||
if err != nil {
|
||||
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,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
}
|
||||
if clientAuthTLSOptionName != "" {
|
||||
rtTLS.TLS.Options = clientAuthTLSOptionName
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, defaultBackendTLSName, "", ingressConfig, false, rtTLS, conf); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
@@ -427,6 +448,9 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
Service: key,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
}
|
||||
if clientAuthTLSOptionName != "" {
|
||||
rtTLS.TLS.Options = clientAuthTLSOptionName
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, key+"-tls", "", ingressConfig, false, rtTLS, conf); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
@@ -481,6 +505,10 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
}
|
||||
if hasTLS {
|
||||
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))
|
||||
@@ -502,6 +530,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
|
||||
conf.TLS = &dynamic.TLSConfiguration{
|
||||
Certificates: slices.Collect(maps.Values(uniqCerts)),
|
||||
Options: tlsOptions,
|
||||
}
|
||||
|
||||
return conf
|
||||
@@ -1298,3 +1327,61 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
||||
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
@@ -46,7 +47,9 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Services: map[string]*dynamic.Service{},
|
||||
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{},
|
||||
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{},
|
||||
},
|
||||
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},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,6 +4,30 @@ import "github.com/traefik/traefik/v3/pkg/types"
|
||||
|
||||
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
|
||||
|
||||
// ClientAuth defines the parameters of the client authentication part of the TLS connection, if any.
|
||||
|
||||
@@ -460,15 +460,15 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) {
|
||||
}
|
||||
|
||||
switch clientAuthType {
|
||||
case "NoClientCert":
|
||||
case NoClientCert:
|
||||
conf.ClientAuth = tls.NoClientCert
|
||||
case "RequestClientCert":
|
||||
case RequestClientCert:
|
||||
conf.ClientAuth = tls.RequestClientCert
|
||||
case "RequireAnyClientCert":
|
||||
case RequireAnyClientCert:
|
||||
conf.ClientAuth = tls.RequireAnyClientCert
|
||||
case "VerifyClientCertIfGiven":
|
||||
case VerifyClientCertIfGiven:
|
||||
conf.ClientAuth = tls.VerifyClientCertIfGiven
|
||||
case "RequireAndVerifyClientCert":
|
||||
case RequireAndVerifyClientCert:
|
||||
conf.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown client auth type %q", clientAuthType)
|
||||
|
||||
Reference in New Issue
Block a user