1
0
Fork 0

Migrates the pass client tls cert middleware

This commit is contained in:
Jean-Baptiste Doumenjou 2019-01-09 11:28:04 +01:00 committed by Traefiker Bot
parent 0b436563bd
commit 7efafa5a2c
4 changed files with 671 additions and 471 deletions

View file

@ -18,17 +18,48 @@ 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.
@ -36,56 +67,33 @@ func New(ctx context.Context, next http.Handler, config config.PassTLSClientCert
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 {
func newTLSClientInfo(info *config.TLSClientCertificateInfo) *tlsClientCertificateInfo {
if info == 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 {
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,
}
}
@ -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
var content []string
if options.country && len(cs.Country) > 0 {
content = append(content, fmt.Sprintf("C=%s", cs.Country[0]))
}
if options.province && len(cs.Province) > 0 {
content = append(content, fmt.Sprintf("ST=%s", cs.Province[0]))
}
if options.locality && len(cs.Locality) > 0 {
content = append(content, fmt.Sprintf("L=%s", cs.Locality[0]))
}
if options.Organization && len(cs.Organization) > 0 {
content = append(content, fmt.Sprintf("O=%s", cs.Organization[0]))
}
if options.commonName && len(cs.CommonName) > 0 {
content = append(content, fmt.Sprintf("CN=%s", cs.CommonName))
}
if len(content) > 0 {
subject = `Subject="` + strings.Join(content, ",") + `"`
// 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))
}
}
return subject
if options.CountryName {
writeParts(content, cs.Country, "C")
}
if options.StateOrProvinceName {
writeParts(content, cs.Province, "ST")
}
if options.LocalityName {
writeParts(content, cs.Locality, "L")
}
if options.OrganizationName {
writeParts(content, cs.Organization, "O")
}
if options.SerialNumber {
writePart(content, cs.SerialNumber, "SN")
}
if options.CommonName {
writePart(content, cs.CommonName, "CN")
}
if content.Len() > 0 {
return prefix + `="` + strings.TrimSuffix(content.String(), ",") + `"`
}
return ""
}
// getXForwardedTLSClientCertInfos Build a string with the wanted client certificates information
func writeParts(content *strings.Builder, entries []string, prefix string) {
for _, entry := range entries {
writePart(content, entry, prefix)
}
}
func writePart(content *strings.Builder, entry string, prefix string) {
if len(entry) > 0 {
content.WriteString(fmt.Sprintf("%s=%s,", prefix, entry))
}
}
// 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 {
@ -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()))
@ -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")
}