diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2025-02-23 19:05:57 +1100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-23 08:05:57 +0000 |
| commit | 0af038e0ced689db90da480876a0bb26d78c6fb9 (patch) | |
| tree | 5d97fe07636fcc5f7c6d87d6535bc5e1f0a9f2eb /internal/authentication/ldap_user_provider_lifecycle.go | |
| parent | 197b45521f5e3799d0b9ef1ec0000d4f83abdee9 (diff) | |
feat(authentication): ldap connection pooling (#7217)
This implements optional LDAP connection pooling to optimize the speed of LDAP transactions.
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal/authentication/ldap_user_provider_lifecycle.go')
| -rw-r--r-- | internal/authentication/ldap_user_provider_lifecycle.go | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/internal/authentication/ldap_user_provider_lifecycle.go b/internal/authentication/ldap_user_provider_lifecycle.go new file mode 100644 index 000000000..73dceaa6a --- /dev/null +++ b/internal/authentication/ldap_user_provider_lifecycle.go @@ -0,0 +1,232 @@ +package authentication + +import ( + "fmt" + "strings" + + "github.com/go-ldap/ldap/v3" + + "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/utils" +) + +func (p *LDAPUserProvider) Shutdown() (err error) { + return p.factory.Close() +} + +// StartupCheck implements the startup check provider interface. +func (p *LDAPUserProvider) StartupCheck() (err error) { + if err = p.factory.Initialize(); err != nil { + return err + } + + var client ldap.Client + + if client, err = p.factory.GetClient(); err != nil { + return err + } + + defer func() { + if err := p.factory.ReleaseClient(client); err != nil { + p.log.WithError(err).Warn("Error occurred releasing the LDAP client") + } + }() + + if p.features, err = p.getServerSupportedFeatures(client); err != nil { + return err + } + + if !p.features.Extensions.PwdModifyExOp && !p.disableResetPassword && + p.config.Implementation != schema.LDAPImplementationActiveDirectory { + p.log.Warn("Your LDAP server implementation may not support a method for password hashing " + + "known to Authelia, it's strongly recommended you ensure your directory server hashes the password " + + "attribute when users reset their password via Authelia.") + } + + if p.features.Extensions.TLS && !p.config.StartTLS && !p.config.Address.IsExplicitlySecure() { + p.log.Error("Your LDAP Server supports TLS but you don't appear to be utilizing it. We strongly " + + "recommend using the scheme 'ldaps://' or enabling the StartTLS option to secure connections with your " + + "LDAP Server.") + } + + return nil +} + +func (p *LDAPUserProvider) getServerSupportedFeatures(client ldap.Client) (features LDAPSupportedFeatures, err error) { + var ( + request *ldap.SearchRequest + result *ldap.SearchResult + ) + + request = ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases, + 1, 0, false, ldapBaseObjectFilter, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute}, nil) + + if result, err = client.Search(request); err != nil { + if p.config.PermitFeatureDetectionFailure { + p.log.WithError(err).Warnf("Error occurred during RootDSE search. This may result in reduced functionality.") + + return features, nil + } + + return features, fmt.Errorf("error occurred during RootDSE search: %w", err) + } + + if len(result.Entries) != 1 { + p.log.Errorf("The LDAP Server did not respond appropriately to a RootDSE search. This may result in reduced functionality.") + + return features, nil + } + + var controlTypeOIDs, extensionOIDs []string + + controlTypeOIDs, extensionOIDs, features = ldapGetFeatureSupportFromEntry(result.Entries[0]) + + controlTypes, extensions := none, none + + if len(controlTypeOIDs) != 0 { + controlTypes = strings.Join(controlTypeOIDs, ", ") + } + + if len(extensionOIDs) != 0 { + extensions = strings.Join(extensionOIDs, ", ") + } + + p.log.Debugf("LDAP Supported OIDs. Control Types: %s. Extensions: %s", controlTypes, extensions) + + return features, nil +} + +//nolint:gocyclo +func (p *LDAPUserProvider) parseDynamicUsersConfiguration() { + p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName) + p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username) + p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName) + p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail) + p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf) + + p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter) + + if len(p.config.Attributes.Username) != 0 && !utils.IsStringInSlice(p.config.Attributes.Username, p.usersAttributes) { + p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Username) + p.usersAttributesExtended = append(p.usersAttributesExtended, p.config.Attributes.Username) + } + + if len(p.config.Attributes.Mail) != 0 && !utils.IsStringInSlice(p.config.Attributes.Mail, p.usersAttributes) { + p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Mail) + p.usersAttributesExtended = append(p.usersAttributesExtended, p.config.Attributes.Mail) + } + + if len(p.config.Attributes.DisplayName) != 0 && !utils.IsStringInSlice(p.config.Attributes.DisplayName, p.usersAttributes) { + p.usersAttributes = append(p.usersAttributes, p.config.Attributes.DisplayName) + p.usersAttributesExtended = append(p.usersAttributesExtended, p.config.Attributes.DisplayName) + } + + if len(p.config.Attributes.MemberOf) != 0 { + if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.usersAttributes) { + p.usersAttributes = append(p.usersAttributes, p.config.Attributes.MemberOf) + p.usersAttributesExtended = append(p.usersAttributesExtended, p.config.Attributes.MemberOf) + } + } + + attributesExtended := []string{ + p.config.Attributes.GivenName, + p.config.Attributes.MiddleName, + p.config.Attributes.FamilyName, + p.config.Attributes.Nickname, + p.config.Attributes.Gender, + p.config.Attributes.Birthdate, + p.config.Attributes.Website, + p.config.Attributes.Profile, + p.config.Attributes.Picture, + p.config.Attributes.ZoneInfo, + p.config.Attributes.Locale, + p.config.Attributes.PhoneNumber, + p.config.Attributes.PhoneExtension, + p.config.Attributes.StreetAddress, + p.config.Attributes.Locality, + p.config.Attributes.Region, + p.config.Attributes.PostalCode, + p.config.Attributes.Country, + } + + for _, attribute := range attributesExtended { + if len(attribute) != 0 && !utils.IsStringInSlice(attribute, p.usersAttributesExtended) { + p.usersAttributesExtended = append(p.usersAttributesExtended, attribute) + } + } + + for attribute := range p.config.Attributes.Extra { + if !utils.IsStringInSlice(attribute, p.usersAttributesExtended) { + p.usersAttributesExtended = append(p.usersAttributesExtended, attribute) + } + } + + if p.config.AdditionalUsersDN != "" { + p.usersBaseDN = p.config.AdditionalUsersDN + "," + p.config.BaseDN + } else { + p.usersBaseDN = p.config.BaseDN + } + + p.log.Tracef("Dynamically generated users BaseDN is %s", p.usersBaseDN) + + if strings.Contains(p.config.UsersFilter, ldapPlaceholderInput) { + p.usersFilterReplacementInput = true + } + + if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeGeneralized) { + p.usersFilterReplacementDateTimeGeneralized = true + } + + if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeUnixEpoch) { + p.usersFilterReplacementDateTimeUnixEpoch = true + } + + if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeMicrosoftNTTimeEpoch) { + p.usersFilterReplacementDateTimeMicrosoftNTTimeEpoch = true + } + + p.log.Tracef("Detected user filter replacements that need to be resolved per lookup are: %s=%v", + ldapPlaceholderInput, p.usersFilterReplacementInput) +} + +func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() { + p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName) + p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username) + p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName) + p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail) + p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf) + + if len(p.config.Attributes.GroupName) != 0 && !utils.IsStringInSlice(p.config.Attributes.GroupName, p.groupsAttributes) { + p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.GroupName) + } + + if p.config.AdditionalGroupsDN != "" { + p.groupsBaseDN = p.config.AdditionalGroupsDN + "," + p.config.BaseDN + } else { + p.groupsBaseDN = p.config.BaseDN + } + + p.log.Tracef("Dynamically generated groups BaseDN is %s", p.groupsBaseDN) + + if strings.Contains(p.config.GroupsFilter, ldapPlaceholderInput) { + p.groupsFilterReplacementInput = true + } + + if strings.Contains(p.config.GroupsFilter, ldapPlaceholderUsername) { + p.groupsFilterReplacementUsername = true + } + + if strings.Contains(p.config.GroupsFilter, ldapPlaceholderDistinguishedName) { + p.groupsFilterReplacementDN = true + } + + if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfDistinguishedName) { + p.groupsFilterReplacementsMemberOfDN = true + } + + if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfRelativeDistinguishedName) { + p.groupsFilterReplacementsMemberOfRDN = true + } + + p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN) +} |
