From 5f50d2e23020fca69df4fe739bf6ee89ed591a2e Mon Sep 17 00:00:00 2001 From: David Date: Thu, 12 Dec 2019 00:32:03 +0100 Subject: [PATCH] Add serial number certificate to forward headers --- docs/content/middlewares/passtlsclientcert.md | 3 +- .../reference/dynamic-configuration/file.toml | 1 + .../reference/dynamic-configuration/file.yaml | 1 + .../reference/dynamic-configuration/kv-ref.md | 1 + pkg/config/dynamic/middlewares.go | 11 +++---- pkg/config/label/label_test.go | 12 +++++--- .../passtlsclientcert/pass_tls_client_cert.go | 29 ++++++++++++------- .../pass_tls_client_cert_test.go | 23 +++++++++------ 8 files changed, 52 insertions(+), 29 deletions(-) diff --git a/docs/content/middlewares/passtlsclientcert.md b/docs/content/middlewares/passtlsclientcert.md index b483adced..0743ca2d8 100644 --- a/docs/content/middlewares/passtlsclientcert.md +++ b/docs/content/middlewares/passtlsclientcert.md @@ -70,6 +70,7 @@ http: - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.notafter=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.notbefore=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.sans=true" + - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.serialnumber=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.commonname=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.country=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.domaincomponent=true" @@ -482,7 +483,7 @@ SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0 !!! info "multiple values" All the SANs data are separated by a `,`. - + #### `info.subject` The `info.subject` select the specific client certificate subject details you want to add to the `X-Forwarded-Tls-Client-Cert-Info` header. diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 5b6889254..702524d82 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -196,6 +196,7 @@ notAfter = true notBefore = true sans = true + serialNumber = true [http.middlewares.Middleware12.passTLSClientCert.info.subject] country = true province = true diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index d6e091940..6972ea4f4 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -239,6 +239,7 @@ http: commonName: true serialNumber: true domainComponent: true + serialNumber: true Middleware13: rateLimit: average: 42 diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index 5fae2e8a3..8b14ea982 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -92,6 +92,7 @@ | `traefik/http/middlewares/Middleware12/passTLSClientCert/info/notAfter` | `true` | | `traefik/http/middlewares/Middleware12/passTLSClientCert/info/notBefore` | `true` | | `traefik/http/middlewares/Middleware12/passTLSClientCert/info/sans` | `true` | +| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/serialNumber` | `true` | | `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/commonName` | `true` | | `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/country` | `true` | | `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/domainComponent` | `true` | diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index b729ca563..be2469d9c 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -377,11 +377,12 @@ type StripPrefixRegex struct { // TLSClientCertificateInfo holds the client TLS certificate info configuration. type TLSClientCertificateInfo struct { - NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty"` - NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty"` - Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"` - Subject *TLSCLientCertificateDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty"` - Issuer *TLSCLientCertificateDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty"` + NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty"` + NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty"` + Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"` + Subject *TLSCLientCertificateDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty"` + Issuer *TLSCLientCertificateDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty"` + SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index fbfece11e..1dbdf58c0 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -84,6 +84,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.middlewares.Middleware11.passtlsclientcert.info.notafter": "true", "traefik.http.middlewares.Middleware11.passtlsclientcert.info.notbefore": "true", "traefik.http.middlewares.Middleware11.passtlsclientcert.info.sans": "true", + "traefik.http.middlewares.Middleware11.passTLSClientCert.info.serialNumber": "true", "traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.commonname": "true", "traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.country": "true", "traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.domaincomponent": "true", @@ -294,8 +295,9 @@ func TestDecodeConfiguration(t *testing.T) { PassTLSClientCert: &dynamic.PassTLSClientCert{ PEM: true, Info: &dynamic.TLSClientCertificateInfo{ - NotAfter: true, - NotBefore: true, + NotAfter: true, + NotBefore: true, + SerialNumber: true, Subject: &dynamic.TLSCLientCertificateDNInfo{ Country: true, Province: true, @@ -699,8 +701,9 @@ func TestEncodeConfiguration(t *testing.T) { PassTLSClientCert: &dynamic.PassTLSClientCert{ PEM: true, Info: &dynamic.TLSClientCertificateInfo{ - NotAfter: true, - NotBefore: true, + NotAfter: true, + NotBefore: true, + SerialNumber: true, Subject: &dynamic.TLSCLientCertificateDNInfo{ Country: true, Province: true, @@ -1061,6 +1064,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotAfter": "true", "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotBefore": "true", "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Sans": "true", + "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.SerialNumber": "true", "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Country": "true", "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Province": "true", "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Locality": "true", diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go index 2b5e29dd2..e4304ec19 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go @@ -64,11 +64,12 @@ func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *Dist // tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware. type tlsClientCertificateInfo struct { - notAfter bool - notBefore bool - sans bool - subject *DistinguishedNameOptions - issuer *DistinguishedNameOptions + notAfter bool + notBefore bool + sans bool + subject *DistinguishedNameOptions + issuer *DistinguishedNameOptions + serialNumber bool } func newTLSClientCertificateInfo(info *dynamic.TLSClientCertificateInfo) *tlsClientCertificateInfo { @@ -77,11 +78,12 @@ func newTLSClientCertificateInfo(info *dynamic.TLSClientCertificateInfo) *tlsCli } return &tlsClientCertificateInfo{ - issuer: newDistinguishedNameOptions(info.Issuer), - notAfter: info.NotAfter, - notBefore: info.NotBefore, - subject: newDistinguishedNameOptions(info.Subject), - sans: info.Sans, + issuer: newDistinguishedNameOptions(info.Issuer), + notAfter: info.NotAfter, + notBefore: info.NotBefore, + subject: newDistinguishedNameOptions(info.Subject), + serialNumber: info.SerialNumber, + sans: info.Sans, } } @@ -155,6 +157,13 @@ func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certi values = append(values, fmt.Sprintf(`Issuer="%s"`, strings.TrimSuffix(issuer, subFieldSeparator))) } + if p.info.serialNumber && peerCert.SerialNumber != nil { + sn := peerCert.SerialNumber.String() + if sn != "" { + values = append(values, fmt.Sprintf(`SerialNumber="%s"`, strings.TrimSuffix(sn, subFieldSeparator))) + } + } + if p.info.notBefore { values = append(values, fmt.Sprintf(`NB="%d"`, uint64(peerCert.NotBefore.Unix()))) } diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index 49497080e..2b0328cfd 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go @@ -345,6 +345,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { minimalCheeseCertAllInfo := strings.Join([]string{ `Subject="C=FR,ST=Some-State,O=Cheese"`, `Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`, + `SerialNumber="481535886039632329873080491016862977516759989652"`, `NB="1544094636"`, `NA="1632568236"`, }, fieldSeparator) @@ -352,6 +353,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { completeCertAllInfo := strings.Join([]string{ `Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com"`, `Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`, + `SerialNumber="1"`, `NB="1544094616"`, `NA="1607166616"`, `SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"`, @@ -399,9 +401,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { certContents: []string{minimalCheeseCrt}, config: dynamic.PassTLSClientCert{ Info: &dynamic.TLSClientCertificateInfo{ - NotAfter: true, - NotBefore: true, - Sans: true, + NotAfter: true, + NotBefore: true, + Sans: true, + SerialNumber: true, Subject: &dynamic.TLSCLientCertificateDNInfo{ CommonName: true, Country: true, @@ -446,9 +449,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { certContents: []string{completeCheeseCrt}, config: dynamic.PassTLSClientCert{ Info: &dynamic.TLSClientCertificateInfo{ - NotAfter: true, - NotBefore: true, - Sans: true, + NotAfter: true, + NotBefore: true, + Sans: true, + SerialNumber: true, Subject: &dynamic.TLSCLientCertificateDNInfo{ Country: true, Province: true, @@ -476,9 +480,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { certContents: []string{minimalCheeseCrt, completeCheeseCrt}, config: dynamic.PassTLSClientCert{ Info: &dynamic.TLSClientCertificateInfo{ - NotAfter: true, - NotBefore: true, - Sans: true, + NotAfter: true, + NotBefore: true, + Sans: true, + SerialNumber: true, Subject: &dynamic.TLSCLientCertificateDNInfo{ Country: true, Province: true,