2018-11-14 10:18:03 +01:00
package passtlsclientcert
import (
"context"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
2019-03-04 16:40:05 +01:00
"io"
2018-11-14 10:18:03 +01:00
"net/http"
"net/url"
"strings"
"github.com/opentracing/opentracing-go/ext"
2020-09-16 15:46:04 +02:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares"
"github.com/traefik/traefik/v2/pkg/tracing"
2018-11-14 10:18:03 +01:00
)
2019-12-09 12:20:06 +01:00
const typeName = "PassClientTLSCert"
2018-11-14 10:18:03 +01:00
const (
2019-01-09 11:28:04 +01:00
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
2019-02-26 05:50:07 -08:00
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
2019-12-09 12:20:06 +01:00
)
const (
certSeparator = ","
fieldSeparator = ";"
subFieldSeparator = ","
2018-11-14 10:18:03 +01:00
)
2019-01-09 11:28:04 +01:00
var attributeTypeNames = map [ string ] string {
"0.9.2342.19200300.100.1.25" : "DC" , // Domain component OID - RFC 2247
}
2021-07-28 16:42:09 +01:00
// IssuerDistinguishedNameOptions is a struct for specifying the configuration
// for the distinguished name info of the issuer. This information is defined in
// RFC3739, section 3.1.1.
type IssuerDistinguishedNameOptions struct {
2019-01-09 11:28:04 +01:00
CommonName bool
CountryName bool
DomainComponent bool
LocalityName bool
OrganizationName bool
SerialNumber bool
StateOrProvinceName bool
}
2021-07-28 16:42:09 +01:00
func newIssuerDistinguishedNameOptions ( info * dynamic . TLSCLientCertificateIssuerDNInfo ) * IssuerDistinguishedNameOptions {
2019-01-09 11:28:04 +01:00
if info == nil {
return nil
}
2021-07-28 16:42:09 +01:00
return & IssuerDistinguishedNameOptions {
2019-01-09 11:28:04 +01:00
CommonName : info . CommonName ,
CountryName : info . Country ,
DomainComponent : info . DomainComponent ,
LocalityName : info . Locality ,
OrganizationName : info . Organization ,
SerialNumber : info . SerialNumber ,
StateOrProvinceName : info . Province ,
}
}
2021-07-28 16:42:09 +01:00
// SubjectDistinguishedNameOptions is a struct for specifying the configuration
// for the distinguished name info of the subject. This information is defined
// in RFC3739, section 3.1.2.
type SubjectDistinguishedNameOptions struct {
CommonName bool
CountryName bool
DomainComponent bool
LocalityName bool
OrganizationName bool
OrganizationalUnitName bool
SerialNumber bool
StateOrProvinceName bool
}
func newSubjectDistinguishedNameOptions ( info * dynamic . TLSCLientCertificateSubjectDNInfo ) * SubjectDistinguishedNameOptions {
if info == nil {
return nil
}
return & SubjectDistinguishedNameOptions {
CommonName : info . CommonName ,
CountryName : info . Country ,
DomainComponent : info . DomainComponent ,
LocalityName : info . Locality ,
OrganizationName : info . Organization ,
OrganizationalUnitName : info . OrganizationalUnit ,
SerialNumber : info . SerialNumber ,
StateOrProvinceName : info . Province ,
}
}
2019-01-09 11:28:04 +01:00
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
type tlsClientCertificateInfo struct {
2019-12-12 00:32:03 +01:00
notAfter bool
notBefore bool
sans bool
2021-07-28 16:42:09 +01:00
subject * SubjectDistinguishedNameOptions
issuer * IssuerDistinguishedNameOptions
2019-12-12 00:32:03 +01:00
serialNumber bool
2018-11-14 10:18:03 +01:00
}
2019-12-09 12:20:06 +01:00
func newTLSClientCertificateInfo ( info * dynamic . TLSClientCertificateInfo ) * tlsClientCertificateInfo {
2019-01-09 11:28:04 +01:00
if info == nil {
2018-11-14 10:18:03 +01:00
return nil
}
2019-01-09 11:28:04 +01:00
return & tlsClientCertificateInfo {
2021-07-28 16:42:09 +01:00
issuer : newIssuerDistinguishedNameOptions ( info . Issuer ) ,
2019-12-12 00:32:03 +01:00
notAfter : info . NotAfter ,
notBefore : info . NotBefore ,
2021-07-28 16:42:09 +01:00
subject : newSubjectDistinguishedNameOptions ( info . Subject ) ,
2019-12-12 00:32:03 +01:00
serialNumber : info . SerialNumber ,
sans : info . Sans ,
2018-11-14 10:18:03 +01:00
}
}
2019-12-09 12:20:06 +01:00
// 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
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 dynamic . PassTLSClientCert , name string ) ( http . Handler , error ) {
log . FromContext ( middlewares . GetLoggerCtx ( ctx , name , typeName ) ) . Debug ( "Creating middleware" )
return & passTLSClientCert {
next : next ,
name : name ,
pem : config . PEM ,
info : newTLSClientCertificateInfo ( config . Info ) ,
} , nil
}
2018-11-14 10:18:03 +01:00
func ( p * passTLSClientCert ) GetTracingInformation ( ) ( string , ext . SpanKindEnum ) {
return p . name , tracing . SpanKindNoneEnum
}
func ( p * passTLSClientCert ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2019-09-13 19:28:04 +02:00
ctx := middlewares . GetLoggerCtx ( req . Context ( ) , p . name , typeName )
2019-12-09 12:20:06 +01:00
logger := log . FromContext ( ctx )
if p . pem {
if req . TLS != nil && len ( req . TLS . PeerCertificates ) > 0 {
req . Header . Set ( xForwardedTLSClientCert , getCertificates ( ctx , req . TLS . PeerCertificates ) )
} else {
logger . Warn ( "Tried to extract a certificate on a request without mutual TLS" )
}
}
if p . info != nil {
if req . TLS != nil && len ( req . TLS . PeerCertificates ) > 0 {
headerContent := p . getCertInfo ( ctx , req . TLS . PeerCertificates )
req . Header . Set ( xForwardedTLSClientCertInfo , url . QueryEscape ( headerContent ) )
} else {
logger . Warn ( "Tried to extract a certificate on a request without mutual TLS" )
}
}
2019-09-13 19:28:04 +02:00
2018-11-14 10:18:03 +01:00
p . next . ServeHTTP ( rw , req )
}
2019-09-13 19:28:04 +02:00
2019-12-09 12:20:06 +01:00
// getCertInfo Build a string with the wanted client certificates information
// - the `,` is used to separate certificates
// - the `;` is used to separate root fields
// - the value of root fields is always wrapped by double quote
2020-05-11 12:06:07 +02:00
// - if a field is empty, the field is ignored.
2019-12-09 12:20:06 +01:00
func ( p * passTLSClientCert ) getCertInfo ( ctx context . Context , certs [ ] * x509 . Certificate ) string {
var headerValues [ ] string
for _ , peerCert := range certs {
var values [ ] string
if p . info != nil {
2021-07-28 16:42:09 +01:00
subject := getSubjectDNInfo ( ctx , p . info . subject , & peerCert . Subject )
2019-12-09 12:20:06 +01:00
if subject != "" {
values = append ( values , fmt . Sprintf ( ` Subject="%s" ` , strings . TrimSuffix ( subject , subFieldSeparator ) ) )
}
2021-07-28 16:42:09 +01:00
issuer := getIssuerDNInfo ( ctx , p . info . issuer , & peerCert . Issuer )
2019-12-09 12:20:06 +01:00
if issuer != "" {
values = append ( values , fmt . Sprintf ( ` Issuer="%s" ` , strings . TrimSuffix ( issuer , subFieldSeparator ) ) )
}
2019-12-12 00:32:03 +01:00
if p . info . serialNumber && peerCert . SerialNumber != nil {
sn := peerCert . SerialNumber . String ( )
if sn != "" {
values = append ( values , fmt . Sprintf ( ` SerialNumber="%s" ` , strings . TrimSuffix ( sn , subFieldSeparator ) ) )
}
}
2019-12-09 12:20:06 +01:00
if p . info . notBefore {
values = append ( values , fmt . Sprintf ( ` NB="%d" ` , uint64 ( peerCert . NotBefore . Unix ( ) ) ) )
}
if p . info . notAfter {
values = append ( values , fmt . Sprintf ( ` NA="%d" ` , uint64 ( peerCert . NotAfter . Unix ( ) ) ) )
}
if p . info . sans {
sans := getSANs ( peerCert )
if len ( sans ) > 0 {
values = append ( values , fmt . Sprintf ( ` SAN="%s" ` , strings . Join ( sans , subFieldSeparator ) ) )
}
}
}
value := strings . Join ( values , fieldSeparator )
headerValues = append ( headerValues , value )
}
return strings . Join ( headerValues , certSeparator )
}
2021-07-28 16:42:09 +01:00
func getIssuerDNInfo ( ctx context . Context , options * IssuerDistinguishedNameOptions , cs * pkix . Name ) string {
if options == nil {
return ""
}
content := & strings . Builder { }
// 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%s" , name . Value , subFieldSeparator ) )
}
}
if options . CountryName {
writeParts ( ctx , content , cs . Country , "C" )
}
if options . StateOrProvinceName {
writeParts ( ctx , content , cs . Province , "ST" )
}
if options . LocalityName {
writeParts ( ctx , content , cs . Locality , "L" )
}
if options . OrganizationName {
writeParts ( ctx , content , cs . Organization , "O" )
}
if options . SerialNumber {
writePart ( ctx , content , cs . SerialNumber , "SN" )
}
if options . CommonName {
writePart ( ctx , content , cs . CommonName , "CN" )
}
return content . String ( )
}
func getSubjectDNInfo ( ctx context . Context , options * SubjectDistinguishedNameOptions , cs * pkix . Name ) string {
2019-01-09 11:28:04 +01:00
if options == nil {
return ""
}
content := & strings . Builder { }
// Manage non standard attributes
for _ , name := range cs . Names {
// Domain Component - RFC 2247
if options . DomainComponent && attributeTypeNames [ name . Type . String ( ) ] == "DC" {
2019-12-09 12:20:06 +01:00
content . WriteString ( fmt . Sprintf ( "DC=%s%s" , name . Value , subFieldSeparator ) )
2019-01-09 11:28:04 +01:00
}
}
2018-11-14 10:18:03 +01:00
2019-01-09 11:28:04 +01:00
if options . CountryName {
2019-09-13 19:28:04 +02:00
writeParts ( ctx , content , cs . Country , "C" )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2019-01-09 11:28:04 +01:00
if options . StateOrProvinceName {
2019-09-13 19:28:04 +02:00
writeParts ( ctx , content , cs . Province , "ST" )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2019-01-09 11:28:04 +01:00
if options . LocalityName {
2019-09-13 19:28:04 +02:00
writeParts ( ctx , content , cs . Locality , "L" )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2019-01-09 11:28:04 +01:00
if options . OrganizationName {
2019-09-13 19:28:04 +02:00
writeParts ( ctx , content , cs . Organization , "O" )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2021-07-28 16:42:09 +01:00
if options . OrganizationalUnitName {
writeParts ( ctx , content , cs . OrganizationalUnit , "OU" )
}
2019-01-09 11:28:04 +01:00
if options . SerialNumber {
2019-09-13 19:28:04 +02:00
writePart ( ctx , content , cs . SerialNumber , "SN" )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2019-01-09 11:28:04 +01:00
if options . CommonName {
2019-09-13 19:28:04 +02:00
writePart ( ctx , content , cs . CommonName , "CN" )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2019-12-09 12:20:06 +01:00
return content . String ( )
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2019-09-13 19:28:04 +02:00
func writeParts ( ctx context . Context , content io . StringWriter , entries [ ] string , prefix string ) {
2019-01-09 11:28:04 +01:00
for _ , entry := range entries {
2019-09-13 19:28:04 +02:00
writePart ( ctx , content , entry , prefix )
2018-11-14 10:18:03 +01:00
}
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
2020-07-07 14:42:03 +02:00
func writePart ( ctx context . Context , content io . StringWriter , entry , prefix string ) {
2019-01-09 11:28:04 +01:00
if len ( entry ) > 0 {
2019-12-09 12:20:06 +01:00
_ , err := content . WriteString ( fmt . Sprintf ( "%s=%s%s" , prefix , entry , subFieldSeparator ) )
2019-03-04 16:40:05 +01:00
if err != nil {
2019-09-13 19:28:04 +02:00
log . FromContext ( ctx ) . Error ( err )
2019-03-04 16:40:05 +01:00
}
2019-01-09 11:28:04 +01:00
}
2018-11-14 10:18:03 +01:00
}
2019-12-09 12:20:06 +01:00
// sanitize As we pass the raw certificates, remove the useless data and make it http request compliant.
func sanitize ( cert [ ] byte ) string {
cleaned := strings . NewReplacer (
"-----BEGIN CERTIFICATE-----" , "" ,
"-----END CERTIFICATE-----" , "" ,
"\n" , "" ,
) . Replace ( string ( cert ) )
2018-11-14 10:18:03 +01:00
2019-12-09 12:20:06 +01:00
return url . QueryEscape ( cleaned )
2018-11-14 10:18:03 +01:00
}
2019-12-09 12:20:06 +01:00
// getCertificates Build a string with the client certificates.
func getCertificates ( ctx context . Context , certs [ ] * x509 . Certificate ) string {
var headerValues [ ] string
2019-09-13 19:28:04 +02:00
2019-12-09 12:20:06 +01:00
for _ , peerCert := range certs {
headerValues = append ( headerValues , extractCertificate ( ctx , peerCert ) )
2018-11-14 10:18:03 +01:00
}
2019-12-09 12:20:06 +01:00
return strings . Join ( headerValues , certSeparator )
2018-11-14 10:18:03 +01:00
}
// extractCertificate extract the certificate from the request.
2019-09-13 19:28:04 +02:00
func extractCertificate ( ctx context . Context , cert * x509 . Certificate ) string {
2019-12-09 12:20:06 +01:00
certPEM := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : cert . Raw } )
2018-11-14 10:18:03 +01:00
if certPEM == nil {
2019-09-13 19:28:04 +02:00
log . FromContext ( ctx ) . Error ( "Cannot extract the certificate content" )
2018-11-14 10:18:03 +01:00
return ""
}
2019-12-09 12:20:06 +01:00
return sanitize ( certPEM )
2018-11-14 10:18:03 +01:00
}
// getSANs get the Subject Alternate Name values.
func getSANs ( cert * x509 . Certificate ) [ ] string {
if cert == nil {
2019-12-09 12:20:06 +01:00
return nil
2018-11-14 10:18:03 +01:00
}
2019-12-09 12:20:06 +01:00
var sans [ ] string
2019-02-05 17:10:03 +01:00
sans = append ( sans , cert . DNSNames ... )
sans = append ( sans , cert . EmailAddresses ... )
2018-11-14 10:18:03 +01:00
for _ , ip := range cert . IPAddresses {
2019-12-09 12:20:06 +01:00
sans = append ( sans , ip . String ( ) )
2018-11-14 10:18:03 +01:00
}
for _ , uri := range cert . URIs {
2019-12-09 12:20:06 +01:00
sans = append ( sans , uri . String ( ) )
2018-11-14 10:18:03 +01:00
}
2019-12-09 12:20:06 +01:00
return sans
2018-11-14 10:18:03 +01:00
}