Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrates the pass client tls cert middleware #4373

Merged
merged 1 commit into from
Jan 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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