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
parent
a3b14871ba
commit
b2a49e1780
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue