Skip to content

Commit

Permalink
feat(configuration): rfc2307bis implementation (#4900)
Browse files Browse the repository at this point in the history
This adds configuration defaults for RFC2307bis LDAP implementations such as OpenLDAP with the RFC2307bis LDIF which should service most user needs.
  • Loading branch information
james-d-elliott committed Feb 8, 2023
1 parent ba89200 commit 2e6d17b
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 40 deletions.
40 changes: 31 additions & 9 deletions docs/content/en/reference/guides/ldap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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 |
Expand All @@ -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.

| 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)) |
- 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))) |
| 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

Expand Down
14 changes: 14 additions & 0 deletions internal/configuration/schema/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})))",
Expand Down
3 changes: 3 additions & 0 deletions internal/configuration/schema/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
66 changes: 37 additions & 29 deletions internal/configuration/validator/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,47 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val
config.LDAP.Implementation = schema.LDAPImplementationCustom
}

defaultTLS := validateLDAPAuthenticationBackendImplementation(config, validator)

if config.LDAP.URL == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
} else {
defaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator)
}

if config.LDAP.TLS == nil {
config.LDAP.TLS = &schema.TLSConfig{}
}

if err := ValidateTLSConfig(config.LDAP.TLS, defaultTLS); err != nil {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err))
}

if strings.Contains(config.LDAP.UsersFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
}

if strings.Contains(config.LDAP.GroupsFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
}

if strings.Contains(config.LDAP.GroupsFilter, "{1}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
}

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:
Expand All @@ -338,48 +372,22 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '")))
}

configDefaultTLS := &schema.TLSConfig{}
tlsconfig := &schema.TLSConfig{}

if implementation != nil {
if config.LDAP.Timeout == 0 {
config.LDAP.Timeout = implementation.Timeout
}

configDefaultTLS = &schema.TLSConfig{
tlsconfig = &schema.TLSConfig{
MinimumVersion: implementation.TLS.MinimumVersion,
MaximumVersion: implementation.TLS.MaximumVersion,
}

setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config.LDAP, implementation)
}

if config.LDAP.URL == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
} else {
configDefaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator)
}

if config.LDAP.TLS == nil {
config.LDAP.TLS = &schema.TLSConfig{}
}

if err := ValidateTLSConfig(config.LDAP.TLS, configDefaultTLS); err != nil {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err))
}

if strings.Contains(config.LDAP.UsersFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
}

if strings.Contains(config.LDAP.GroupsFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
}

if strings.Contains(config.LDAP.GroupsFilter, "{1}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
}

validateLDAPRequiredParameters(config, validator)
return tlsconfig
}

func ldapImplementationShouldSetStr(config, implementation string) bool {
Expand Down

0 comments on commit 2e6d17b

Please sign in to comment.