diff --git a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go index 4511b8407..eafae48aa 100644 --- a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go +++ b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Package activedirectoryupstreamwatcher implements a controller which watches ActiveDirectoryIdentityProviders. @@ -8,7 +8,6 @@ import ( "context" "encoding/base64" "fmt" - "regexp" "strconv" "strings" @@ -454,14 +453,29 @@ func groupSAMAccountNameWithDomainSuffix(entry *ldap.Entry) (string, error) { return sAMAccountName + "@" + domain, nil } -var domainComponentsRegexp = regexp.MustCompile(",DC=|,dc=") - func getDomainFromDistinguishedName(distinguishedName string) (string, error) { - domainComponents := domainComponentsRegexp.Split(distinguishedName, -1) - if len(domainComponents) == 1 { - return "", fmt.Errorf("did not find domain components in group dn: %s", distinguishedName) + parsedDN, err := ldap.ParseDN(distinguishedName) + if err != nil { + return "", fmt.Errorf("could not parse DN %q: %w", distinguishedName, err) } - return strings.Join(domainComponents[1:], "."), nil + + var dcValues []string + // Iterate through the Relative Distinguished Names (RDNs) + for _, rdn := range parsedDN.RDNs { + // Each RDN can contain multiple attribute-value pairs (usually just one) + for _, attr := range rdn.Attributes { + // Compare case-insensitively since LDAP attributes are case-insensitive + if strings.EqualFold(attr.Type, "dc") { + dcValues = append(dcValues, attr.Value) + } + } + } + + if len(dcValues) == 0 { + return "", fmt.Errorf("did not find domain components in group DN %q", distinguishedName) + } + + return strings.Join(dcValues, "."), nil } //nolint:gochecknoglobals // this needs to be a global variable so that tests can check pointer equality diff --git a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go index ca6a28915..64dee7ced 100644 --- a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go +++ b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go @@ -2481,14 +2481,35 @@ func TestGroupSAMAccountNameWithDomainSuffix(t *testing.T) { wantResult: "Mammals@mycompany.example.com", }, { - name: "no domain components in DN", + name: "unusual CN with DN and valid sAMAccountName", entry: &ldap.Entry{ - DN: "no-domain-components", + // A backslash within a CN escapes the next character, so we want to ignore the letters "DC=" there. + DN: `CN=animals\,DC=should-be-ignored-because-it-is-part-of-cn,OU=Users,OU=pinniped-ad,DC=mycompany,DC=example,DC=com`, Attributes: []*ldap.EntryAttribute{ ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}), }, }, - wantErr: "did not find domain components in group dn: no-domain-components", + wantResult: "Mammals@mycompany.example.com", + }, + { + name: "invalid DN", + entry: &ldap.Entry{ + DN: "invalid-dn", + Attributes: []*ldap.EntryAttribute{ + ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}), + }, + }, + wantErr: `could not parse DN "invalid-dn": DN ended with incomplete type, value pair`, + }, + { + name: "no domain components in DN", + entry: &ldap.Entry{ + DN: "CN=foo", + Attributes: []*ldap.EntryAttribute{ + ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}), + }, + }, + wantErr: `did not find domain components in group DN "CN=foo"`, }, { name: "multiple values for sAMAccountName attribute", @@ -2576,9 +2597,19 @@ func TestGetDomainFromDistinguishedName(t *testing.T) { wantDomain: "activedirectory.mycompany.example.com", }, { - name: "no domain components", + name: "unusual CN", + distinguishedName: `CN=animals\,DC=should-be-ignored-because-it-is-part-of-cn,OU=Users,OU=pinniped-ad,dc=activedirectory,DC=mycompany,DC=example,DC=com`, + wantDomain: "activedirectory.mycompany.example.com", + }, + { + name: "invalid DN", distinguishedName: "not-a-dn", - wantErr: "did not find domain components in group dn: not-a-dn", + wantErr: `could not parse DN "not-a-dn": DN ended with incomplete type, value pair`, + }, + { + name: "no domain components", + distinguishedName: "CN=foo", + wantErr: `did not find domain components in group DN "CN=foo"`, }, }