Allow specifying the name of the header for forwarding client certificates

This commit is contained in:
Antonio Huete Jimenez 2023-09-01 20:25:12 +02:00
parent a687343fed
commit affd8733a0
No known key found for this signature in database
GPG key ID: EB139A8DBD9B3797
3 changed files with 60 additions and 13 deletions

View file

@ -37,6 +37,7 @@ const (
annotationAuthTLSVerifyDepth = "auth-tls-verify-depth"
annotationAuthTLSErrorPage = "auth-tls-error-page"
annotationAuthTLSPassCertToUpstream = "auth-tls-pass-certificate-to-upstream" //#nosec G101
annotationAuthTLSPassCertToUpstreamHeader = "auth-tls-pass-certificate-to-upstream-header" //#nosec G101
annotationAuthTLSMatchCN = "auth-tls-match-cn"
)
@ -80,6 +81,12 @@ var authTLSAnnotations = parser.Annotation{
Risk: parser.AnnotationRiskLow,
Documentation: `This annotation defines if the received certificates should be passed or not to the upstream server in the header "ssl-client-cert"`,
},
annotationAuthTLSPassCertToUpstreamHeader: {
Validator: parser.ValidateNull,
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow,
Documentation: `This annotation defines the header name in which the client certificate will be passed to the upstream server`,
},
annotationAuthTLSMatchCN: {
Validator: parser.ValidateRegex(commonNameRegex, true),
Scope: parser.AnnotationScopeLocation,
@ -97,6 +104,7 @@ type Config struct {
ValidationDepth int `json:"validationDepth"`
ErrorPage string `json:"errorPage"`
PassCertToUpstream bool `json:"passCertToUpstream"`
PassCertToUpstreamHeader string `json:"passCertToUpstreamHeader"`
MatchCN string `json:"matchCN"`
AuthTLSError string
}
@ -125,6 +133,10 @@ func (assl1 *Config) Equal(assl2 *Config) bool {
return false
}
if assl1.PassCertToUpstreamHeader != assl2.PassCertToUpstreamHeader {
return false
}
return true
}
@ -200,6 +212,14 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
config.PassCertToUpstream = false
}
config.PassCertToUpstreamHeader, err = parser.GetStringAnnotation(annotationAuthTLSPassCertToUpstreamHeader, ing, a.annotationConfig.Annotations)
if err != nil {
if ing_errors.IsValidationError(err) {
return &Config{}, err
}
config.PassCertToUpstreamHeader = "ssl-client-cert"
}
config.MatchCN, err = parser.GetStringAnnotation(annotationAuthTLSMatchCN, ing, a.annotationConfig.Annotations)
if err != nil {
if ing_errors.IsValidationError(err) {

View file

@ -132,6 +132,9 @@ func TestAnnotations(t *testing.T) {
if u.PassCertToUpstream != false {
t.Errorf("expected %v but got %v", false, u.PassCertToUpstream)
}
if u.PassCertToUpstreamHeader != "ssl-client-cert" {
t.Errorf("expected %v but got %v", "ssl-client-cert", u.PassCertToUpstreamHeader)
}
if u.MatchCN != "" {
t.Errorf("expected empty string, but got %v", u.MatchCN)
}
@ -140,6 +143,7 @@ func TestAnnotations(t *testing.T) {
data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyDepth)] = "2"
data[parser.GetAnnotationWithPrefix(annotationAuthTLSErrorPage)] = "ok.com/error"
data[parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstream)] = "true"
data[parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstreamHeader)] = "X-SSL-CERT"
data[parser.GetAnnotationWithPrefix(annotationAuthTLSMatchCN)] = "CN=(hello-app|ok|goodbye)"
ing.SetAnnotations(data)
@ -169,6 +173,9 @@ func TestAnnotations(t *testing.T) {
if u.PassCertToUpstream != true {
t.Errorf("expected %v but got %v", true, u.PassCertToUpstream)
}
if u.PassCertToUpstreamHeader != "X-SSL-CERT" {
t.Errorf("expected %v but got %v", "X-SSL-CERT", u.PassCertToUpstreamHeader)
}
if u.MatchCN != "CN=(hello-app|ok|goodbye)" {
t.Errorf("expected %v but got %v", "CN=(hello-app|ok|goodbye)", u.MatchCN)
}
@ -235,6 +242,14 @@ func TestInvalidAnnotations(t *testing.T) {
}
delete(data, parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstream))
data[parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstreamHeader)] = 1
ing.SetAnnotations(data)
_, err = NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
delete(data, parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstreamHeader))
data[parser.GetAnnotationWithPrefix(annotationAuthTLSMatchCN)] = "<script>nope</script>"
ing.SetAnnotations(data)
_, err = NewParser(fakeSecret).Parse(ing)
@ -263,6 +278,9 @@ func TestInvalidAnnotations(t *testing.T) {
if u.PassCertToUpstream != false {
t.Errorf("expected %v but got %v", false, u.PassCertToUpstream)
}
if u.PassCertToUpstreamHeader != "ssl-client-cert" {
t.Errorf("expected %v but got %v", "ssl-client-cert", u.PassCertToUpstreamHeader)
}
if u.MatchCN != "" {
t.Errorf("expected empty string but got %v", u.MatchCN)
}
@ -333,6 +351,15 @@ func TestEquals(t *testing.T) {
}
cfg2.PassCertToUpstream = true
// Different Pass to Upstream Header
cfg1.PassCertToUpstreamHeader = "ssl-client-cert"
cfg2.PassCertToUpstreamHeader = "X-SSL-CERT"
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.PassCertToUpstreamHeader = "ssl-client-cert"
// Equal Configs
result = cfg1.Equal(cfg2)
if result != true {

View file

@ -1182,7 +1182,7 @@ stream {
# Pass the extracted client certificate to the auth provider
{{ if not (empty $server.CertificateAuth.CAFileName) }}
{{ if $server.CertificateAuth.PassCertToUpstream }}
proxy_set_header ssl-client-cert $ssl_client_escaped_cert;
proxy_set_header {{ $server.CertificateAuth.PassCertToUpstreamHeader }} $ssl_client_escaped_cert;
{{ end }}
proxy_set_header ssl-client-verify $ssl_client_verify;
proxy_set_header ssl-client-subject-dn $ssl_client_s_dn;