feat(authentication): unauthenticated ldap bind (#3291)
This allows configuring unauthenticated LDAP binding.pull/3538/head
parent
ee6ef51620
commit
25b5c1ee2e
|
@ -790,8 +790,7 @@ notifier:
|
||||||
## Enables additional debug messages.
|
## Enables additional debug messages.
|
||||||
# enable_client_debug_messages: false
|
# enable_client_debug_messages: false
|
||||||
|
|
||||||
## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it below 8 for
|
## SECURITY NOTICE: It's not recommended changing this option and values below 8 are strongly discouraged.
|
||||||
## security reasons.
|
|
||||||
# minimum_parameter_entropy: 8
|
# minimum_parameter_entropy: 8
|
||||||
|
|
||||||
## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it set to 'never'
|
## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it set to 'never'
|
||||||
|
|
|
@ -38,6 +38,7 @@ authentication_backend:
|
||||||
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
||||||
group_name_attribute: cn
|
group_name_attribute: cn
|
||||||
permit_referrals: false
|
permit_referrals: false
|
||||||
|
permit_unauthenticated_bind: false
|
||||||
user: CN=admin,DC=example,DC=com
|
user: CN=admin,DC=example,DC=com
|
||||||
password: password
|
password: password
|
||||||
```
|
```
|
||||||
|
@ -50,7 +51,7 @@ authentication_backend:
|
||||||
|
|
||||||
Configures the LDAP implementation used by Authelia.
|
Configures the LDAP implementation used by Authelia.
|
||||||
|
|
||||||
See the [Implementation Guide](#implementation-guide) for information.
|
See the [Implementation Guide](../../reference/guides/ldap.md#implementation-guide) for information.
|
||||||
|
|
||||||
### url
|
### url
|
||||||
|
|
||||||
|
@ -170,7 +171,7 @@ using the following filter which is currently only tested against Microsoft Acti
|
||||||
|
|
||||||
`(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))`
|
`(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))`
|
||||||
|
|
||||||
## group_name_attribute
|
### group_name_attribute
|
||||||
|
|
||||||
{{< confkey type="string" required="situational" >}}
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
@ -180,13 +181,24 @@ information.*
|
||||||
|
|
||||||
The LDAP attribute that is used by Authelia to determine the group name.
|
The LDAP attribute that is used by Authelia to determine the group name.
|
||||||
|
|
||||||
## permit_referrals
|
### permit_referrals
|
||||||
|
|
||||||
{{< confkey type="boolean" default="false" required="no" >}}
|
{{< confkey type="boolean" default="false" required="no" >}}
|
||||||
|
|
||||||
Permits following referrals. This is useful if you have read-only servers in your architecture and thus require
|
Permits following referrals. This is useful if you have read-only servers in your architecture and thus require
|
||||||
referrals to be followed when performing write operations.
|
referrals to be followed when performing write operations.
|
||||||
|
|
||||||
|
### permit_unauthenticated_bind
|
||||||
|
|
||||||
|
{{< confkey type="boolean" default="false" required="no" >}}
|
||||||
|
|
||||||
|
*__WARNING:__ This option is strongly discouraged. Please consider disabling unauthenticated binding to your LDAP
|
||||||
|
server and utilizing a service account.*
|
||||||
|
|
||||||
|
Permits binding to the server without a password. For this option to be enabled both the [password](#password)
|
||||||
|
configuration option must be blank and [disable_reset_password](introduction.md#disable_reset_password) must be
|
||||||
|
disabled.
|
||||||
|
|
||||||
### user
|
### user
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< confkey type="string" required="yes" >}}
|
||||||
|
@ -200,65 +212,6 @@ The distinguished name of the user paired with the password to bind with for loo
|
||||||
The password of the user paired with the user to bind with for lookup and password change operations.
|
The password of the user paired with the user to bind with for lookup and password change operations.
|
||||||
Can also be defined using a [secret](../methods/secrets.md) which is the recommended for containerized deployments.
|
Can also be defined using a [secret](../methods/secrets.md) which is the recommended for containerized deployments.
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
### Filter replacements
|
|
||||||
|
|
||||||
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
|
|
||||||
search.
|
|
||||||
|
|
||||||
#### Users filter replacements
|
|
||||||
|
|
||||||
| Placeholder | Phase | Replacement |
|
|
||||||
|:------------------------:|:-------:|:-------------------------------------:|
|
|
||||||
| {username_attribute} | startup | The configured username attribute |
|
|
||||||
| {mail_attribute} | startup | The configured mail attribute |
|
|
||||||
| {display_name_attribute} | startup | The configured display name attribute |
|
|
||||||
| {input} | search | The input into the username field |
|
|
||||||
|
|
||||||
#### Groups filter replacements
|
|
||||||
|
|
||||||
| Placeholder | Phase | Replacement |
|
|
||||||
|:-----------:|:------:|:-------------------------------------------------------------------------:|
|
|
||||||
| {input} | search | The input into the username field |
|
|
||||||
| {username} | search | The username from the profile lookup obtained from the username attribute |
|
|
||||||
| {dn} | search | The distinguished name from the profile lookup |
|
|
||||||
|
|
||||||
### Defaults
|
|
||||||
|
|
||||||
The below tables describes the current attribute defaults for each implementation.
|
|
||||||
|
|
||||||
#### Attribute defaults
|
|
||||||
|
|
||||||
This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the
|
|
||||||
Username column.
|
|
||||||
|
|
||||||
| Implementation | Username | Display Name | Mail | Group Name |
|
|
||||||
|:---------------:|:--------------:|:------------:|:----:|:----------:|
|
|
||||||
| custom | N/A | displayName | mail | cn |
|
|
||||||
| activedirectory | sAMAccountName | displayName | mail | cn |
|
|
||||||
|
|
||||||
#### Filter defaults
|
|
||||||
|
|
||||||
The filters are probably the most important part to get correct when setting up LDAP. You want to exclude disabled
|
|
||||||
accounts. The active directory example has two attribute filters that accomplish this as an example (more examples would
|
|
||||||
be appreciated). The userAccountControl filter checks that the account is not disabled and the pwdLastSet makes sure that
|
|
||||||
value is not 0 which means the password requires changing at the next login.
|
|
||||||
|
|
||||||
| 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))) | (&(member={dn})(objectClass=group)(objectCategory=group)) |
|
|
||||||
|
|
||||||
*__Note:__* The Active Directory filter `(sAMAccountType=805306368)` is exactly the same as
|
|
||||||
`(&(objectCategory=person)(objectClass=user))` except that the former is more performant, you can read more about this
|
|
||||||
and other Active Directory filters on the [TechNet wiki](https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx).
|
|
||||||
|
|
||||||
## Refresh Interval
|
## Refresh Interval
|
||||||
|
|
||||||
It's recommended you either use the default [refresh interval](./introduction.md#refresh_interval) or configure this to
|
It's recommended you either use the default [refresh interval](./introduction.md#refresh_interval) or configure this to
|
||||||
|
@ -274,6 +227,10 @@ In order to avoid such problems, we highly recommended you follow [RFC2307] by u
|
||||||
Directory and `uid` for other implementations as the attribute holding the unique identifier
|
Directory and `uid` for other implementations as the attribute holding the unique identifier
|
||||||
for your users.
|
for your users.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [LDAP Reference Guide](../../reference/guides/ldap.md)
|
||||||
|
|
||||||
[username attribute]: #username_attribute
|
[username attribute]: #username_attribute
|
||||||
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
|
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
|
||||||
[RFC2307]: https://www.rfc-editor.org/rfc/rfc2307.html
|
[RFC2307]: https://www.rfc-editor.org/rfc/rfc2307.html
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
---
|
||||||
|
title: "LDAP"
|
||||||
|
description: "A reference guide on the LDAP implementation specifics"
|
||||||
|
lead: "This section contains reference documentation for Authelia's LDAP implementation specifics."
|
||||||
|
date: 2022-03-20T12:52:27+11:00
|
||||||
|
draft: false
|
||||||
|
images: []
|
||||||
|
menu:
|
||||||
|
reference:
|
||||||
|
parent: "guides"
|
||||||
|
weight: 220
|
||||||
|
toc: true
|
||||||
|
---
|
||||||
|
|
||||||
|
## Binding
|
||||||
|
|
||||||
|
When it comes to LDAP there are several considerations for deciding how to bind to the LDAP server.
|
||||||
|
|
||||||
|
### Unauthenticated Binding
|
||||||
|
|
||||||
|
The most insecure method is unauthenticated binds. They are generally considered insecure due to the fact allowing them
|
||||||
|
at all ensures anyone with any level of network access can easily obtain objects and their attributes.
|
||||||
|
|
||||||
|
Authelia does support unauthenticated binds but it is not by default, you must configure the
|
||||||
|
[permit_unauthenticated_bind](../../configuration/first-factor/ldap.md#permit_unauthenticated_bind) configuration
|
||||||
|
option.
|
||||||
|
|
||||||
|
### End-User Binding
|
||||||
|
|
||||||
|
One method to bind to the server that is favored by a lot of people is binding to the LDAP server as the end user. While
|
||||||
|
this is more secure than methods such as [Unauthenticated Binding](#unauthenticated-binding) the drawback is that it can
|
||||||
|
only be used securely at the time the user enters their credentials. Storing a password in memory in general is not very
|
||||||
|
secure and prone to breakage due to outside influences (i.e. the user changes their password).
|
||||||
|
|
||||||
|
In addition, this method is not compatible with the password reset / forgot password flow at all (not to be confused
|
||||||
|
with a change password flow).
|
||||||
|
|
||||||
|
Authelia doesn't currently support such a binding method excluding for checking user passwords.
|
||||||
|
|
||||||
|
### Service-User Binding
|
||||||
|
|
||||||
|
This is the most common method of binding to LDAP. This involves setting up a special service user with a complex
|
||||||
|
password which has the minimum permissions required to do the tasks required.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Filter replacements
|
||||||
|
|
||||||
|
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
|
||||||
|
search.
|
||||||
|
|
||||||
|
#### Users filter replacements
|
||||||
|
|
||||||
|
| Placeholder | Phase | Replacement |
|
||||||
|
|:------------------------:|:-------:|:-------------------------------------:|
|
||||||
|
| {username_attribute} | startup | The configured username attribute |
|
||||||
|
| {mail_attribute} | startup | The configured mail attribute |
|
||||||
|
| {display_name_attribute} | startup | The configured display name attribute |
|
||||||
|
| {input} | search | The input into the username field |
|
||||||
|
|
||||||
|
#### Groups filter replacements
|
||||||
|
|
||||||
|
| Placeholder | Phase | Replacement |
|
||||||
|
|:-----------:|:------:|:-------------------------------------------------------------------------:|
|
||||||
|
| {input} | search | The input into the username field |
|
||||||
|
| {username} | search | The username from the profile lookup obtained from the username attribute |
|
||||||
|
| {dn} | search | The distinguished name from the profile lookup |
|
||||||
|
|
||||||
|
### Defaults
|
||||||
|
|
||||||
|
The below tables describes the current attribute defaults for each implementation.
|
||||||
|
|
||||||
|
#### Attribute defaults
|
||||||
|
|
||||||
|
This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the
|
||||||
|
Username column.
|
||||||
|
|
||||||
|
| Implementation | Username | Display Name | Mail | Group Name |
|
||||||
|
|:---------------:|:--------------:|:------------:|:----:|:----------:|
|
||||||
|
| custom | N/A | displayName | mail | cn |
|
||||||
|
| activedirectory | sAMAccountName | displayName | mail | cn |
|
||||||
|
|
||||||
|
#### Filter defaults
|
||||||
|
|
||||||
|
The filters are probably the most important part to get correct when setting up LDAP. You want to exclude disabled
|
||||||
|
accounts. The active directory example has two attribute filters that accomplish this as an example (more examples would
|
||||||
|
be appreciated). The userAccountControl filter checks that the account is not disabled and the pwdLastSet makes sure that
|
||||||
|
value is not 0 which means the password requires changing at the next login.
|
||||||
|
|
||||||
|
| 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))) | (&(member={dn})(objectClass=group)(objectCategory=group)) |
|
||||||
|
|
||||||
|
*__Note:__* The Active Directory filter `(sAMAccountType=805306368)` is exactly the same as
|
||||||
|
`(&(objectCategory=person)(objectClass=user))` except that the former is more performant, you can read more about this
|
||||||
|
and other Active Directory filters on the [TechNet wiki](https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx).
|
|
@ -118,3 +118,17 @@ func (mr *MockLDAPClientMockRecorder) StartTLS(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockLDAPClient)(nil).StartTLS), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockLDAPClient)(nil).StartTLS), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnauthenticatedBind mocks base method.
|
||||||
|
func (m *MockLDAPClient) UnauthenticatedBind(arg0 string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UnauthenticatedBind", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnauthenticatedBind indicates an expected call of UnauthenticatedBind.
|
||||||
|
func (mr *MockLDAPClientMockRecorder) UnauthenticatedBind(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnauthenticatedBind", reflect.TypeOf((*MockLDAPClient)(nil).UnauthenticatedBind), arg0)
|
||||||
|
}
|
||||||
|
|
|
@ -226,7 +226,7 @@ func (p *LDAPUserProvider) connect() (client LDAPClient, err error) {
|
||||||
return p.connectCustom(p.config.URL, p.config.User, p.config.Password, p.config.StartTLS, p.dialOpts...)
|
return p.connectCustom(p.config.URL, p.config.User, p.config.Password, p.config.StartTLS, p.dialOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) connectCustom(url, userDN, password string, startTLS bool, opts ...ldap.DialOpt) (client LDAPClient, err error) {
|
func (p *LDAPUserProvider) connectCustom(url, username, password string, startTLS bool, opts ...ldap.DialOpt) (client LDAPClient, err error) {
|
||||||
if client, err = p.factory.DialURL(url, opts...); err != nil {
|
if client, err = p.factory.DialURL(url, opts...); err != nil {
|
||||||
return nil, fmt.Errorf("dial failed with error: %w", err)
|
return nil, fmt.Errorf("dial failed with error: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,13 @@ func (p *LDAPUserProvider) connectCustom(url, userDN, password string, startTLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = client.Bind(userDN, password); err != nil {
|
if password == "" {
|
||||||
|
err = client.UnauthenticatedBind(username)
|
||||||
|
} else {
|
||||||
|
err = client.Bind(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
client.Close()
|
client.Close()
|
||||||
|
|
||||||
return nil, fmt.Errorf("bind failed with error: %w", err)
|
return nil, fmt.Errorf("bind failed with error: %w", err)
|
||||||
|
|
|
@ -20,6 +20,7 @@ type LDAPClient interface {
|
||||||
StartTLS(config *tls.Config) (err error)
|
StartTLS(config *tls.Config) (err error)
|
||||||
|
|
||||||
Bind(username, password string) (err error)
|
Bind(username, password string) (err error)
|
||||||
|
UnauthenticatedBind(username string) (err error)
|
||||||
|
|
||||||
Modify(modifyRequest *ldap.ModifyRequest) (err error)
|
Modify(modifyRequest *ldap.ModifyRequest) (err error)
|
||||||
PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) (pwdModifyResult *ldap.PasswordModifyResult, err error)
|
PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) (pwdModifyResult *ldap.PasswordModifyResult, err error)
|
||||||
|
|
|
@ -790,8 +790,7 @@ notifier:
|
||||||
## Enables additional debug messages.
|
## Enables additional debug messages.
|
||||||
# enable_client_debug_messages: false
|
# enable_client_debug_messages: false
|
||||||
|
|
||||||
## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it below 8 for
|
## SECURITY NOTICE: It's not recommended changing this option and values below 8 are strongly discouraged.
|
||||||
## security reasons.
|
|
||||||
# minimum_parameter_entropy: 8
|
# minimum_parameter_entropy: 8
|
||||||
|
|
||||||
## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it set to 'never'
|
## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it set to 'never'
|
||||||
|
|
|
@ -27,6 +27,7 @@ type LDAPAuthenticationBackendConfiguration struct {
|
||||||
DisplayNameAttribute string `koanf:"display_name_attribute"`
|
DisplayNameAttribute string `koanf:"display_name_attribute"`
|
||||||
|
|
||||||
PermitReferrals bool `koanf:"permit_referrals"`
|
PermitReferrals bool `koanf:"permit_referrals"`
|
||||||
|
PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
|
||||||
|
|
||||||
User string `koanf:"user"`
|
User string `koanf:"user"`
|
||||||
Password string `koanf:"password"`
|
Password string `koanf:"password"`
|
||||||
|
|
|
@ -62,6 +62,7 @@ var Keys = []string{
|
||||||
"authentication_backend.ldap.mail_attribute",
|
"authentication_backend.ldap.mail_attribute",
|
||||||
"authentication_backend.ldap.display_name_attribute",
|
"authentication_backend.ldap.display_name_attribute",
|
||||||
"authentication_backend.ldap.permit_referrals",
|
"authentication_backend.ldap.permit_referrals",
|
||||||
|
"authentication_backend.ldap.permit_unauthenticated_bind",
|
||||||
"authentication_backend.ldap.user",
|
"authentication_backend.ldap.user",
|
||||||
"authentication_backend.ldap.password",
|
"authentication_backend.ldap.password",
|
||||||
"authentication_backend.file.path",
|
"authentication_backend.file.path",
|
||||||
|
|
|
@ -22,7 +22,7 @@ func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfigura
|
||||||
if config.File != nil {
|
if config.File != nil {
|
||||||
validateFileAuthenticationBackend(config.File, validator)
|
validateFileAuthenticationBackend(config.File, validator)
|
||||||
} else if config.LDAP != nil {
|
} else if config.LDAP != nil {
|
||||||
validateLDAPAuthenticationBackend(config.LDAP, validator)
|
validateLDAPAuthenticationBackend(config, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.RefreshInterval == "" {
|
if config.RefreshInterval == "" {
|
||||||
|
@ -118,50 +118,50 @@ func validateFileAuthenticationBackendArgon2id(config *schema.PasswordConfigurat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
if config.Timeout == 0 {
|
if config.LDAP.Timeout == 0 {
|
||||||
config.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
|
config.LDAP.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Implementation == "" {
|
if config.LDAP.Implementation == "" {
|
||||||
config.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
|
config.LDAP.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.TLS == nil {
|
if config.LDAP.TLS == nil {
|
||||||
config.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
||||||
} else if config.TLS.MinimumVersion == "" {
|
} else if config.LDAP.TLS.MinimumVersion == "" {
|
||||||
config.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
|
config.LDAP.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := utils.TLSStringToTLSConfigVersion(config.TLS.MinimumVersion); err != nil {
|
if _, err := utils.TLSStringToTLSConfigVersion(config.LDAP.TLS.MinimumVersion); err != nil {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSMinVersion, config.TLS.MinimumVersion, err))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSMinVersion, config.LDAP.TLS.MinimumVersion, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch config.Implementation {
|
switch config.LDAP.Implementation {
|
||||||
case schema.LDAPImplementationCustom:
|
case schema.LDAPImplementationCustom:
|
||||||
setDefaultImplementationCustomLDAPAuthenticationBackend(config)
|
setDefaultImplementationCustomLDAPAuthenticationBackend(config.LDAP)
|
||||||
case schema.LDAPImplementationActiveDirectory:
|
case schema.LDAPImplementationActiveDirectory:
|
||||||
setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config)
|
setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config.LDAP)
|
||||||
default:
|
default:
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.Implementation, strings.Join([]string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory}, "', '")))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join([]string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory}, "', '")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(config.UsersFilter, "{0}") {
|
if strings.Contains(config.LDAP.UsersFilter, "{0}") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(config.GroupsFilter, "{0}") {
|
if strings.Contains(config.LDAP.GroupsFilter, "{0}") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(config.GroupsFilter, "{1}") {
|
if strings.Contains(config.LDAP.GroupsFilter, "{1}") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.URL == "" {
|
if config.LDAP.URL == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
|
||||||
} else {
|
} else {
|
||||||
validateLDAPAuthenticationBackendURL(config, validator)
|
validateLDAPAuthenticationBackendURL(config.LDAP, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
validateLDAPRequiredParameters(config, validator)
|
validateLDAPRequiredParameters(config, validator)
|
||||||
|
@ -191,42 +191,50 @@ func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBacke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLDAPRequiredParameters(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateLDAPRequiredParameters(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
// TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387).
|
if config.LDAP.PermitUnauthenticatedBind {
|
||||||
if config.User == "" {
|
if config.LDAP.Password != "" {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithPassword))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.DisableResetPassword {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if config.LDAP.User == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "user"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "user"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387).
|
if config.LDAP.Password == "" {
|
||||||
if config.Password == "" {
|
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "password"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "password"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.BaseDN == "" {
|
if config.LDAP.BaseDN == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "base_dn"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "base_dn"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.UsersFilter == "" {
|
if config.LDAP.UsersFilter == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter"))
|
||||||
} else {
|
} else {
|
||||||
if !strings.HasPrefix(config.UsersFilter, "(") || !strings.HasSuffix(config.UsersFilter, ")") {
|
if !strings.HasPrefix(config.LDAP.UsersFilter, "(") || !strings.HasSuffix(config.LDAP.UsersFilter, ")") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "users_filter", config.UsersFilter, config.UsersFilter))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "users_filter", config.LDAP.UsersFilter, config.LDAP.UsersFilter))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(config.UsersFilter, "{username_attribute}") {
|
if !strings.Contains(config.LDAP.UsersFilter, "{username_attribute}") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "username_attribute"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "username_attribute"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test helps the user know that users_filter is broken after the breaking change induced by this commit.
|
// This test helps the user know that users_filter is broken after the breaking change induced by this commit.
|
||||||
if !strings.Contains(config.UsersFilter, "{input}") {
|
if !strings.Contains(config.LDAP.UsersFilter, "{input}") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "input"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "input"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.GroupsFilter == "" {
|
if config.LDAP.GroupsFilter == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter"))
|
||||||
} else if !strings.HasPrefix(config.GroupsFilter, "(") || !strings.HasSuffix(config.GroupsFilter, ")") {
|
} else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.GroupsFilter, config.GroupsFilter))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,9 @@ const (
|
||||||
"must at least be parallelism multiplied by 8 when using algorithm 'argon2id' " +
|
"must at least be parallelism multiplied by 8 when using algorithm 'argon2id' " +
|
||||||
"with parallelism %d it should be at least %d but it is configured as '%d'"
|
"with parallelism %d it should be at least %d but it is configured as '%d'"
|
||||||
|
|
||||||
|
errFmtLDAPAuthBackendUnauthenticatedBindWithPassword = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when a password is specified"
|
||||||
|
errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when password reset is enabled"
|
||||||
|
|
||||||
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
|
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
|
||||||
errFmtLDAPAuthBackendTLSMinVersion = "authentication_backend: ldap: tls: option " +
|
errFmtLDAPAuthBackendTLSMinVersion = "authentication_backend: ldap: tls: option " +
|
||||||
"'minimum_tls_version' is invalid: %s: %w"
|
"'minimum_tls_version' is invalid: %s: %w"
|
||||||
|
|
Loading…
Reference in New Issue