Add organizationalUnit to passtlscert middleware
This commit is contained in:
parent
c76d58d532
commit
817ac8f256
18 changed files with 339 additions and 157 deletions
|
@ -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)
|
||||
|
|
|
@ -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])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue