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.
pull/4901/head
James Elliott 2023-02-08 13:35:57 +11:00 committed by GitHub
parent ba89200c19
commit 2e6d17ba8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 43 deletions

View File

@ -56,6 +56,9 @@ The following implementations exist:
- Specific configuration defaults for [Active Directory] - Specific configuration defaults for [Active Directory]
- Special implementation details: - Special implementation details:
- Includes a special encoding format required for changing passwords with [Active Directory] - Includes a special encoding format required for changing passwords with [Active Directory]
- `rfc2307bis`:
- Specific configuration defaults for [RFC2307bis]
- No special implementation details
- `freeipa`: - `freeipa`:
- Specific configuration defaults for [FreeIPA] - Specific configuration defaults for [FreeIPA]
- No special implementation details - No special implementation details
@ -70,11 +73,17 @@ The following implementations exist:
[FreeIPA]: https://www.freeipa.org/ [FreeIPA]: https://www.freeipa.org/
[lldap]: https://github.com/nitnelave/lldap [lldap]: https://github.com/nitnelave/lldap
[GLAuth]: https://glauth.github.io/ [GLAuth]: https://glauth.github.io/
[RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02
### Filter replacements ### Filter replacements
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP 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 #### Users filter replacements
@ -117,6 +126,7 @@ Username column.
|:---------------:|:--------------:|:------------:|:----:|:----------:| |:---------------:|:--------------:|:------------:|:----:|:----------:|
| custom | N/A | displayName | mail | cn | | custom | N/A | displayName | mail | cn |
| activedirectory | sAMAccountName | displayName | mail | cn | | activedirectory | sAMAccountName | displayName | mail | cn |
| rfc2307bis | uid | displayName | mail | cn |
| freeipa | uid | displayName | mail | cn | | freeipa | uid | displayName | mail | cn |
| lldap | uid | cn | mail | cn | | lldap | uid | cn | mail | cn |
| glauth | cn | description | 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 [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 [FreeIPA] implementation achieves this via the `(!(nsAccountLock=TRUE))` filter.
- The [GLAuth] implementation achieves this via the `(!(accountStatus=inactive))` 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: - Their password is expired:
- The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter. - The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter.
- The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` 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: - Their account is expired:
- The [Active Directory] implementation achieves this via the `(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))` filter. - 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 [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 | | Implementation | Users Filter | Groups Filter |
|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:| |:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|
| custom | N/A | N/A | | 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))) | | 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)) | | rfc2307bis | (&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson))) | (&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) |
| lldap | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) | | freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) |
| glauth | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) | | 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 ##### Microsoft Active Directory sAMAccountType

View File

@ -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. // DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",

View File

@ -65,6 +65,9 @@ const (
// LDAPImplementationActiveDirectory is the string for the Active Directory LDAP implementation. // LDAPImplementationActiveDirectory is the string for the Active Directory LDAP implementation.
LDAPImplementationActiveDirectory = "activedirectory" LDAPImplementationActiveDirectory = "activedirectory"
// LDAPImplementationRFC2307bis is the string for the RFC2307bis LDAP implementation.
LDAPImplementationRFC2307bis = "rfc2307bis"
// LDAPImplementationFreeIPA is the string for the FreeIPA LDAP implementation. // LDAPImplementationFreeIPA is the string for the FreeIPA LDAP implementation.
LDAPImplementationFreeIPA = "freeipa" LDAPImplementationFreeIPA = "freeipa"

View File

@ -321,49 +321,19 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val
config.LDAP.Implementation = schema.LDAPImplementationCustom config.LDAP.Implementation = schema.LDAPImplementationCustom
} }
var implementation *schema.LDAPAuthenticationBackend defaultTLS := validateLDAPAuthenticationBackendImplementation(config, validator)
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)
}
if config.LDAP.URL == "" { if config.LDAP.URL == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url")) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
} else { } else {
configDefaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator) defaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator)
} }
if config.LDAP.TLS == nil { if config.LDAP.TLS == nil {
config.LDAP.TLS = &schema.TLSConfig{} 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)) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err))
} }
@ -382,6 +352,44 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val
validateLDAPRequiredParameters(config, validator) 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 { func ldapImplementationShouldSetStr(config, implementation string) bool {
return config == "" && implementation != "" return config == "" && implementation != ""
} }

View File

@ -609,7 +609,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat
suite.Assert().Len(suite.validator.Warnings(), 0) suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1) 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() { func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
@ -1002,6 +1002,108 @@ func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
suite.Run(t, new(ActiveDirectoryAuthenticationBackendSuite)) 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 { type FreeIPAAuthenticationBackendSuite struct {
suite.Suite suite.Suite
config schema.AuthenticationBackend config schema.AuthenticationBackend

View File

@ -339,7 +339,14 @@ const (
) )
var ( 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 ( const (