feat(configuration): freeipa ldap implementation (#4482)

This adds a FreeIPA LDAP implementation which purely adds sane defaults for FreeIPA. There are no functional differences just when the implementation option is set to 'freeipa' sane defaults which should be sufficient for most use cases are set. See the documentation at https://www.authelia.com/r/ldap#defaults for more details.

Closes #2177, Closes #2161
pull/4483/head^2
James Elliott 2022-12-21 21:07:00 +11:00 committed by GitHub
parent c7f4d5999d
commit d0d80b4f66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 166 additions and 50 deletions

View File

@ -284,8 +284,9 @@ authentication_backend:
# ldap:
## The LDAP implementation, this affects elements like the attribute utilised for resetting a password.
## Acceptable options are as follows:
## - 'activedirectory' - For Microsoft Active Directory.
## - 'custom' - For custom specifications of attributes and filters.
## - 'activedirectory' - for Microsoft Active Directory.
## - 'freeipa' - for FreeIPA.
## - 'custom' - for custom specifications of attributes and filters.
## This currently defaults to 'custom' to maintain existing behaviour.
##
## Depending on the option here certain other values in this section have a default value, notably all of the

View File

@ -10,6 +10,8 @@ menu:
parent: "guides"
weight: 220
toc: true
aliases:
- /r/ldap
---
## Binding
@ -46,10 +48,10 @@ Authelia primarily supports this method.
## Implementation Guide
There are currently two implementations, `custom` and `activedirectory`. The `activedirectory` implementation
must be used if you wish to allow users to change or reset their password as Active Directory
uses a custom attribute for this, and an input format other implementations do not use. The long term
intention of this is to have logical defaults for various RFC implementations of LDAP.
There are currently two implementations, `custom`, `activedirectory`, and `freeipa`. The `activedirectory`
implementation must be used if you wish to allow users to change or reset their password as Active Directory
uses a custom attribute and mechanism for this. The long term intention of this is to have logical defaults for various
RFC implementations of LDAP.
### Filter replacements
@ -86,6 +88,7 @@ Username column.
|:---------------:|:--------------:|:------------:|:----:|:----------:|
| custom | N/A | displayName | mail | cn |
| activedirectory | sAMAccountName | displayName | mail | cn |
| freeipa | uid | displayName | mail | cn |
#### Filter defaults
@ -98,6 +101,7 @@ value is not 0 which means the password requires changing at the next login.
|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:|
| 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))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) |
| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))) | (&(member={dn})(objectClass=groupOfNames)) |
##### Microsoft Active Directory sAMAccountType

View File

@ -284,8 +284,9 @@ authentication_backend:
# ldap:
## The LDAP implementation, this affects elements like the attribute utilised for resetting a password.
## Acceptable options are as follows:
## - 'activedirectory' - For Microsoft Active Directory.
## - 'custom' - For custom specifications of attributes and filters.
## - 'activedirectory' - for Microsoft Active Directory.
## - 'freeipa' - for FreeIPA.
## - 'custom' - for custom specifications of attributes and filters.
## This currently defaults to 'custom' to maintain existing behaviour.
##
## Depending on the option here certain other values in this section have a default value, notably all of the

View File

@ -175,24 +175,38 @@ var DefaultCIPasswordConfig = Password{
// DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config.
var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayName",
GroupNameAttribute: "cn",
UsernameAttribute: ldapAttrUserID,
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrDisplayName,
GroupNameAttribute: ldapAttrCommonName,
Timeout: time.Second * 5,
TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12},
},
}
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the MSAD Implementation.
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0)))",
UsernameAttribute: "sAMAccountName",
MailAttribute: "mail",
DisplayNameAttribute: "displayName",
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrDisplayName,
GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))",
GroupNameAttribute: "cn",
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)))",
UsernameAttribute: ldapAttrUserID,
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrDisplayName,
GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))",
GroupNameAttribute: ldapAttrCommonName,
Timeout: time.Second * 5,
TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12},

View File

@ -64,6 +64,9 @@ const (
// LDAPImplementationActiveDirectory is the string for the Active Directory LDAP implementation.
LDAPImplementationActiveDirectory = "activedirectory"
// LDAPImplementationFreeIPA is the string for the FreeIPA LDAP implementation.
LDAPImplementationFreeIPA = "freeipa"
)
// TOTP Algorithm.
@ -99,3 +102,10 @@ const (
blockCERTIFICATE = "CERTIFICATE"
blockRSAPRIVATEKEY = "RSA PRIVATE KEY"
)
const (
ldapAttrMail = "mail"
ldapAttrUserID = "uid"
ldapAttrDisplayName = "displayName"
ldapAttrCommonName = "cn"
)

View File

@ -328,8 +328,10 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom
case schema.LDAPImplementationActiveDirectory:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory
case schema.LDAPImplementationFreeIPA:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA
default:
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join([]string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory}, "', '")))
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '")))
}
configDefaultTLS := &schema.TLSConfig{}

View File

@ -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'")
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'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
@ -875,7 +875,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterT
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.2")
}
func TestLdapAuthenticationBackend(t *testing.T) {
func TestLDAPAuthenticationBackend(t *testing.T) {
suite.Run(t, new(LDAPAuthenticationBackendSuite))
}
@ -894,7 +894,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
suite.config.LDAP.User = testLDAPUser
suite.config.LDAP.Password = testLDAPPassword
suite.config.LDAP.BaseDN = testLDAPBaseDN
suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS
suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.TLS
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() {
@ -904,7 +904,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirec
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Timeout,
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
suite.config.LDAP.Timeout)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
@ -938,7 +938,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefault
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Timeout,
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
suite.config.LDAP.Timeout)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
@ -981,3 +981,88 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnIn
func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
suite.Run(t, new(ActiveDirectoryAuthenticationBackendSuite))
}
type FreeIPAAuthenticationBackendSuite struct {
suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
}
func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.config = schema.AuthenticationBackend{}
suite.config.LDAP = &schema.LDAPAuthenticationBackend{}
suite.config.LDAP.Implementation = schema.LDAPImplementationFreeIPA
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.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.TLS
}
func (suite *FreeIPAAuthenticationBackendSuite) TestShouldSetDefaults() {
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
suite.config.LDAP.Timeout)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
}
func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))"
suite.config.LDAP.UsernameAttribute = "dn"
suite.config.LDAP.MailAttribute = "email"
suite.config.LDAP.DisplayNameAttribute = "gecos"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))"
suite.config.LDAP.GroupNameAttribute = "groupName"
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
suite.config.LDAP.Timeout)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
}
func TestFreeIPAAuthenticationBackend(t *testing.T) {
suite.Run(t, new(FreeIPAAuthenticationBackendSuite))
}

View File

@ -5,6 +5,8 @@ import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
)
@ -311,32 +313,6 @@ const (
errFilePOptions = "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password"
)
var validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
var validSHA2CryptVariants = []string{digestSHA256, digestSHA512}
var validPBKDF2Variants = []string{digestSHA1, digestSHA224, digestSHA256, digestSHA384, digestSHA512}
var validBCryptVariants = []string{"standard", digestSHA256}
var validHashAlgorithms = []string{hashSHA2Crypt, hashPBKDF2, hashSCrypt, hashBCrypt, hashArgon2}
var validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"}
var validThemeNames = []string{"light", "dark", "grey", "auto"}
var validSessionSameSiteValues = []string{"none", "lax", "strict"}
var validLoLevels = []string{"trace", "debug", "info", "warn", "error"}
var validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
var validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
var validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
var validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
const (
operatorPresent = "present"
operatorAbsent = "absent"
@ -346,6 +322,29 @@ const (
operatorNotPattern = "not pattern"
)
var (
validLDAPImplementations = []string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory, schema.LDAPImplementationFreeIPA}
)
var (
validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
validSHA2CryptVariants = []string{digestSHA256, digestSHA512}
validPBKDF2Variants = []string{digestSHA1, digestSHA224, digestSHA256, digestSHA384, digestSHA512}
validBCryptVariants = []string{"standard", digestSHA256}
validHashAlgorithms = []string{hashSHA2Crypt, hashPBKDF2, hashSCrypt, hashBCrypt, hashArgon2}
)
var (
validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"}
validThemeNames = []string{"light", "dark", "grey", "auto"}
validSessionSameSiteValues = []string{"none", "lax", "strict"}
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
)
var (
validACLHTTPMethodVerbs = append(validRFC7231HTTPMethodVerbs, validRFC4918HTTPMethodVerbs...)
validACLRulePolicies = []string{policyBypass, policyOneFactor, policyTwoFactor, policyDeny}

View File

@ -18,7 +18,7 @@ func ValidateLog(config *schema.Configuration, validator *schema.StructValidator
config.Log.Format = schema.DefaultLoggingConfiguration.Format
}
if !utils.IsStringInSlice(config.Log.Level, validLoLevels) {
validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strings.Join(validLoLevels, "', '"), config.Log.Level))
if !utils.IsStringInSlice(config.Log.Level, validLogLevels) {
validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strings.Join(validLogLevels, "', '"), config.Log.Level))
}
}