Migrates the pass client tls cert middleware
This commit is contained in:
parent
0b436563bd
commit
7efafa5a2c
4 changed files with 671 additions and 471 deletions
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue