add user/group DNs to config
continuous-integration/drone/push Build is passing

This commit is contained in:
Nathan Coad
2026-04-21 14:24:16 +10:00
parent 14d242c8d1
commit 4fca10795e
5 changed files with 48 additions and 2 deletions
+23 -2
View File
@@ -25,6 +25,8 @@ var (
type LDAPConfig struct {
BindAddress string
BaseDN string
UserBaseDN string
GroupBaseDN string
TrustCertFile string
DisableValidation bool
Insecure bool
@@ -45,6 +47,8 @@ type LDAPIdentity struct {
type LDAPAuthenticator struct {
bindAddress string
baseDN string
userBaseDN string
groupBaseDN string
trustCertFile string
disableValidation bool
insecure bool
@@ -54,6 +58,8 @@ type LDAPAuthenticator struct {
func NewLDAPAuthenticator(cfg LDAPConfig) (*LDAPAuthenticator, error) {
bindAddress := strings.TrimSpace(cfg.BindAddress)
baseDN := strings.TrimSpace(cfg.BaseDN)
userBaseDN := strings.TrimSpace(cfg.UserBaseDN)
groupBaseDN := strings.TrimSpace(cfg.GroupBaseDN)
trustCertFile := strings.TrimSpace(cfg.TrustCertFile)
if bindAddress == "" {
@@ -62,6 +68,12 @@ func NewLDAPAuthenticator(cfg LDAPConfig) (*LDAPAuthenticator, error) {
if baseDN == "" {
return nil, fmt.Errorf("%w: base DN is required", ErrInvalidLDAPConfig)
}
if userBaseDN == "" {
userBaseDN = baseDN
}
if groupBaseDN == "" {
groupBaseDN = baseDN
}
if _, err := url.ParseRequestURI(bindAddress); err != nil {
return nil, fmt.Errorf("%w: bind address must be a valid URL: %v", ErrInvalidLDAPConfig, err)
}
@@ -74,6 +86,8 @@ func NewLDAPAuthenticator(cfg LDAPConfig) (*LDAPAuthenticator, error) {
return &LDAPAuthenticator{
bindAddress: bindAddress,
baseDN: baseDN,
userBaseDN: userBaseDN,
groupBaseDN: groupBaseDN,
trustCertFile: trustCertFile,
disableValidation: cfg.DisableValidation,
insecure: cfg.Insecure,
@@ -119,6 +133,10 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
if rewrittenToUPN {
identity.Diagnostics = append(identity.Diagnostics, "bind_username_rewritten_to_upn")
}
identity.Diagnostics = append(identity.Diagnostics,
"user_lookup_base_dn="+a.userBaseDN,
"group_lookup_base_dn="+a.groupBaseDN,
)
if whoami, err := conn.WhoAmI(nil); err != nil {
identity.Diagnostics = append(identity.Diagnostics, fmt.Sprintf("whoami_failed:%v", err))
} else if boundDN := parseWhoAmIDN(whoami.AuthzID); boundDN != "" {
@@ -131,7 +149,7 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
}
userLookupStartedAt := time.Now()
entry, lookupStrategy, err := a.lookupUserEntry(conn, bindUsername, identity.UserDN)
entry, lookupStrategy, err := a.lookupUserEntry(conn, inputUsername, identity.UserDN)
identity.UserLookupDuration = time.Since(userLookupStartedAt)
identity.Diagnostics = append(identity.Diagnostics, fmt.Sprintf("user_lookup_duration_ms=%d", identity.UserLookupDuration.Milliseconds()))
if err != nil {
@@ -329,6 +347,9 @@ func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string, us
if entry != nil {
return entry, "principal_upn", nil
}
// For UPN principals, avoid fallback attribute probes that are unlikely to match
// and can be expensive on large directory trees.
continue
}
entry, err := a.searchUserByAttribute(conn, "sAMAccountName", principal)
@@ -359,7 +380,7 @@ func (a *LDAPAuthenticator) searchUserByAttribute(conn *ldap.Conn, attribute str
}
searchRes, err := conn.Search(ldap.NewSearchRequest(
a.baseDN,
a.userBaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
2,
+10
View File
@@ -79,6 +79,8 @@ type SettingsYML struct {
LDAPGroups []string `yaml:"ldap_groups"`
LDAPBindAddress string `yaml:"ldap_bind_address"`
LDAPBaseDN string `yaml:"ldap_base_dn"`
LDAPUserBaseDN string `yaml:"ldap_user_base_dn"`
LDAPGroupBaseDN string `yaml:"ldap_group_base_dn"`
LDAPTrustCertFile string `yaml:"ldap_trust_cert_file"`
LDAPDisableValidation bool `yaml:"ldap_disable_validation"`
LDAPInsecure bool `yaml:"ldap_insecure"`
@@ -284,6 +286,8 @@ func applyDefaultsAndValidateSettings(cfg *SettingsYML) error {
s.AuthJWTSigningKey = strings.TrimSpace(s.AuthJWTSigningKey)
s.LDAPBindAddress = strings.TrimSpace(s.LDAPBindAddress)
s.LDAPBaseDN = strings.TrimSpace(s.LDAPBaseDN)
s.LDAPUserBaseDN = strings.TrimSpace(s.LDAPUserBaseDN)
s.LDAPGroupBaseDN = strings.TrimSpace(s.LDAPGroupBaseDN)
s.LDAPTrustCertFile = strings.TrimSpace(s.LDAPTrustCertFile)
s.LDAPGroups = compactTrimmedStrings(s.LDAPGroups)
@@ -340,6 +344,12 @@ func applyDefaultsAndValidateSettings(cfg *SettingsYML) error {
if s.LDAPBaseDN == "" {
return errors.New("settings.ldap_base_dn is required when settings.auth_enabled=true")
}
if s.LDAPUserBaseDN == "" {
s.LDAPUserBaseDN = s.LDAPBaseDN
}
if s.LDAPGroupBaseDN == "" {
s.LDAPGroupBaseDN = s.LDAPBaseDN
}
if len(s.AuthGroupRoleMappings) == 0 {
return errors.New("settings.auth_group_role_mappings must define at least one mapping when settings.auth_enabled=true")
}
@@ -193,6 +193,12 @@ func TestReadYMLSettingsAcceptsValidAuthConfigAndNormalizesMappings(t *testing.T
if len(got.LDAPGroups) != 1 || got.LDAPGroups[0] != "cn=vctp-viewers,ou=groups,dc=example,dc=com" {
t.Fatalf("expected ldap_groups to be compacted+trimmed, got %#v", got.LDAPGroups)
}
if got.LDAPUserBaseDN != "dc=example,dc=com" {
t.Fatalf("expected default ldap_user_base_dn to fall back to ldap_base_dn, got %q", got.LDAPUserBaseDN)
}
if got.LDAPGroupBaseDN != "dc=example,dc=com" {
t.Fatalf("expected default ldap_group_base_dn to fall back to ldap_base_dn, got %q", got.LDAPGroupBaseDN)
}
if got.AuthGroupRoleMappings["cn=vctp-admins,ou=groups,dc=example,dc=com"] != authRoleAdmin {
t.Fatalf("expected admin mapping to normalize role to %q, got %#v", authRoleAdmin, got.AuthGroupRoleMappings)
}