Skip to content

Commit

Permalink
Migrates the pass client tls cert middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
jbdoumenjou authored and traefiker committed Jan 9, 2019
1 parent 0b43656 commit 7efafa5
Show file tree
Hide file tree
Showing 4 changed files with 665 additions and 465 deletions.
35 changes: 19 additions & 16 deletions config/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ type MaxConn struct {

// PassTLSClientCert holds the TLS client cert headers configuration.
type PassTLSClientCert struct {
PEM bool `description:"Enable header with escaped client pem" json:"pem"`
Infos *TLSClientCertificateInfos `description:"Enable header with configured client cert infos" json:"infos,omitempty"`
PEM bool `description:"Enable header with escaped client pem" json:"pem"`
Info *TLSClientCertificateInfo `description:"Enable header with configured client cert info" json:"info,omitempty"`
}

// Rate holds the rate limiting configuration for a specific time period.
Expand Down Expand Up @@ -252,22 +252,25 @@ type StripPrefixRegex struct {
Regex []string `json:"regex,omitempty"`
}

// TLSClientCertificateInfos holds the client TLS certificate infos configuration.
type TLSClientCertificateInfos struct {
NotAfter bool `description:"Add NotAfter info in header" json:"notAfter"`
NotBefore bool `description:"Add NotBefore info in header" json:"notBefore"`
Subject *TLSCLientCertificateSubjectInfos `description:"Add Subject info in header" json:"subject,omitempty"`
Sans bool `description:"Add Sans info in header" json:"sans"`
// TLSClientCertificateInfo holds the client TLS certificate info configuration.
type TLSClientCertificateInfo struct {
NotAfter bool `description:"Add NotAfter info in header" json:"notAfter"`
NotBefore bool `description:"Add NotBefore info in header" json:"notBefore"`
Sans bool `description:"Add Sans info in header" json:"sans"`
Subject *TLSCLientCertificateDNInfo `description:"Add Subject info in header" json:"subject,omitempty"`
Issuer *TLSCLientCertificateDNInfo `description:"Add Issuer info in header" json:"issuer,omitempty"`
}

// TLSCLientCertificateSubjectInfos holds the client TLS certificate subject infos configuration.
type TLSCLientCertificateSubjectInfos struct {
Country bool `description:"Add Country info in header" json:"country"`
Province bool `description:"Add Province info in header" json:"province"`
Locality bool `description:"Add Locality info in header" json:"locality"`
Organization bool `description:"Add Organization info in header" json:"organization"`
CommonName bool `description:"Add CommonName info in header" json:"commonName"`
SerialNumber bool `description:"Add SerialNumber info in header" json:"serialNumber"`
// TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration
// cf https://tools.ietf.org/html/rfc3739
type TLSCLientCertificateDNInfo struct {
Country bool `description:"Add Country info in header" json:"country"`
Province bool `description:"Add Province info in header" json:"province"`
Locality bool `description:"Add Locality info in header" json:"locality"`
Organization bool `description:"Add Organization info in header" json:"organization"`
CommonName bool `description:"Add CommonName info in header" json:"commonName"`
SerialNumber bool `description:"Add SerialNumber info in header" json:"serialNumber"`
DomainComponent bool `description:"Add Domain Component info in header" json:"domainComponent"`
}

// Users holds a list of users
Expand Down
195 changes: 115 additions & 80 deletions middlewares/passtlsclientcert/pass_tls_client_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,74 +18,82 @@ import (
)

const (
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
xForwardedTLSClientCertInfos = "X-Forwarded-Tls-Client-Cert-infos"
typeName = "PassClientTLSCert"
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-info"
typeName = "PassClientTLSCert"
)

var attributeTypeNames = map[string]string{
"0.9.2342.19200300.100.1.25": "DC", // Domain component OID - RFC 2247
}

// DistinguishedNameOptions is a struct for specifying the configuration for the distinguished name info.
type DistinguishedNameOptions struct {
CommonName bool
CountryName bool
DomainComponent bool
LocalityName bool
OrganizationName bool
SerialNumber bool
StateOrProvinceName bool
}

func newDistinguishedNameOptions(info *config.TLSCLientCertificateDNInfo) *DistinguishedNameOptions {
if info == nil {
return nil
}

return &DistinguishedNameOptions{
CommonName: info.CommonName,
CountryName: info.Country,
DomainComponent: info.DomainComponent,
LocalityName: info.Locality,
OrganizationName: info.Organization,
SerialNumber: info.SerialNumber,
StateOrProvinceName: info.Province,
}
}

// passTLSClientCert is a middleware that helps setup a few tls info features.
type passTLSClientCert struct {
next http.Handler
name string
pem bool // pass the sanitized pem to the backend in a specific header
infos *tlsClientCertificateInfos // pass selected information from the client certificate
next http.Handler
name string
pem bool // pass the sanitized pem to the backend in a specific header
info *tlsClientCertificateInfo // pass selected information from the client certificate
}

// New constructs a new PassTLSClientCert instance from supplied frontend header struct.
func New(ctx context.Context, next http.Handler, config config.PassTLSClientCert, name string) (http.Handler, error) {
middlewares.GetLogger(ctx, name, typeName).Debug("Creating middleware")

return &passTLSClientCert{
next: next,
name: name,
pem: config.PEM,
infos: newTLSClientInfos(config.Infos),
next: next,
name: name,
pem: config.PEM,
info: newTLSClientInfo(config.Info),
}, nil
}

// tlsClientCertificateInfos is a struct for specifying the configuration for the passTLSClientCert middleware.
type tlsClientCertificateInfos struct {
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
type tlsClientCertificateInfo struct {
notAfter bool
notBefore bool
subject *tlsCLientCertificateSubjectInfos
sans bool
subject *DistinguishedNameOptions
issuer *DistinguishedNameOptions
}

func newTLSClientInfos(infos *config.TLSClientCertificateInfos) *tlsClientCertificateInfos {
if infos == nil {
return nil
}

return &tlsClientCertificateInfos{
notBefore: infos.NotBefore,
notAfter: infos.NotAfter,
sans: infos.Sans,
subject: newTLSCLientCertificateSubjectInfos(infos.Subject),
}
}

// tlsCLientCertificateSubjectInfos contains the configuration for the certificate subject infos.
type tlsCLientCertificateSubjectInfos struct {
country bool
province bool
locality bool
Organization bool
commonName bool
serialNumber bool
}

func newTLSCLientCertificateSubjectInfos(infos *config.TLSCLientCertificateSubjectInfos) *tlsCLientCertificateSubjectInfos {
if infos == nil {
func newTLSClientInfo(info *config.TLSClientCertificateInfo) *tlsClientCertificateInfo {
if info == nil {
return nil
}

return &tlsCLientCertificateSubjectInfos{
serialNumber: infos.SerialNumber,
commonName: infos.CommonName,
country: infos.Country,
locality: infos.Locality,
Organization: infos.Organization,
province: infos.Province,
return &tlsClientCertificateInfo{
issuer: newDistinguishedNameOptions(info.Issuer),
notAfter: info.NotAfter,
notBefore: info.NotBefore,
subject: newDistinguishedNameOptions(info.Subject),
sans: info.Sans,
}
}

Expand All @@ -98,47 +106,67 @@ func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request)
p.modifyRequestHeaders(logger, req)
p.next.ServeHTTP(rw, req)
}
func getDNInfo(prefix string, options *DistinguishedNameOptions, cs *pkix.Name) string {
if options == nil {
return ""
}

// getSubjectInfos extract the requested information from the certificate subject.
func (p *passTLSClientCert) getSubjectInfos(cs *pkix.Name) string {
var subject string
content := &strings.Builder{}

if p.infos != nil && p.infos.subject != nil {
options := p.infos.subject
// Manage non standard attributes
for _, name := range cs.Names {
// Domain Component - RFC 2247
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
content.WriteString(fmt.Sprintf("DC=%s,", name.Value))
}
}

var content []string
if options.CountryName {
writeParts(content, cs.Country, "C")
}

if options.country && len(cs.Country) > 0 {
content = append(content, fmt.Sprintf("C=%s", cs.Country[0]))
}
if options.StateOrProvinceName {
writeParts(content, cs.Province, "ST")
}

if options.province && len(cs.Province) > 0 {
content = append(content, fmt.Sprintf("ST=%s", cs.Province[0]))
}
if options.LocalityName {
writeParts(content, cs.Locality, "L")
}

if options.locality && len(cs.Locality) > 0 {
content = append(content, fmt.Sprintf("L=%s", cs.Locality[0]))
}
if options.OrganizationName {
writeParts(content, cs.Organization, "O")
}

if options.Organization && len(cs.Organization) > 0 {
content = append(content, fmt.Sprintf("O=%s", cs.Organization[0]))
}
if options.SerialNumber {
writePart(content, cs.SerialNumber, "SN")
}

if options.commonName && len(cs.CommonName) > 0 {
content = append(content, fmt.Sprintf("CN=%s", cs.CommonName))
}
if options.CommonName {
writePart(content, cs.CommonName, "CN")
}

if len(content) > 0 {
subject = `Subject="` + strings.Join(content, ",") + `"`
}
if content.Len() > 0 {
return prefix + `="` + strings.TrimSuffix(content.String(), ",") + `"`
}

return ""
}

func writeParts(content *strings.Builder, entries []string, prefix string) {
for _, entry := range entries {
writePart(content, entry, prefix)
}
}

return subject
func writePart(content *strings.Builder, entry string, prefix string) {
if len(entry) > 0 {
content.WriteString(fmt.Sprintf("%s=%s,", prefix, entry))
}
}

// getXForwardedTLSClientCertInfos Build a string with the wanted client certificates information
// getXForwardedTLSClientCertInfo Build a string with the wanted client certificates information
// like Subject="C=%s,ST=%s,L=%s,O=%s,CN=%s",NB=%d,NA=%d,SAN=%s;
func (p *passTLSClientCert) getXForwardedTLSClientCertInfos(certs []*x509.Certificate) string {
func (p *passTLSClientCert) getXForwardedTLSClientCertInfo(certs []*x509.Certificate) string {
var headerValues []string

for _, peerCert := range certs {
Expand All @@ -147,12 +175,19 @@ func (p *passTLSClientCert) getXForwardedTLSClientCertInfos(certs []*x509.Certif
var nb string
var na string

subject := p.getSubjectInfos(&peerCert.Subject)
if len(subject) > 0 {
values = append(values, subject)
if p.info != nil {
subject := getDNInfo("Subject", p.info.subject, &peerCert.Subject)
if len(subject) > 0 {
values = append(values, subject)
}

issuer := getDNInfo("Issuer", p.info.issuer, &peerCert.Issuer)
if len(issuer) > 0 {
values = append(values, issuer)
}
}

ci := p.infos
ci := p.info
if ci != nil {
if ci.notBefore {
nb = fmt.Sprintf("NB=%d", uint64(peerCert.NotBefore.Unix()))
Expand Down Expand Up @@ -186,10 +221,10 @@ func (p *passTLSClientCert) modifyRequestHeaders(logger logrus.FieldLogger, r *h
}
}

if p.infos != nil {
if p.info != nil {
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
headerContent := p.getXForwardedTLSClientCertInfos(r.TLS.PeerCertificates)
r.Header.Set(xForwardedTLSClientCertInfos, url.QueryEscape(headerContent))
headerContent := p.getXForwardedTLSClientCertInfo(r.TLS.PeerCertificates)
r.Header.Set(xForwardedTLSClientCertInfo, url.QueryEscape(headerContent))
} else {
logger.Warn("Try to extract certificate on a request without TLS")
}
Expand Down
Loading

0 comments on commit 7efafa5

Please sign in to comment.