1
0
Fork 0

Add organizationalUnit to passtlscert middleware

This commit is contained in:
Eric 2021-07-28 16:42:09 +01:00 committed by GitHub
parent c76d58d532
commit 817ac8f256
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 339 additions and 157 deletions

View file

@ -35,8 +35,10 @@ 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 {
// 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 {
CommonName bool
CountryName bool
DomainComponent bool
@ -46,12 +48,12 @@ type DistinguishedNameOptions struct {
StateOrProvinceName bool
}
func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *DistinguishedNameOptions {
func newIssuerDistinguishedNameOptions(info *dynamic.TLSCLientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions {
if info == nil {
return nil
}
return &DistinguishedNameOptions{
return &IssuerDistinguishedNameOptions{
CommonName: info.CommonName,
CountryName: info.Country,
DomainComponent: info.DomainComponent,
@ -62,13 +64,44 @@ func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *Dist
}
}
// 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,
}
}
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
type tlsClientCertificateInfo struct {
notAfter bool
notBefore bool
sans bool
subject *DistinguishedNameOptions
issuer *DistinguishedNameOptions
subject *SubjectDistinguishedNameOptions
issuer *IssuerDistinguishedNameOptions
serialNumber bool
}
@ -78,10 +111,10 @@ func newTLSClientCertificateInfo(info *dynamic.TLSClientCertificateInfo) *tlsCli
}
return &tlsClientCertificateInfo{
issuer: newDistinguishedNameOptions(info.Issuer),
issuer: newIssuerDistinguishedNameOptions(info.Issuer),
notAfter: info.NotAfter,
notBefore: info.NotBefore,
subject: newDistinguishedNameOptions(info.Subject),
subject: newSubjectDistinguishedNameOptions(info.Subject),
serialNumber: info.SerialNumber,
sans: info.Sans,
}
@ -147,12 +180,12 @@ func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certi
var values []string
if p.info != nil {
subject := getDNInfo(ctx, p.info.subject, &peerCert.Subject)
subject := getSubjectDNInfo(ctx, p.info.subject, &peerCert.Subject)
if subject != "" {
values = append(values, fmt.Sprintf(`Subject="%s"`, strings.TrimSuffix(subject, subFieldSeparator)))
}
issuer := getDNInfo(ctx, p.info.issuer, &peerCert.Issuer)
issuer := getIssuerDNInfo(ctx, p.info.issuer, &peerCert.Issuer)
if issuer != "" {
values = append(values, fmt.Sprintf(`Issuer="%s"`, strings.TrimSuffix(issuer, subFieldSeparator)))
}
@ -187,7 +220,7 @@ func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certi
return strings.Join(headerValues, certSeparator)
}
func getDNInfo(ctx context.Context, options *DistinguishedNameOptions, cs *pkix.Name) string {
func getIssuerDNInfo(ctx context.Context, options *IssuerDistinguishedNameOptions, cs *pkix.Name) string {
if options == nil {
return ""
}
@ -229,6 +262,52 @@ func getDNInfo(ctx context.Context, options *DistinguishedNameOptions, cs *pkix.
return content.String()
}
func getSubjectDNInfo(ctx context.Context, options *SubjectDistinguishedNameOptions, 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.OrganizationalUnitName {
writeParts(ctx, content, cs.OrganizationalUnit, "OU")
}
if options.SerialNumber {
writePart(ctx, content, cs.SerialNumber, "SN")
}
if options.CommonName {
writePart(ctx, content, cs.CommonName, "CN")
}
return content.String()
}
func writeParts(ctx context.Context, content io.StringWriter, entries []string, prefix string) {
for _, entry := range entries {
writePart(ctx, content, entry, prefix)

View file

@ -310,22 +310,22 @@ func TestPassTLSClientCert_PEM(t *testing.T) {
}
for _, test := range testCases {
tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
require.NoError(t, err)
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
if test.certContents != nil && len(test.certContents) > 0 {
req.TLS = buildTLSWith(test.certContents)
}
tlsClientHeaders.ServeHTTP(res, req)
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
require.NoError(t, err)
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
if test.certContents != nil && len(test.certContents) > 0 {
req.TLS = buildTLSWith(test.certContents)
}
tlsClientHeaders.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
@ -351,7 +351,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
}, fieldSeparator)
completeCertAllInfo := strings.Join([]string{
`Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com"`,
`Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,OU=Simple Signing Section,OU=Simple Signing Section 2,CN=*.cheese.com"`,
`Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`,
`SerialNumber="1"`,
`NB="1544094616"`,
@ -376,13 +376,14 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
desc: "No TLS, with subject info",
config: dynamic.PassTLSClientCert{
Info: &dynamic.TLSClientCertificateInfo{
Subject: &dynamic.TLSCLientCertificateDNInfo{
CommonName: true,
Organization: true,
Locality: true,
Province: true,
Country: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
CommonName: true,
Organization: true,
OrganizationalUnit: true,
Locality: true,
Province: true,
Country: true,
SerialNumber: true,
},
},
},
@ -392,7 +393,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
config: dynamic.PassTLSClientCert{
PEM: false,
Info: &dynamic.TLSClientCertificateInfo{
Subject: &dynamic.TLSCLientCertificateDNInfo{},
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{},
},
},
},
@ -405,16 +406,17 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
CommonName: true,
Country: true,
DomainComponent: true,
Locality: true,
Organization: true,
Province: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
CommonName: true,
Country: true,
DomainComponent: true,
Locality: true,
Organization: true,
OrganizationalUnit: true,
Province: true,
SerialNumber: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
CommonName: true,
Country: true,
DomainComponent: true,
@ -434,10 +436,30 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Organization: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
},
},
},
expectedHeader: `Subject="O=Cheese";Issuer="C=FR,C=US";NA="1632568236"`,
},
{
desc: "TLS with simple certificate, requesting non-existent info",
certContents: []string{minimalCheeseCrt},
config: dynamic.PassTLSClientCert{
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Organization: true,
// OrganizationalUnit is not set on this example certificate,
// so even though it's requested, it will be absent.
OrganizationalUnit: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
},
},
@ -453,16 +475,17 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
OrganizationalUnit: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@ -484,16 +507,17 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
OrganizationalUnit: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@ -509,22 +533,22 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
}
for _, test := range testCases {
tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
require.NoError(t, err)
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
if test.certContents != nil && len(test.certContents) > 0 {
req.TLS = buildTLSWith(test.certContents)
}
tlsClientHeaders.ServeHTTP(res, req)
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
require.NoError(t, err)
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
if test.certContents != nil && len(test.certContents) > 0 {
req.TLS = buildTLSWith(test.certContents)
}
tlsClientHeaders.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
@ -621,12 +645,12 @@ func Test_getSANs(t *testing.T) {
}
for _, test := range testCases {
sans := getSANs(test.cert)
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
sans := getSANs(test.cert)
if len(test.expected) > 0 {
for i, expected := range test.expected {
assert.Equal(t, expected, sans[i])