feat(authentication): allow customizable ldap connection timeout (#2240)

This implements both a connection timeout for LDAP connections, and makes it configurable by administrators. The default is 5s. The reason for this commit is currently if a connection to an LDAP server cannot be established it does not timeout in a reasonable period.
pull/2241/head
James Elliott 2021-08-05 14:30:00 +10:00 committed by GitHub
parent a3b14871ba
commit b2a49e1780
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 146 additions and 115 deletions

View File

@ -149,6 +149,9 @@ authentication_backend:
## Scheme can be ldap or ldaps in the format (port optional). ## Scheme can be ldap or ldaps in the format (port optional).
url: ldap://127.0.0.1 url: ldap://127.0.0.1
## The dial timeout for LDAP.
timeout: 5s
## Use StartTLS with the LDAP connection. ## Use StartTLS with the LDAP connection.
start_tls: false start_tls: false

View File

@ -7,11 +7,9 @@ nav_order: 2
--- ---
# LDAP # LDAP
**Authelia** supports using a LDAP server as the users database. **Authelia** supports using a LDAP server as the users database.
## Configuration ## Configuration
```yaml ```yaml
authentication_backend: authentication_backend:
disable_reset_password: false disable_reset_password: false
@ -19,12 +17,13 @@ authentication_backend:
ldap: ldap:
implementation: custom implementation: custom
url: ldap://127.0.0.1 url: ldap://127.0.0.1
timeout: 5s
start_tls: false start_tls: false
tls: tls:
server_name: ldap.example.com server_name: ldap.example.com
skip_verify: false skip_verify: false
minimum_version: TLS1.2 minimum_version: TLS1.2
base_dn: dc=example,dc=com base_dn: DC=example,DC=com
username_attribute: uid username_attribute: uid
additional_users_dn: ou=users additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectClass=person)) users_filter: (&({username_attribute}={input})(objectClass=person))
@ -33,7 +32,7 @@ authentication_backend:
group_name_attribute: cn group_name_attribute: cn
mail_attribute: mail mail_attribute: mail
display_name_attribute: displayName display_name_attribute: displayName
user: cn=admin,dc=example,dc=com user: CN=admin,DC=example,DC=com
password: password password: password
``` ```
@ -70,6 +69,18 @@ If utilising an IPv6 literal address it must be enclosed by square brackets:
url: ldap://[fd00:1111:2222:3333::1] url: ldap://[fd00:1111:2222:3333::1]
``` ```
### timeout
<div markdown="1">
type: duration
{: .label .label-config .label-purple }
default: 5s
{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
</div>
The timeout for dialing an LDAP connection.
### start_tls ### start_tls
<div markdown="1"> <div markdown="1">
type: boolean type: boolean
@ -86,7 +97,6 @@ URL's are slightly more secure.
### tls ### tls
Controls the TLS connection validation process. You can see how to configure the tls Controls the TLS connection validation process. You can see how to configure the tls
section [here](../index.md#tls-configuration). section [here](../index.md#tls-configuration).
@ -143,11 +153,9 @@ The default value is dependent on the [implementation](#implementation), refer t
[attribute defaults](#attribute-defaults) for more information. [attribute defaults](#attribute-defaults) for more information.
### additional_groups_dn ### additional_groups_dn
Similar to [additional_users_dn](#additional_users_dn) but it applies to group searches. Similar to [additional_users_dn](#additional_users_dn) but it applies to group searches.
### groups_filter ### groups_filter
Similar to [users_filter](#users_filter) but it applies to group searches. In order to include groups the memeber is not Similar to [users_filter](#users_filter) but it applies to group searches. In order to include groups the memeber is not
a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try
using the following filter which is currently only tested against Microsoft Active Directory: using the following filter which is currently only tested against Microsoft Active Directory:
@ -155,7 +163,6 @@ 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))`
### mail_attribute ### mail_attribute
The attribute to retrieve which contains the users email addresses. This is important for the device registration and The attribute to retrieve which contains the users email addresses. This is important for the device registration and
password reset processes. password reset processes.
The user must have an email address in order for Authelia to perform The user must have an email address in order for Authelia to perform
@ -163,32 +170,26 @@ identity verification when a user attempts to reset their password or
register a second factor device. register a second factor device.
### display_name_attribute ### display_name_attribute
The attribute to retrieve which is shown on the Web UI to the user when they log in. The attribute to retrieve which is shown on the Web UI to the user when they log in.
### user ### user
The distinguished name of the user paired with the password to bind with for lookup and password change operations. The distinguished name of the user paired with the password to bind with for lookup and password change operations.
### password ### password
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](../secrets.md) which is the recommended for containerized deployments. Can also be defined using a [secret](../secrets.md) which is the recommended for containerized deployments.
## Implementation Guide ## Implementation Guide
There are currently two implementations, `custom` and `activedirectory`. The `activedirectory` implementation 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 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 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. intention of this is to have logical defaults for various RFC implementations of LDAP.
### 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.
#### Users filter replacements #### Users filter replacements
|Placeholder |Phase |Replacement | |Placeholder |Phase |Replacement |
|:----------------------:|:-----:|:--------------------------------------------------------------:| |:----------------------:|:-----:|:--------------------------------------------------------------:|
|{username_attribute} |startup|The [username attribute](#username_attribute) configured | |{username_attribute} |startup|The [username attribute](#username_attribute) configured |
@ -197,7 +198,6 @@ search.
|{input} |search |The input into the username field | |{input} |search |The input into the username field |
#### Groups filter replacements #### Groups filter replacements
|Placeholder |Phase |Replacement | |Placeholder |Phase |Replacement |
|:----------------------:|:-----:|:-------------------------------------------------------------------------:| |:----------------------:|:-----:|:-------------------------------------------------------------------------:|
|{input} |search |The input into the username field | |{input} |search |The input into the username field |
@ -205,7 +205,6 @@ search.
|{dn} |search |The distinguished name from the profile lookup | |{dn} |search |The distinguished name from the profile lookup |
### Defaults ### Defaults
The below tables describes the current attribute defaults for each implementation. The below tables describes the current attribute defaults for each implementation.
#### Attribute defaults #### Attribute defaults
@ -218,7 +217,6 @@ described by the Username column.
|activedirectory|sAMAccountName|displayName |mail|cn | |activedirectory|sAMAccountName|displayName |mail|cn |
#### Filter defaults #### Filter defaults
The filters are probably the most important part to get correct when setting up LDAP. 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 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 filters that accomplish this as an example (more examples would be appreciated). The
@ -236,7 +234,6 @@ _**Note:**_ The Active Directory filter `(sAMAccountType=805306368)` is exactly
and other Active Directory filters on the [TechNet wiki](https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx). 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
This setting takes a [duration notation](../index.md#duration-notation-format) that sets the max frequency This setting takes a [duration notation](../index.md#duration-notation-format) that sets the max frequency
for how often Authelia contacts the backend to verify the user still exists and that the groups stored for how often Authelia contacts the backend to verify the user still exists and that the groups stored
in the session are up to date. This allows us to destroy sessions when the user no longer matches the in the session are up to date. This allows us to destroy sessions when the user no longer matches the
@ -254,7 +251,6 @@ on a page loads which could be substantially costly. It's a trade-off between lo
you should adapt according to your own security policy. you should adapt according to your own security policy.
## Important notes ## Important notes
Users must be uniquely identified by an attribute, this attribute must obviously contain a single value and Users must be uniquely identified by an attribute, this attribute must obviously contain a single value and
be guaranteed by the administrator to be unique. If multiple users have the same value, Authelia will simply be guaranteed by the administrator to be unique. If multiple users have the same value, Authelia will simply
fail authenticating the user and display an error message in the logs. fail authenticating the user and display an error message in the logs.

View File

@ -64,7 +64,7 @@ func (lc *LDAPConnectionImpl) StartTLS(config *tls.Config) error {
// LDAPConnectionFactory an interface of factory of ldap connections. // LDAPConnectionFactory an interface of factory of ldap connections.
type LDAPConnectionFactory interface { type LDAPConnectionFactory interface {
DialURL(addr string, opts ldap.DialOpt) (LDAPConnection, error) DialURL(addr string, opts ...ldap.DialOpt) (LDAPConnection, error)
} }
// LDAPConnectionFactoryImpl the production implementation of an ldap connection factory. // LDAPConnectionFactoryImpl the production implementation of an ldap connection factory.
@ -76,8 +76,8 @@ func NewLDAPConnectionFactoryImpl() *LDAPConnectionFactoryImpl {
} }
// DialURL creates a connection from an LDAP URL when successful. // DialURL creates a connection from an LDAP URL when successful.
func (lcf *LDAPConnectionFactoryImpl) DialURL(addr string, opts ldap.DialOpt) (LDAPConnection, error) { func (lcf *LDAPConnectionFactoryImpl) DialURL(addr string, opts ...ldap.DialOpt) (LDAPConnection, error) {
conn, err := ldap.DialURL(addr, opts) conn, err := ldap.DialURL(addr, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -10,30 +10,30 @@ import (
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )
// MockLDAPConnection is a mock of LDAPConnection interface // MockLDAPConnection is a mock of LDAPConnection interface.
type MockLDAPConnection struct { type MockLDAPConnection struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockLDAPConnectionMockRecorder recorder *MockLDAPConnectionMockRecorder
} }
// MockLDAPConnectionMockRecorder is the mock recorder for MockLDAPConnection // MockLDAPConnectionMockRecorder is the mock recorder for MockLDAPConnection.
type MockLDAPConnectionMockRecorder struct { type MockLDAPConnectionMockRecorder struct {
mock *MockLDAPConnection mock *MockLDAPConnection
} }
// NewMockLDAPConnection creates a new mock instance // NewMockLDAPConnection creates a new mock instance.
func NewMockLDAPConnection(ctrl *gomock.Controller) *MockLDAPConnection { func NewMockLDAPConnection(ctrl *gomock.Controller) *MockLDAPConnection {
mock := &MockLDAPConnection{ctrl: ctrl} mock := &MockLDAPConnection{ctrl: ctrl}
mock.recorder = &MockLDAPConnectionMockRecorder{mock} mock.recorder = &MockLDAPConnectionMockRecorder{mock}
return mock return mock
} }
// EXPECT returns an object that allows the caller to indicate expected use // EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLDAPConnection) EXPECT() *MockLDAPConnectionMockRecorder { func (m *MockLDAPConnection) EXPECT() *MockLDAPConnectionMockRecorder {
return m.recorder return m.recorder
} }
// Bind mocks base method // Bind mocks base method.
func (m *MockLDAPConnection) Bind(username, password string) error { func (m *MockLDAPConnection) Bind(username, password string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Bind", username, password) ret := m.ctrl.Call(m, "Bind", username, password)
@ -41,25 +41,53 @@ func (m *MockLDAPConnection) Bind(username, password string) error {
return ret0 return ret0
} }
// Bind indicates an expected call of Bind // Bind indicates an expected call of Bind.
func (mr *MockLDAPConnectionMockRecorder) Bind(username, password interface{}) *gomock.Call { func (mr *MockLDAPConnectionMockRecorder) Bind(username, password interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockLDAPConnection)(nil).Bind), username, password) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockLDAPConnection)(nil).Bind), username, password)
} }
// Close mocks base method // Close mocks base method.
func (m *MockLDAPConnection) Close() { func (m *MockLDAPConnection) Close() {
m.ctrl.T.Helper() m.ctrl.T.Helper()
m.ctrl.Call(m, "Close") m.ctrl.Call(m, "Close")
} }
// Close indicates an expected call of Close // Close indicates an expected call of Close.
func (mr *MockLDAPConnectionMockRecorder) Close() *gomock.Call { func (mr *MockLDAPConnectionMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockLDAPConnection)(nil).Close)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockLDAPConnection)(nil).Close))
} }
// Search mocks base method // Modify mocks base method.
func (m *MockLDAPConnection) Modify(modifyRequest *ldap.ModifyRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Modify", modifyRequest)
ret0, _ := ret[0].(error)
return ret0
}
// Modify indicates an expected call of Modify.
func (mr *MockLDAPConnectionMockRecorder) Modify(modifyRequest interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockLDAPConnection)(nil).Modify), modifyRequest)
}
// PasswordModify mocks base method.
func (m *MockLDAPConnection) PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PasswordModify", pwdModifyRequest)
ret0, _ := ret[0].(error)
return ret0
}
// PasswordModify indicates an expected call of PasswordModify.
func (mr *MockLDAPConnectionMockRecorder) PasswordModify(pwdModifyRequest interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordModify", reflect.TypeOf((*MockLDAPConnection)(nil).PasswordModify), pwdModifyRequest)
}
// Search mocks base method.
func (m *MockLDAPConnection) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) { func (m *MockLDAPConnection) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Search", searchRequest) ret := m.ctrl.Call(m, "Search", searchRequest)
@ -68,41 +96,13 @@ func (m *MockLDAPConnection) Search(searchRequest *ldap.SearchRequest) (*ldap.Se
return ret0, ret1 return ret0, ret1
} }
// Search indicates an expected call of Search // Search indicates an expected call of Search.
func (mr *MockLDAPConnectionMockRecorder) Search(searchRequest interface{}) *gomock.Call { func (mr *MockLDAPConnectionMockRecorder) Search(searchRequest interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockLDAPConnection)(nil).Search), searchRequest) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockLDAPConnection)(nil).Search), searchRequest)
} }
// Modify mocks base method // StartTLS mocks base method.
func (m *MockLDAPConnection) Modify(modifyRequest *ldap.ModifyRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Modify", modifyRequest)
ret0, _ := ret[0].(error)
return ret0
}
// Modify indicates an expected call of Modify
func (mr *MockLDAPConnectionMockRecorder) Modify(modifyRequest interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockLDAPConnection)(nil).Modify), modifyRequest)
}
// PasswordModify mocks base method
func (m *MockLDAPConnection) PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PasswordModify", pwdModifyRequest)
ret0, _ := ret[0].(error)
return ret0
}
// PasswordModify indicates an expected call of PasswordModify
func (mr *MockLDAPConnectionMockRecorder) PasswordModify(pwdModifyRequest interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordModify", reflect.TypeOf((*MockLDAPConnection)(nil).Modify), pwdModifyRequest)
}
// StartTLS mocks base method
func (m *MockLDAPConnection) StartTLS(config *tls.Config) error { func (m *MockLDAPConnection) StartTLS(config *tls.Config) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StartTLS", config) ret := m.ctrl.Call(m, "StartTLS", config)
@ -110,46 +110,51 @@ func (m *MockLDAPConnection) StartTLS(config *tls.Config) error {
return ret0 return ret0
} }
// StartTLS indicates an expected call of StartTLS // StartTLS indicates an expected call of StartTLS.
func (mr *MockLDAPConnectionMockRecorder) StartTLS(config interface{}) *gomock.Call { func (mr *MockLDAPConnectionMockRecorder) StartTLS(config interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockLDAPConnection)(nil).StartTLS), config) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockLDAPConnection)(nil).StartTLS), config)
} }
// MockLDAPConnectionFactory is a mock of LDAPConnectionFactory interface // MockLDAPConnectionFactory is a mock of LDAPConnectionFactory interface.
type MockLDAPConnectionFactory struct { type MockLDAPConnectionFactory struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockLDAPConnectionFactoryMockRecorder recorder *MockLDAPConnectionFactoryMockRecorder
} }
// MockLDAPConnectionFactoryMockRecorder is the mock recorder for MockLDAPConnectionFactory // MockLDAPConnectionFactoryMockRecorder is the mock recorder for MockLDAPConnectionFactory.
type MockLDAPConnectionFactoryMockRecorder struct { type MockLDAPConnectionFactoryMockRecorder struct {
mock *MockLDAPConnectionFactory mock *MockLDAPConnectionFactory
} }
// NewMockLDAPConnectionFactory creates a new mock instance // NewMockLDAPConnectionFactory creates a new mock instance.
func NewMockLDAPConnectionFactory(ctrl *gomock.Controller) *MockLDAPConnectionFactory { func NewMockLDAPConnectionFactory(ctrl *gomock.Controller) *MockLDAPConnectionFactory {
mock := &MockLDAPConnectionFactory{ctrl: ctrl} mock := &MockLDAPConnectionFactory{ctrl: ctrl}
mock.recorder = &MockLDAPConnectionFactoryMockRecorder{mock} mock.recorder = &MockLDAPConnectionFactoryMockRecorder{mock}
return mock return mock
} }
// EXPECT returns an object that allows the caller to indicate expected use // EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLDAPConnectionFactory) EXPECT() *MockLDAPConnectionFactoryMockRecorder { func (m *MockLDAPConnectionFactory) EXPECT() *MockLDAPConnectionFactoryMockRecorder {
return m.recorder return m.recorder
} }
// DialURL mocks base method // DialURL mocks base method.
func (m *MockLDAPConnectionFactory) DialURL(addr string, opts ldap.DialOpt) (LDAPConnection, error) { func (m *MockLDAPConnectionFactory) DialURL(addr string, opts ...ldap.DialOpt) (LDAPConnection, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DialURL", addr, opts) varargs := []interface{}{addr}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DialURL", varargs...)
ret0, _ := ret[0].(LDAPConnection) ret0, _ := ret[0].(LDAPConnection)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// DialURL indicates an expected call of DialURL // DialURL indicates an expected call of DialURL.
func (mr *MockLDAPConnectionFactoryMockRecorder) DialURL(addr, opts interface{}) *gomock.Call { func (mr *MockLDAPConnectionFactoryMockRecorder) DialURL(addr interface{}, opts ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialURL", reflect.TypeOf((*MockLDAPConnectionFactory)(nil).DialURL), addr, opts) varargs := append([]interface{}{addr}, opts...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialURL", reflect.TypeOf((*MockLDAPConnectionFactory)(nil).DialURL), varargs...)
} }

View File

@ -4,6 +4,7 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"net"
"strings" "strings"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
@ -19,7 +20,7 @@ import (
type LDAPUserProvider struct { type LDAPUserProvider struct {
configuration schema.LDAPAuthenticationBackendConfiguration configuration schema.LDAPAuthenticationBackendConfiguration
tlsConfig *tls.Config tlsConfig *tls.Config
dialOpts ldap.DialOpt dialOpts []ldap.DialOpt
logger *logrus.Logger logger *logrus.Logger
connectionFactory LDAPConnectionFactory connectionFactory LDAPConnectionFactory
@ -65,10 +66,12 @@ func newLDAPUserProvider(configuration schema.LDAPAuthenticationBackendConfigura
tlsConfig := utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool) tlsConfig := utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool)
var dialOpts ldap.DialOpt var dialOpts = []ldap.DialOpt{
ldap.DialWithDialer(&net.Dialer{Timeout: configuration.Timeout}),
}
if tlsConfig != nil { if tlsConfig != nil {
dialOpts = ldap.DialWithTLSConfig(tlsConfig) dialOpts = append(dialOpts, ldap.DialWithTLSConfig(tlsConfig))
} }
if factory == nil { if factory == nil {
@ -90,7 +93,7 @@ func newLDAPUserProvider(configuration schema.LDAPAuthenticationBackendConfigura
} }
func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnection, error) { func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnection, error) {
conn, err := p.connectionFactory.DialURL(p.configuration.URL, p.dialOpts) conn, err := p.connectionFactory.DialURL(p.configuration.URL, p.dialOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -149,6 +149,9 @@ authentication_backend:
## Scheme can be ldap or ldaps in the format (port optional). ## Scheme can be ldap or ldaps in the format (port optional).
url: ldap://127.0.0.1 url: ldap://127.0.0.1
## The dial timeout for LDAP.
timeout: 5s
## Use StartTLS with the LDAP connection. ## Use StartTLS with the LDAP connection.
start_tls: false start_tls: false

View File

@ -1,22 +1,30 @@
package schema package schema
import "time"
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server. // LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
type LDAPAuthenticationBackendConfiguration struct { type LDAPAuthenticationBackendConfiguration struct {
Implementation string `koanf:"implementation"` Implementation string `koanf:"implementation"`
URL string `koanf:"url"` URL string `koanf:"url"`
Timeout time.Duration `koanf:"timeout"`
StartTLS bool `koanf:"start_tls"`
TLS *TLSConfig `koanf:"tls"`
BaseDN string `koanf:"base_dn"` BaseDN string `koanf:"base_dn"`
AdditionalUsersDN string `koanf:"additional_users_dn"` AdditionalUsersDN string `koanf:"additional_users_dn"`
UsersFilter string `koanf:"users_filter"` UsersFilter string `koanf:"users_filter"`
AdditionalGroupsDN string `koanf:"additional_groups_dn"` AdditionalGroupsDN string `koanf:"additional_groups_dn"`
GroupsFilter string `koanf:"groups_filter"` GroupsFilter string `koanf:"groups_filter"`
GroupNameAttribute string `koanf:"group_name_attribute"` GroupNameAttribute string `koanf:"group_name_attribute"`
UsernameAttribute string `koanf:"username_attribute"` UsernameAttribute string `koanf:"username_attribute"`
MailAttribute string `koanf:"mail_attribute"` MailAttribute string `koanf:"mail_attribute"`
DisplayNameAttribute string `koanf:"display_name_attribute"` DisplayNameAttribute string `koanf:"display_name_attribute"`
User string `koanf:"user"` User string `koanf:"user"`
Password string `koanf:"password"` Password string `koanf:"password"`
StartTLS bool `koanf:"start_tls"`
TLS *TLSConfig `koanf:"tls"`
} }
// FileAuthenticationBackendConfiguration represents the configuration related to file-based backend. // FileAuthenticationBackendConfiguration represents the configuration related to file-based backend.
@ -77,6 +85,7 @@ var DefaultLDAPAuthenticationBackendConfiguration = LDAPAuthenticationBackendCon
MailAttribute: "mail", MailAttribute: "mail",
DisplayNameAttribute: "displayName", DisplayNameAttribute: "displayName",
GroupNameAttribute: "cn", GroupNameAttribute: "cn",
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: "TLS1.2", MinimumVersion: "TLS1.2",
}, },

View File

@ -106,6 +106,10 @@ func validateFileAuthenticationBackendArgon2id(configuration *schema.FileAuthent
} }
func validateLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) { func validateLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.Timeout == 0 {
configuration.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
}
if configuration.Implementation == "" { if configuration.Implementation == "" {
configuration.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation configuration.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
} }

View File

@ -2,6 +2,7 @@ package validator
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -518,26 +519,30 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirec
suite.Assert().False(suite.validator.HasErrors()) suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal( suite.Assert().Equal(
suite.configuration.LDAP.UsersFilter, schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter) suite.configuration.LDAP.Timeout)
suite.Assert().Equal( suite.Assert().Equal(
suite.configuration.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute) suite.configuration.LDAP.UsersFilter)
suite.Assert().Equal( suite.Assert().Equal(
suite.configuration.LDAP.DisplayNameAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute) suite.configuration.LDAP.UsernameAttribute)
suite.Assert().Equal( suite.Assert().Equal(
suite.configuration.LDAP.MailAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute) suite.configuration.LDAP.DisplayNameAttribute)
suite.Assert().Equal( suite.Assert().Equal(
suite.configuration.LDAP.GroupsFilter, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter) suite.configuration.LDAP.MailAttribute)
suite.Assert().Equal( suite.Assert().Equal(
suite.configuration.LDAP.GroupNameAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute) suite.configuration.LDAP.GroupsFilter)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
suite.configuration.LDAP.GroupNameAttribute)
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.configuration.LDAP.Timeout = time.Second * 2
suite.configuration.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))" suite.configuration.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.configuration.LDAP.UsernameAttribute = "cn" suite.configuration.LDAP.UsernameAttribute = "cn"
suite.configuration.LDAP.MailAttribute = "userPrincipalName" suite.configuration.LDAP.MailAttribute = "userPrincipalName"
@ -548,23 +553,26 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefault
ValidateAuthenticationBackend(&suite.configuration, suite.validator) ValidateAuthenticationBackend(&suite.configuration, suite.validator)
suite.Assert().NotEqual( suite.Assert().NotEqual(
suite.configuration.LDAP.UsersFilter, schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter) suite.configuration.LDAP.Timeout)
suite.Assert().NotEqual( suite.Assert().NotEqual(
suite.configuration.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute) suite.configuration.LDAP.UsersFilter)
suite.Assert().NotEqual( suite.Assert().NotEqual(
suite.configuration.LDAP.DisplayNameAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute) suite.configuration.LDAP.UsernameAttribute)
suite.Assert().NotEqual( suite.Assert().NotEqual(
suite.configuration.LDAP.MailAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute) suite.configuration.LDAP.DisplayNameAttribute)
suite.Assert().NotEqual( suite.Assert().NotEqual(
suite.configuration.LDAP.GroupsFilter, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter) suite.configuration.LDAP.MailAttribute)
suite.Assert().NotEqual( suite.Assert().NotEqual(
suite.configuration.LDAP.GroupNameAttribute, schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute) suite.configuration.LDAP.GroupsFilter)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
suite.configuration.LDAP.GroupNameAttribute)
} }
func TestActiveDirectoryAuthenticationBackend(t *testing.T) { func TestActiveDirectoryAuthenticationBackend(t *testing.T) {