From 2e6d17ba8a2295784f081d3bfc2513bc3ad321ba Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 8 Feb 2023 13:35:57 +1100 Subject: [PATCH] feat(configuration): rfc2307bis implementation (#4900) This adds configuration defaults for RFC2307bis LDAP implementations such as OpenLDAP with the RFC2307bis LDIF which should service most user needs. --- docs/content/en/reference/guides/ldap.md | 38 +++++-- .../configuration/schema/authentication.go | 14 +++ internal/configuration/schema/const.go | 3 + .../configuration/validator/authentication.go | 74 +++++++------ .../validator/authentication_test.go | 104 +++++++++++++++++- internal/configuration/validator/const.go | 9 +- 6 files changed, 199 insertions(+), 43 deletions(-) diff --git a/docs/content/en/reference/guides/ldap.md b/docs/content/en/reference/guides/ldap.md index 557e428c4..9f5626c27 100644 --- a/docs/content/en/reference/guides/ldap.md +++ b/docs/content/en/reference/guides/ldap.md @@ -56,6 +56,9 @@ The following implementations exist: - Specific configuration defaults for [Active Directory] - Special implementation details: - Includes a special encoding format required for changing passwords with [Active Directory] +- `rfc2307bis`: + - Specific configuration defaults for [RFC2307bis] + - No special implementation details - `freeipa`: - Specific configuration defaults for [FreeIPA] - No special implementation details @@ -70,11 +73,17 @@ The following implementations exist: [FreeIPA]: https://www.freeipa.org/ [lldap]: https://github.com/nitnelave/lldap [GLAuth]: https://glauth.github.io/ +[RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02 ### Filter replacements Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP -search. +search which is indicated by the phase column. + +The phases exist to optimize performance. The replacements in the startup phase are replaced once before the connection +is ever established. In addition to this, during the startup phase we purposefully check the filters for which search +phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform +every possible replacement on every search regardless of if it's needed or not. #### Users filter replacements @@ -117,6 +126,7 @@ Username column. |:---------------:|:--------------:|:------------:|:----:|:----------:| | custom | N/A | displayName | mail | cn | | activedirectory | sAMAccountName | displayName | mail | cn | +| rfc2307bis | uid | displayName | mail | cn | | freeipa | uid | displayName | mail | cn | | lldap | uid | cn | mail | cn | | glauth | cn | description | mail | cn | @@ -130,20 +140,32 @@ the following conditions: - The [Active Directory] implementation achieves this via the `(!(userAccountControl:1.2.840.113556.1.4.803:=2))` filter. - The [FreeIPA] implementation achieves this via the `(!(nsAccountLock=TRUE))` filter. - The [GLAuth] implementation achieves this via the `(!(accountStatus=inactive))` filter. + - The following implementations have no suitable attribute for this as far as we're aware: + - [RFC2307bis] + - [lldap] - Their password is expired: - The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter. - The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter. + - The following implementations have no suitable attribute for this as far as we're aware: + - [RFC2307bis] + - [GLAuth] + - [lldap] - Their account is expired: - The [Active Directory] implementation achieves this via the `(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))` filter. - The [FreeIPA] implementation achieves this via the `(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))` filter. + - The following implementations have no suitable attribute for this as far as we're aware: + - [RFC2307bis] + - [GLAuth] + - [lldap] -| Implementation | Users Filter | Groups Filter | -|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:| -| custom | N/A | N/A | -| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) | -| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) | -| lldap | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) | -| glauth | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) | +| Implementation | Users Filter | Groups Filter | +|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:| +| custom | N/A | N/A | +| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) | +| rfc2307bis | (&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson))) | (&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) | +| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) | +| lldap | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) | +| glauth | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) | ##### Microsoft Active Directory sAMAccountType diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go index bd7d8914b..c44954c9d 100644 --- a/internal/configuration/schema/authentication.go +++ b/internal/configuration/schema/authentication.go @@ -199,6 +199,20 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = }, } +// DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation. +var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{ + UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))", + UsernameAttribute: ldapAttrUserID, + MailAttribute: ldapAttrMail, + DisplayNameAttribute: ldapAttrDisplayName, + GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers)))", + GroupNameAttribute: ldapAttrCommonName, + Timeout: time.Second * 5, + TLS: &TLSConfig{ + MinimumVersion: TLSVersion{tls.VersionTLS12}, + }, +} + // DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation. var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{ UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))", diff --git a/internal/configuration/schema/const.go b/internal/configuration/schema/const.go index 726f23e01..6f0ef781f 100644 --- a/internal/configuration/schema/const.go +++ b/internal/configuration/schema/const.go @@ -65,6 +65,9 @@ const ( // LDAPImplementationActiveDirectory is the string for the Active Directory LDAP implementation. LDAPImplementationActiveDirectory = "activedirectory" + // LDAPImplementationRFC2307bis is the string for the RFC2307bis LDAP implementation. + LDAPImplementationRFC2307bis = "rfc2307bis" + // LDAPImplementationFreeIPA is the string for the FreeIPA LDAP implementation. LDAPImplementationFreeIPA = "freeipa" diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go index f05d264c2..bcd64fabf 100644 --- a/internal/configuration/validator/authentication.go +++ b/internal/configuration/validator/authentication.go @@ -321,49 +321,19 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val config.LDAP.Implementation = schema.LDAPImplementationCustom } - var implementation *schema.LDAPAuthenticationBackend - - switch config.LDAP.Implementation { - case schema.LDAPImplementationCustom: - implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom - case schema.LDAPImplementationActiveDirectory: - implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory - case schema.LDAPImplementationFreeIPA: - implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA - case schema.LDAPImplementationLLDAP: - implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP - case schema.LDAPImplementationGLAuth: - implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth - default: - validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '"))) - } - - configDefaultTLS := &schema.TLSConfig{} - - if implementation != nil { - if config.LDAP.Timeout == 0 { - config.LDAP.Timeout = implementation.Timeout - } - - configDefaultTLS = &schema.TLSConfig{ - MinimumVersion: implementation.TLS.MinimumVersion, - MaximumVersion: implementation.TLS.MaximumVersion, - } - - setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config.LDAP, implementation) - } + defaultTLS := validateLDAPAuthenticationBackendImplementation(config, validator) if config.LDAP.URL == "" { validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url")) } else { - configDefaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator) + defaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator) } if config.LDAP.TLS == nil { config.LDAP.TLS = &schema.TLSConfig{} } - if err := ValidateTLSConfig(config.LDAP.TLS, configDefaultTLS); err != nil { + if err := ValidateTLSConfig(config.LDAP.TLS, defaultTLS); err != nil { validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err)) } @@ -382,6 +352,44 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val validateLDAPRequiredParameters(config, validator) } +func validateLDAPAuthenticationBackendImplementation(config *schema.AuthenticationBackend, validator *schema.StructValidator) *schema.TLSConfig { + var implementation *schema.LDAPAuthenticationBackend + + switch config.LDAP.Implementation { + case schema.LDAPImplementationCustom: + implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom + case schema.LDAPImplementationActiveDirectory: + implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory + case schema.LDAPImplementationRFC2307bis: + implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis + case schema.LDAPImplementationFreeIPA: + implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA + case schema.LDAPImplementationLLDAP: + implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP + case schema.LDAPImplementationGLAuth: + implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth + default: + validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '"))) + } + + tlsconfig := &schema.TLSConfig{} + + if implementation != nil { + if config.LDAP.Timeout == 0 { + config.LDAP.Timeout = implementation.Timeout + } + + tlsconfig = &schema.TLSConfig{ + MinimumVersion: implementation.TLS.MinimumVersion, + MaximumVersion: implementation.TLS.MaximumVersion, + } + + setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config.LDAP, implementation) + } + + return tlsconfig +} + func ldapImplementationShouldSetStr(config, implementation string) bool { return config == "" && implementation != "" } diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go index 4db6efba5..cc540f064 100644 --- a/internal/configuration/validator/authentication_test.go +++ b/internal/configuration/validator/authentication_test.go @@ -609,7 +609,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory', 'freeipa', 'lldap'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', 'glauth'") } func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() { @@ -1002,6 +1002,108 @@ func TestActiveDirectoryAuthenticationBackend(t *testing.T) { suite.Run(t, new(ActiveDirectoryAuthenticationBackendSuite)) } +type RFC2307bisAuthenticationBackendSuite struct { + suite.Suite + config schema.AuthenticationBackend + validator *schema.StructValidator +} + +func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() { + suite.validator = schema.NewStructValidator() + suite.config = schema.AuthenticationBackend{} + suite.config.LDAP = &schema.LDAPAuthenticationBackend{} + suite.config.LDAP.Implementation = schema.LDAPImplementationRFC2307bis + suite.config.LDAP.URL = testLDAPURL + suite.config.LDAP.User = testLDAPUser + suite.config.LDAP.Password = testLDAPPassword + suite.config.LDAP.BaseDN = testLDAPBaseDN + suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.TLS +} + +func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldSetDefaults() { + ValidateAuthenticationBackend(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) + + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout, + suite.config.LDAP.Timeout) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN, + suite.config.LDAP.AdditionalUsersDN) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN, + suite.config.LDAP.AdditionalGroupsDN) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter, + suite.config.LDAP.UsersFilter) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute, + suite.config.LDAP.UsernameAttribute) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute, + suite.config.LDAP.DisplayNameAttribute) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute, + suite.config.LDAP.MailAttribute) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter, + suite.config.LDAP.GroupsFilter) + suite.Assert().Equal( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute, + suite.config.LDAP.GroupNameAttribute) +} + +func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { + suite.config.LDAP.Timeout = time.Second * 2 + suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))" + suite.config.LDAP.UsernameAttribute = "o" + suite.config.LDAP.MailAttribute = "Email" + suite.config.LDAP.DisplayNameAttribute = "Given" + suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))" + suite.config.LDAP.GroupNameAttribute = "gid" + suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP" + suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP" + + ValidateAuthenticationBackend(&suite.config, suite.validator) + + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout, + suite.config.LDAP.Timeout) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN, + suite.config.LDAP.AdditionalUsersDN) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN, + suite.config.LDAP.AdditionalGroupsDN) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout, + suite.config.LDAP.Timeout) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter, + suite.config.LDAP.UsersFilter) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute, + suite.config.LDAP.UsernameAttribute) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute, + suite.config.LDAP.DisplayNameAttribute) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute, + suite.config.LDAP.MailAttribute) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter, + suite.config.LDAP.GroupsFilter) + suite.Assert().NotEqual( + schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute, + suite.config.LDAP.GroupNameAttribute) +} + +func TestRFC2307bisAuthenticationBackend(t *testing.T) { + suite.Run(t, new(RFC2307bisAuthenticationBackendSuite)) +} + type FreeIPAAuthenticationBackendSuite struct { suite.Suite config schema.AuthenticationBackend diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 786200771..8730518de 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -339,7 +339,14 @@ const ( ) var ( - validLDAPImplementations = []string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory, schema.LDAPImplementationFreeIPA, schema.LDAPImplementationLLDAP} + validLDAPImplementations = []string{ + schema.LDAPImplementationCustom, + schema.LDAPImplementationActiveDirectory, + schema.LDAPImplementationRFC2307bis, + schema.LDAPImplementationFreeIPA, + schema.LDAPImplementationLLDAP, + schema.LDAPImplementationGLAuth, + } ) const (