mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
[FEATURE] Enhance LDAP/SMTP TLS Configuration and Unify Them (#1557)
* add new directive in the global scope `certificates_directory` which is used to bulk load certs and trust them in Authelia * this is in ADDITION to system certs and are trusted by both LDAP and SMTP * added a shared TLSConfig struct to be used by both SMTP and LDAP, and anything else in the future that requires tuning the TLS * remove usage of deprecated LDAP funcs Dial and DialTLS in favor of DialURL which is also easier to use * use the server name from LDAP URL or SMTP host when validating the certificate unless otherwise defined in the TLS section * added temporary translations from the old names to the new ones for all deprecated options * added docs * updated example configuration * final deprecations to be done in 4.28.0 * doc updates * fix misc linting issues * uniform deprecation notices for ease of final removal * added additional tests covering previously uncovered areas and the new configuration options * add non-fatal to certificate loading when system certs could not be loaded * adjust timeout of Suite ShortTimeouts * add warnings pusher for the StructValidator * make the schema suites uninform * utilize the warnings in the StructValidator * fix test suite usage for skip_verify * extract LDAP filter parsing into it's own function to make it possible to test * test LDAP filter parsing * update ErrorContainer interface * add tests to the StructValidator * add NewTLSConfig test * move baseDN for users/groups into parsed values * add tests to cover many of the outstanding areas in LDAP * add explicit deferred LDAP conn close to UpdatePassword * add some basic testing to SMTP notifier * suggestions from code review
This commit is contained in:
parent
3487fd392e
commit
29a900226d
11
BREAKING.md
11
BREAKING.md
|
@ -6,10 +6,19 @@ recommended not to use the 'latest' Docker image tag blindly but pick a version
|
|||
and read this documentation before upgrading. This is where you will get information about
|
||||
breaking changes and about what you should do to overcome those changes.
|
||||
|
||||
## Breaking in v4.25.0
|
||||
|
||||
### Deprecation Notice(s)
|
||||
* All of these deprecations will be fully removed in release 4.28.0
|
||||
* The SMTP notifiers `trusted_cert` option has been deprecated (replaced by global certificates_directory)
|
||||
* The SMTP notifiers `disable_verify_cert` option has been deprecated (replaced by `notifier.smtp.tls.skip_verify`)
|
||||
* The LDAP authentication backends `skip_verify` option has been deprecated (replaced by `authentication_backend.ldap.tls.skip_verify`)
|
||||
* The LDAP authentication backends `minimum_tls_version` option has been deprecated (replaced by `authentication_backend.ldap.tls.minimum_version`)
|
||||
|
||||
## Breaking in v4.24.0
|
||||
|
||||
### Deprecation Notice(s)
|
||||
* LDAP User Provider Filters (final removal in 4.27.0):
|
||||
* LDAP User Provider Filters (final removal in 4.28.0):
|
||||
* User Filters containing `{0}` are being deprecated and will generate warnings. Replaced with `{input}`.
|
||||
* Group Filters containing `{0}` or `{1}` are being deprecated and will generate warnings.
|
||||
Replaced with `{input}` and `{username}` respectively.
|
||||
|
|
|
@ -35,6 +35,21 @@ func startServer() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
autheliaCertPool, errs, nonFatalErrs := utils.NewX509CertPool(config.CertificatesDirectory, config)
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
logging.Logger().Error(err)
|
||||
}
|
||||
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if len(nonFatalErrs) > 0 {
|
||||
for _, err := range nonFatalErrs {
|
||||
logging.Logger().Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := logging.InitializeLogger(config.LogFormat, config.LogFilePath); err != nil {
|
||||
logging.Logger().Fatalf("Cannot initialize logger: %v", err)
|
||||
}
|
||||
|
@ -55,17 +70,6 @@ func startServer() {
|
|||
logging.Logger().Info("===> Authelia is running in development mode. <===")
|
||||
}
|
||||
|
||||
var userProvider authentication.UserProvider
|
||||
|
||||
switch {
|
||||
case config.AuthenticationBackend.File != nil:
|
||||
userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File)
|
||||
case config.AuthenticationBackend.Ldap != nil:
|
||||
userProvider = authentication.NewLDAPUserProvider(*config.AuthenticationBackend.Ldap)
|
||||
default:
|
||||
logging.Logger().Fatalf("Unrecognized authentication backend")
|
||||
}
|
||||
|
||||
var storageProvider storage.Provider
|
||||
|
||||
switch {
|
||||
|
@ -79,11 +83,22 @@ func startServer() {
|
|||
logging.Logger().Fatalf("Unrecognized storage backend")
|
||||
}
|
||||
|
||||
var userProvider authentication.UserProvider
|
||||
|
||||
switch {
|
||||
case config.AuthenticationBackend.File != nil:
|
||||
userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File)
|
||||
case config.AuthenticationBackend.Ldap != nil:
|
||||
userProvider = authentication.NewLDAPUserProvider(*config.AuthenticationBackend.Ldap, autheliaCertPool)
|
||||
default:
|
||||
logging.Logger().Fatalf("Unrecognized authentication backend")
|
||||
}
|
||||
|
||||
var notifier notification.Notifier
|
||||
|
||||
switch {
|
||||
case config.Notifier.SMTP != nil:
|
||||
notifier = notification.NewSMTPNotifier(*config.Notifier.SMTP)
|
||||
notifier = notification.NewSMTPNotifier(*config.Notifier.SMTP, autheliaCertPool)
|
||||
case config.Notifier.FileSystem != nil:
|
||||
notifier = notification.NewFileNotifier(*config.Notifier.FileSystem)
|
||||
default:
|
||||
|
|
|
@ -107,14 +107,18 @@ authentication_backend:
|
|||
# The url to the ldap server. Scheme can be ldap or ldaps in the format (port optional) <scheme>://<address>[:<port>].
|
||||
url: ldap://127.0.0.1
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
skip_verify: false
|
||||
|
||||
# Use StartTLS with the LDAP connection.
|
||||
start_tls: false
|
||||
|
||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||
minimum_tls_version: TLS1.2
|
||||
tls:
|
||||
# Server Name for certificate validation (in case it's not set correctly in the URL).
|
||||
# server_name: ldap.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||
minimum_version: TLS1.2
|
||||
|
||||
# The base dn for every entries.
|
||||
base_dn: dc=example,dc=com
|
||||
|
@ -406,13 +410,7 @@ notifier:
|
|||
# [Security] By default Authelia will:
|
||||
# - force all SMTP connections over TLS including unauthenticated connections
|
||||
# - use the disable_require_tls boolean value to disable this requirement (only works for unauthenticated connections)
|
||||
# - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates
|
||||
# - trusted_cert option:
|
||||
# - this is a string value, that may specify the path of a PEM format cert, it is completely optional
|
||||
# - if it is not set, a blank string, or an invalid path; will still trust the host machine/containers cert store
|
||||
# - defaults to the host machine (or docker container's) trusted certificate chain for validation
|
||||
# - use the trusted_cert string value to specify the path of a PEM format public cert to trust in addition to the hosts trusted certificates
|
||||
# - use the disable_verify_cert boolean value to disable the validation (prefer the trusted_cert option as it's more secure)
|
||||
# - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates (configure in tls section)
|
||||
smtp:
|
||||
username: test
|
||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
|
@ -427,11 +425,19 @@ notifier:
|
|||
subject: "[Authelia] {title}"
|
||||
# This address is used during the startup check to verify the email configuration is correct. It's not important what it is except if your email server only allows local delivery.
|
||||
startup_check_address: test@authelia.com
|
||||
trusted_cert: ""
|
||||
disable_require_tls: false
|
||||
disable_verify_cert: false
|
||||
disable_html_emails: false
|
||||
|
||||
tls:
|
||||
# Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
# server_name: smtp.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either StartTLS or SMTPS.
|
||||
minimum_version: TLS1.2
|
||||
|
||||
# Sending an email using a Gmail account is as simple as the next section.
|
||||
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
||||
## smtp:
|
||||
|
|
|
@ -52,15 +52,19 @@ authentication_backend:
|
|||
|
||||
# The url to the ldap server. Scheme can be ldap or ldaps in the format (port optional) <scheme>://<address>[:<port>].
|
||||
url: ldap://127.0.0.1
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
skip_verify: false
|
||||
|
||||
# Use StartTLS with the LDAP connection.
|
||||
start_tls: false
|
||||
|
||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||
minimum_tls_version: TLS1.2
|
||||
tls:
|
||||
# Server Name for certificate validation (in case it's not set correctly in the URL).
|
||||
# server_name: ldap.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||
minimum_version: TLS1.2
|
||||
|
||||
# The base dn for every entries.
|
||||
base_dn: dc=example,dc=com
|
||||
|
@ -139,24 +143,15 @@ url: ldap://[fd00:1111:2222:3333::1]
|
|||
|
||||
## TLS Settings
|
||||
|
||||
### Skip Verify
|
||||
|
||||
The key `skip_verify` disables checking the authenticity of the TLS certificate. You should not disable this, instead
|
||||
you should add the certificate that signed the certificate of your LDAP server to the machines certificate PKI trust.
|
||||
For docker you can just add this to the hosts trusted store.
|
||||
|
||||
### Start TLS
|
||||
|
||||
The key `start_tls` enables use of the LDAP StartTLS process which is not commonly used. You should only configure this
|
||||
if you know you need it. The initial connection will be over plain text, and Authelia will try to upgrade it with the
|
||||
LDAP server. LDAPS URL's are slightly more secure.
|
||||
|
||||
### Minimum TLS Version
|
||||
### TLS (section)
|
||||
|
||||
The key `minimum_tls_version` controls the minimum TLS version Authelia will use when opening LDAP connections.
|
||||
The possible values are `TLS1.3`, `TLS1.2`, `TLS1.1`, `TLS1.0`. Anything other than `TLS1.3` or `TLS1.2`
|
||||
are very old and deprecated. You should avoid using these and upgrade your LDAP solution instead of decreasing
|
||||
this value.
|
||||
The key `tls` is a map of options for tuning TLS options. You can see how to configure the tls section [here](../index.md#tls-configuration).
|
||||
|
||||
## Implementation
|
||||
|
||||
|
|
|
@ -54,4 +54,26 @@ denoting the unit of time measurement. The table below describes the units of ti
|
|||
Examples:
|
||||
* 1 hour and 30 minutes: 90m
|
||||
* 1 day: 1d
|
||||
* 10 hours: 10h
|
||||
* 10 hours: 10h
|
||||
|
||||
## TLS Configuration
|
||||
|
||||
Various sections of the configuration use a uniform configuration section called TLS. Notably LDAP and SMTP.
|
||||
This section documents the usage.
|
||||
|
||||
### Server Name
|
||||
|
||||
The key `server_name` overrides the name checked against the certificate in the verification process. Useful if you
|
||||
require to use a direct IP address for the address of the backend service but want to verify a specific SNI.
|
||||
|
||||
### Skip Verify
|
||||
|
||||
The key `skip_verify` completely negates validating the certificate of the backend service. This is not recommended,
|
||||
instead you should tweak the `server_name` option, and the global option [certificates_directory](./miscellaneous.md#certificates-directory).
|
||||
|
||||
### Minimum Version
|
||||
|
||||
The key `minimum_version` controls the minimum TLS version Authelia will use when opening TLS connections.
|
||||
The possible values are `TLS1.3`, `TLS1.2`, `TLS1.1`, `TLS1.0`. Anything other than `TLS1.3` or `TLS1.2`
|
||||
are very old and deprecated. You should avoid using these and upgrade your backend service instead of decreasing
|
||||
this value.
|
|
@ -38,6 +38,16 @@ tls_key: /config/ssl/key.pem
|
|||
tls_cert: /config/ssl/cert.pem
|
||||
```
|
||||
|
||||
## Certificates Directory
|
||||
|
||||
`optional: true`
|
||||
|
||||
This option defines the location of additional certificates to load into the trust chain specifically for Authelia.
|
||||
This currently affects both the SMTP notifier and the LDAP authentication backend. The certificates should all be in the
|
||||
PEM format and end with the extension `.pem` or `.crt`. You can either add the individual certificates public key
|
||||
or the CA public key which signed them (don't add the private key).
|
||||
|
||||
|
||||
## Log
|
||||
|
||||
### Log level
|
||||
|
|
|
@ -7,7 +7,6 @@ nav_order: 2
|
|||
---
|
||||
|
||||
# SMTP
|
||||
|
||||
**Authelia** can send emails to users through an SMTP server.
|
||||
It can be configured as described below.
|
||||
|
||||
|
@ -29,13 +28,7 @@ notifier:
|
|||
# [Security] By default Authelia will:
|
||||
# - force all SMTP connections over TLS including unauthenticated connections
|
||||
# - use the disable_require_tls boolean value to disable this requirement (only works for unauthenticated connections)
|
||||
# - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates
|
||||
# - trusted_cert option:
|
||||
# - this is a string value, that may specify the path of a PEM format cert, it is completely optional
|
||||
# - if it is not set, a blank string, or an invalid path; will still trust the host machine/containers cert store
|
||||
# - defaults to the host machine (or docker container's) trusted certificate chain for validation
|
||||
# - use the trusted_cert string value to specify the path of a PEM format public cert to trust in addition to the hosts trusted certificates
|
||||
# - use the disable_verify_cert boolean value to disable the validation (prefer the trusted_cert option as it's more secure)
|
||||
# - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates (configure in tls section)
|
||||
smtp:
|
||||
username: test
|
||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
|
@ -50,19 +43,37 @@ notifier:
|
|||
subject: "[Authelia] {title}"
|
||||
# This address is used during the startup check to verify the email configuration is correct. It's not important what it is except if your email server only allows local delivery.
|
||||
startup_check_address: test@authelia.com
|
||||
trusted_cert: ""
|
||||
disable_require_tls: false
|
||||
disable_verify_cert: false
|
||||
disable_html_emails: false
|
||||
|
||||
tls:
|
||||
# Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
# server_name: smtp.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either StartTLS or SMTPS.
|
||||
minimum_version: TLS1.2
|
||||
|
||||
# Sending an email using a Gmail account is as simple as the next section.
|
||||
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
||||
## smtp:
|
||||
## username: myaccount@gmail.com
|
||||
## # Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
## password: yourapppassword
|
||||
## sender: admin@example.com
|
||||
## host: smtp.gmail.com
|
||||
## port: 587
|
||||
```
|
||||
|
||||
## Configuration options
|
||||
|
||||
Most configuration options are self-explanatory, however here is an explanation of the ones that may not
|
||||
be as obvious.
|
||||
|
||||
### host
|
||||
If utilising an IPv6 literal address it must be enclosed by square brackets and quoted:
|
||||
|
||||
```yaml
|
||||
host: "[fd00:1111:2222:3333::1]"
|
||||
```
|
||||
|
@ -79,21 +90,14 @@ be included in all emails as it is the internal descriptor for the contents of t
|
|||
For security reasons the default settings for Authelia require the SMTP connection is encrypted by TLS. See [security] for
|
||||
more information. This option disables this measure (not recommended).
|
||||
|
||||
### disable_verify_cert
|
||||
For security reasons Authelia only trusts certificates valid according to the OS's PKI chain. See [security] for more information.
|
||||
This option disables this measure (not recommended).
|
||||
|
||||
### disable_html_emails
|
||||
This option forces Authelia to only send plain text email via the notifier. This is the default for the file based
|
||||
notifier, but some users may wish to use plain text for security reasons.
|
||||
|
||||
### trusted_cert
|
||||
This option allows you to specify the file path to a public key portion of a X509 certificate in order to trust it, or
|
||||
certificates signed with the private key portion of the X509 certificate. This is an alternative to `disable_verify_cert`
|
||||
that is much more secure. This is not required if your certificate is trusted by the operating system PKI.
|
||||
### TLS (section)
|
||||
The key `tls` is a map of options for tuning TLS options. You can see how to configure the tls section [here](../index.md#tls-configuration).
|
||||
|
||||
## Using Gmail
|
||||
|
||||
You need to generate an app password in order to use Gmail SMTP servers. The process is
|
||||
described [here](https://support.google.com/accounts/answer/185833?hl=en)
|
||||
|
||||
|
@ -109,7 +113,6 @@ notifier:
|
|||
```
|
||||
|
||||
## Loading a password from a secret instead of inside the configuration
|
||||
|
||||
Password can also be defined using a [secret](../secrets.md).
|
||||
|
||||
[security]: ../../security/measures.md#notifier-security-measures-smtp
|
|
@ -57,8 +57,7 @@ func (lc *LDAPConnectionImpl) StartTLS(config *tls.Config) error {
|
|||
|
||||
// LDAPConnectionFactory an interface of factory of ldap connections.
|
||||
type LDAPConnectionFactory interface {
|
||||
DialTLS(network, addr string, config *tls.Config) (LDAPConnection, error)
|
||||
Dial(network, addr string) (LDAPConnection, error)
|
||||
DialURL(addr string, opts ldap.DialOpt) (LDAPConnection, error)
|
||||
}
|
||||
|
||||
// LDAPConnectionFactoryImpl the production implementation of an ldap connection factory.
|
||||
|
@ -69,19 +68,9 @@ func NewLDAPConnectionFactoryImpl() *LDAPConnectionFactoryImpl {
|
|||
return &LDAPConnectionFactoryImpl{}
|
||||
}
|
||||
|
||||
// DialTLS contact ldap server over TLS.
|
||||
func (lcf *LDAPConnectionFactoryImpl) DialTLS(network, addr string, config *tls.Config) (LDAPConnection, error) {
|
||||
conn, err := ldap.DialTLS(network, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewLDAPConnectionImpl(conn), nil
|
||||
}
|
||||
|
||||
// Dial contact ldap server over raw tcp.
|
||||
func (lcf *LDAPConnectionFactoryImpl) Dial(network, addr string) (LDAPConnection, error) {
|
||||
conn, err := ldap.Dial(network, addr)
|
||||
// DialURL creates a connection from an LDAP URL when successful.
|
||||
func (lcf *LDAPConnectionFactoryImpl) DialURL(addr string, opts ldap.DialOpt) (LDAPConnection, error) {
|
||||
conn, err := ldap.DialURL(addr, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ package authentication
|
|||
|
||||
import (
|
||||
tls "crypto/tls"
|
||||
reflect "reflect"
|
||||
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockLDAPConnection is a mock of LDAPConnection interface
|
||||
|
@ -124,32 +125,17 @@ func (m *MockLDAPConnectionFactory) EXPECT() *MockLDAPConnectionFactoryMockRecor
|
|||
return m.recorder
|
||||
}
|
||||
|
||||
// DialTLS mocks base method
|
||||
func (m *MockLDAPConnectionFactory) DialTLS(network, addr string, config *tls.Config) (LDAPConnection, error) {
|
||||
// DialURL mocks base method
|
||||
func (m *MockLDAPConnectionFactory) DialURL(addr string, opts ldap.DialOpt) (LDAPConnection, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DialTLS", network, addr, config)
|
||||
ret := m.ctrl.Call(m, "DialURL", addr, opts)
|
||||
ret0, _ := ret[0].(LDAPConnection)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DialTLS indicates an expected call of DialTLS
|
||||
func (mr *MockLDAPConnectionFactoryMockRecorder) DialTLS(network, addr, config interface{}) *gomock.Call {
|
||||
// DialURL indicates an expected call of DialURL
|
||||
func (mr *MockLDAPConnectionFactoryMockRecorder) DialURL(addr, opts interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialTLS", reflect.TypeOf((*MockLDAPConnectionFactory)(nil).DialTLS), network, addr, config)
|
||||
}
|
||||
|
||||
// Dial mocks base method
|
||||
func (m *MockLDAPConnectionFactory) Dial(network, addr string) (LDAPConnection, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Dial", network, addr)
|
||||
ret0, _ := ret[0].(LDAPConnection)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Dial indicates an expected call of Dial
|
||||
func (mr *MockLDAPConnectionFactoryMockRecorder) Dial(network, addr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dial", reflect.TypeOf((*MockLDAPConnectionFactory)(nil).Dial), network, addr)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialURL", reflect.TypeOf((*MockLDAPConnectionFactory)(nil).DialURL), addr, opts)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package authentication
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
|
@ -16,116 +16,126 @@ import (
|
|||
|
||||
// LDAPUserProvider is a provider using a LDAP or AD as a user database.
|
||||
type LDAPUserProvider struct {
|
||||
configuration schema.LDAPAuthenticationBackendConfiguration
|
||||
tlsConfig *tls.Config
|
||||
|
||||
configuration schema.LDAPAuthenticationBackendConfiguration
|
||||
tlsConfig *tls.Config
|
||||
dialOpts ldap.DialOpt
|
||||
connectionFactory LDAPConnectionFactory
|
||||
usersDN string
|
||||
groupsDN string
|
||||
}
|
||||
|
||||
// NewLDAPUserProvider creates a new instance of LDAPUserProvider.
|
||||
func NewLDAPUserProvider(configuration schema.LDAPAuthenticationBackendConfiguration) *LDAPUserProvider {
|
||||
minimumTLSVersion, _ := utils.TLSStringToTLSConfigVersion(configuration.MinimumTLSVersion)
|
||||
|
||||
// TODO: RELEASE-4.27.0 Deprecated Completely in this release.
|
||||
logger := logging.Logger()
|
||||
|
||||
if strings.Contains(configuration.UsersFilter, "{0}") {
|
||||
logger.Warnf("DEPRECATION NOTICE: LDAP Users Filter will no longer support replacing `{0}` in 4.27.0. Please use `{input}` instead.")
|
||||
|
||||
configuration.UsersFilter = strings.ReplaceAll(configuration.UsersFilter, "{0}", "{input}")
|
||||
func NewLDAPUserProvider(configuration schema.LDAPAuthenticationBackendConfiguration, certPool *x509.CertPool) *LDAPUserProvider {
|
||||
if configuration.TLS == nil {
|
||||
configuration.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
||||
}
|
||||
|
||||
if strings.Contains(configuration.GroupsFilter, "{0}") {
|
||||
logger.Warnf("DEPRECATION NOTICE: LDAP Groups Filter will no longer support replacing `{0}` in 4.27.0. Please use `{input}` instead.")
|
||||
tlsConfig := utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool)
|
||||
|
||||
configuration.GroupsFilter = strings.ReplaceAll(configuration.GroupsFilter, "{0}", "{input}")
|
||||
var dialOpts ldap.DialOpt
|
||||
|
||||
if tlsConfig != nil {
|
||||
dialOpts = ldap.DialWithTLSConfig(tlsConfig)
|
||||
}
|
||||
|
||||
if strings.Contains(configuration.GroupsFilter, "{1}") {
|
||||
logger.Warnf("DEPRECATION NOTICE: LDAP Groups Filter will no longer support replacing `{1}` in 4.27.0. Please use `{username}` instead.")
|
||||
|
||||
configuration.GroupsFilter = strings.ReplaceAll(configuration.GroupsFilter, "{1}", "{username}")
|
||||
}
|
||||
// TODO: RELEASE-4.27.0 Deprecated Completely in this release.
|
||||
|
||||
configuration.UsersFilter = strings.ReplaceAll(configuration.UsersFilter, "{username_attribute}", configuration.UsernameAttribute)
|
||||
configuration.UsersFilter = strings.ReplaceAll(configuration.UsersFilter, "{mail_attribute}", configuration.MailAttribute)
|
||||
configuration.UsersFilter = strings.ReplaceAll(configuration.UsersFilter, "{display_name_attribute}", configuration.DisplayNameAttribute)
|
||||
|
||||
return &LDAPUserProvider{
|
||||
configuration: configuration,
|
||||
tlsConfig: &tls.Config{InsecureSkipVerify: configuration.SkipVerify, MinVersion: minimumTLSVersion}, //nolint:gosec // Disabling InsecureSkipVerify is an informed choice by users.
|
||||
|
||||
provider := &LDAPUserProvider{
|
||||
configuration: configuration,
|
||||
tlsConfig: tlsConfig,
|
||||
dialOpts: dialOpts,
|
||||
connectionFactory: NewLDAPConnectionFactoryImpl(),
|
||||
}
|
||||
|
||||
provider.parseDynamicConfiguration()
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with existing factory.
|
||||
func NewLDAPUserProviderWithFactory(configuration schema.LDAPAuthenticationBackendConfiguration, connectionFactory LDAPConnectionFactory) *LDAPUserProvider {
|
||||
provider := NewLDAPUserProvider(configuration)
|
||||
func NewLDAPUserProviderWithFactory(configuration schema.LDAPAuthenticationBackendConfiguration, certPool *x509.CertPool, connectionFactory LDAPConnectionFactory) *LDAPUserProvider {
|
||||
provider := NewLDAPUserProvider(configuration, certPool)
|
||||
provider.connectionFactory = connectionFactory
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnection, error) {
|
||||
var newConnection LDAPConnection
|
||||
func (p *LDAPUserProvider) parseDynamicConfiguration() {
|
||||
logger := logging.Logger() // Deprecated: This is temporary for deprecation notice purposes. TODO: Remove in 4.28.
|
||||
|
||||
ldapURL, err := url.Parse(p.configuration.URL)
|
||||
// Deprecated: This is temporary for deprecation notice purposes. TODO: Remove in 4.28.
|
||||
if strings.Contains(p.configuration.UsersFilter, "{0}") {
|
||||
logger.Warnf("DEPRECATION NOTICE: LDAP Users Filter will no longer support replacing `{0}` in 4.28.0. Please use `{input}` instead.")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse URL to LDAP: %s", ldapURL)
|
||||
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{0}", "{input}")
|
||||
}
|
||||
|
||||
if ldapURL.Scheme == "ldaps" {
|
||||
logging.Logger().Trace("LDAP client starts a TLS session")
|
||||
// Deprecated: This is temporary for deprecation notice purposes. TODO: Remove in 4.28.
|
||||
if strings.Contains(p.configuration.GroupsFilter, "{0}") {
|
||||
logger.Warnf("DEPRECATION NOTICE: LDAP Groups Filter will no longer support replacing `{0}` in 4.28.0. Please use `{input}` instead.")
|
||||
|
||||
conn, err := p.connectionFactory.DialTLS("tcp", ldapURL.Host, p.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.configuration.GroupsFilter = strings.ReplaceAll(p.configuration.GroupsFilter, "{0}", "{input}")
|
||||
}
|
||||
|
||||
newConnection = conn
|
||||
// Deprecated: This is temporary for deprecation notice purposes. TODO: Remove in 4.28.
|
||||
if strings.Contains(p.configuration.GroupsFilter, "{1}") {
|
||||
logger.Warnf("DEPRECATION NOTICE: LDAP Groups Filter will no longer support replacing `{1}` in 4.28.0. Please use `{username}` instead.")
|
||||
|
||||
p.configuration.GroupsFilter = strings.ReplaceAll(p.configuration.GroupsFilter, "{1}", "{username}")
|
||||
}
|
||||
|
||||
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{username_attribute}", p.configuration.UsernameAttribute)
|
||||
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{mail_attribute}", p.configuration.MailAttribute)
|
||||
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{display_name_attribute}", p.configuration.DisplayNameAttribute)
|
||||
|
||||
if p.configuration.AdditionalUsersDN != "" {
|
||||
p.usersDN = p.configuration.AdditionalUsersDN + "," + p.configuration.BaseDN
|
||||
} else {
|
||||
logging.Logger().Trace("LDAP client starts a session over raw TCP")
|
||||
conn, err := p.connectionFactory.Dial("tcp", ldapURL.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newConnection = conn
|
||||
p.usersDN = p.configuration.BaseDN
|
||||
}
|
||||
|
||||
if p.configuration.StartTLS {
|
||||
if err := newConnection.StartTLS(p.tlsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.configuration.AdditionalGroupsDN != "" {
|
||||
p.groupsDN = p.configuration.AdditionalGroupsDN + "," + p.configuration.BaseDN
|
||||
} else {
|
||||
p.groupsDN = p.configuration.BaseDN
|
||||
}
|
||||
}
|
||||
|
||||
if err := newConnection.Bind(userDN, password); err != nil {
|
||||
func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnection, error) {
|
||||
conn, err := p.connectionFactory.DialURL(p.configuration.URL, p.dialOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newConnection, nil
|
||||
if p.configuration.StartTLS {
|
||||
if err := conn.StartTLS(p.tlsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := conn.Bind(userDN, password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// CheckUserPassword checks if provided password matches for the given user.
|
||||
func (p *LDAPUserProvider) CheckUserPassword(inputUsername string, password string) (bool, error) {
|
||||
adminClient, err := p.connect(p.configuration.User, p.configuration.Password)
|
||||
conn, err := p.connect(p.configuration.User, p.configuration.Password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer adminClient.Close()
|
||||
defer conn.Close()
|
||||
|
||||
profile, err := p.getUserProfile(adminClient, inputUsername)
|
||||
profile, err := p.getUserProfile(conn, inputUsername)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
conn, err := p.connect(profile.DN, password)
|
||||
userConn, err := p.connect(profile.DN, password)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Authentication of user %s failed. Cause: %s", inputUsername, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
defer userConn.Close()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
@ -159,11 +169,6 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
|
|||
userFilter := p.resolveUsersFilter(p.configuration.UsersFilter, inputUsername)
|
||||
logging.Logger().Tracef("Computed user filter is %s", userFilter)
|
||||
|
||||
baseDN := p.configuration.BaseDN
|
||||
if p.configuration.AdditionalUsersDN != "" {
|
||||
baseDN = p.configuration.AdditionalUsersDN + "," + baseDN
|
||||
}
|
||||
|
||||
attributes := []string{"dn",
|
||||
p.configuration.DisplayNameAttribute,
|
||||
p.configuration.MailAttribute,
|
||||
|
@ -171,7 +176,7 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
|
|||
|
||||
// Search for the given username.
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
p.usersDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
1, 0, false, userFilter, attributes, nil,
|
||||
)
|
||||
|
||||
|
@ -252,14 +257,9 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
|
|||
|
||||
logging.Logger().Tracef("Computed groups filter is %s", groupsFilter)
|
||||
|
||||
groupBaseDN := p.configuration.BaseDN
|
||||
if p.configuration.AdditionalGroupsDN != "" {
|
||||
groupBaseDN = p.configuration.AdditionalGroupsDN + "," + groupBaseDN
|
||||
}
|
||||
|
||||
// Search for the given username.
|
||||
searchGroupRequest := ldap.NewSearchRequest(
|
||||
groupBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
p.groupsDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
0, 0, false, groupsFilter, []string{p.configuration.GroupNameAttribute}, nil,
|
||||
)
|
||||
|
||||
|
@ -290,13 +290,13 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
|
|||
|
||||
// UpdatePassword update the password of the given user.
|
||||
func (p *LDAPUserProvider) UpdatePassword(inputUsername string, newPassword string) error {
|
||||
client, err := p.connect(p.configuration.User, p.configuration.Password)
|
||||
|
||||
conn, err := p.connect(p.configuration.User, p.configuration.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to update password. Cause: %s", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
profile, err := p.getUserProfile(client, inputUsername)
|
||||
profile, err := p.getUserProfile(conn, inputUsername)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to update password. Cause: %s", err)
|
||||
|
@ -315,7 +315,7 @@ func (p *LDAPUserProvider) UpdatePassword(inputUsername string, newPassword stri
|
|||
modifyRequest.Replace("userPassword", []string{newPassword})
|
||||
}
|
||||
|
||||
err = client.Modify(modifyRequest)
|
||||
err = conn.Modify(modifyRequest)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to update password. Cause: %s", err)
|
||||
|
|
|
@ -19,19 +19,22 @@ func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(nil)
|
||||
|
||||
_, err := ldap.connect("cn=admin,dc=example,dc=com", "password")
|
||||
_, err := ldapClient.connect("cn=admin,dc=example,dc=com", "password")
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
@ -43,19 +46,22 @@ func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
DialTLS(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389"), gomock.Any()).
|
||||
DialURL(gomock.Eq("ldaps://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(nil)
|
||||
|
||||
_, err := ldap.connect("cn=admin,dc=example,dc=com", "password")
|
||||
_, err := ldapClient.connect("cn=admin,dc=example,dc=com", "password")
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
@ -65,26 +71,30 @@ func TestEscapeSpecialCharsFromUserInput(t *testing.T) {
|
|||
defer ctrl.Finish()
|
||||
|
||||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
}, mockFactory)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
// No escape
|
||||
assert.Equal(t, "xyz", ldap.ldapEscape("xyz"))
|
||||
assert.Equal(t, "xyz", ldapClient.ldapEscape("xyz"))
|
||||
|
||||
// Escape
|
||||
assert.Equal(t, "test\\,abc", ldap.ldapEscape("test,abc"))
|
||||
assert.Equal(t, "test\\5cabc", ldap.ldapEscape("test\\abc"))
|
||||
assert.Equal(t, "test\\2aabc", ldap.ldapEscape("test*abc"))
|
||||
assert.Equal(t, "test \\28abc\\29", ldap.ldapEscape("test (abc)"))
|
||||
assert.Equal(t, "test\\#abc", ldap.ldapEscape("test#abc"))
|
||||
assert.Equal(t, "test\\+abc", ldap.ldapEscape("test+abc"))
|
||||
assert.Equal(t, "test\\<abc", ldap.ldapEscape("test<abc"))
|
||||
assert.Equal(t, "test\\>abc", ldap.ldapEscape("test>abc"))
|
||||
assert.Equal(t, "test\\;abc", ldap.ldapEscape("test;abc"))
|
||||
assert.Equal(t, "test\\\"abc", ldap.ldapEscape("test\"abc"))
|
||||
assert.Equal(t, "test\\=abc", ldap.ldapEscape("test=abc"))
|
||||
assert.Equal(t, "test\\,\\5c\\28abc\\29", ldap.ldapEscape("test,\\(abc)"))
|
||||
assert.Equal(t, "test\\,abc", ldapClient.ldapEscape("test,abc"))
|
||||
assert.Equal(t, "test\\5cabc", ldapClient.ldapEscape("test\\abc"))
|
||||
assert.Equal(t, "test\\2aabc", ldapClient.ldapEscape("test*abc"))
|
||||
assert.Equal(t, "test \\28abc\\29", ldapClient.ldapEscape("test (abc)"))
|
||||
assert.Equal(t, "test\\#abc", ldapClient.ldapEscape("test#abc"))
|
||||
assert.Equal(t, "test\\+abc", ldapClient.ldapEscape("test+abc"))
|
||||
assert.Equal(t, "test\\<abc", ldapClient.ldapEscape("test<abc"))
|
||||
assert.Equal(t, "test\\>abc", ldapClient.ldapEscape("test>abc"))
|
||||
assert.Equal(t, "test\\;abc", ldapClient.ldapEscape("test;abc"))
|
||||
assert.Equal(t, "test\\\"abc", ldapClient.ldapEscape("test\"abc"))
|
||||
assert.Equal(t, "test\\=abc", ldapClient.ldapEscape("test=abc"))
|
||||
assert.Equal(t, "test\\,\\5c\\28abc\\29", ldapClient.ldapEscape("test,\\(abc)"))
|
||||
}
|
||||
|
||||
func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {
|
||||
|
@ -92,10 +102,14 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {
|
|||
defer ctrl.Finish()
|
||||
|
||||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))",
|
||||
}, mockFactory)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
profile := ldapUserProfile{
|
||||
DN: "cn=john (external),dc=example,dc=com",
|
||||
|
@ -104,10 +118,10 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {
|
|||
Emails: []string{"john.doe@authelia.com"},
|
||||
}
|
||||
|
||||
filter, _ := ldap.resolveGroupsFilter("john", &profile)
|
||||
filter, _ := ldapClient.resolveGroupsFilter("john", &profile)
|
||||
assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john))", filter)
|
||||
|
||||
filter, _ = ldap.resolveGroupsFilter("john#=(abc,def)", &profile)
|
||||
filter, _ = ldapClient.resolveGroupsFilter("john#=(abc,def)", &profile)
|
||||
assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john\\#\\=\\28abc\\,def\\29))", filter)
|
||||
}
|
||||
|
||||
|
@ -135,17 +149,20 @@ func TestShouldEscapeUserInput(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
Password: "password",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
Password: "password",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockConn.EXPECT().
|
||||
// Here we ensure that the input has been correctly escaped.
|
||||
|
@ -164,17 +181,20 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
UsernameAttribute: "uid",
|
||||
UsersFilter: "(&({username_attribute}={input})(&(objectCategory=person)(objectClass=user)))",
|
||||
Password: "password",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
UsernameAttribute: "uid",
|
||||
UsersFilter: "(&({username_attribute}={input})(&(objectCategory=person)(objectClass=user)))",
|
||||
Password: "password",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockConn.EXPECT().
|
||||
Search(NewSearchRequestMatcher("(&(uid=john)(&(objectCategory=person)(objectClass=user)))")).
|
||||
|
@ -206,20 +226,23 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
|
@ -274,18 +297,21 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
|
@ -331,20 +357,23 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
|
@ -392,6 +421,209 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
|
|||
assert.Equal(t, details.Username, "John")
|
||||
}
|
||||
|
||||
func TestShouldUpdateUserPassword(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
modifyRequest := ldap.NewModifyRequest("uid=test,dc=example,dc=com", nil)
|
||||
modifyRequest.Replace("userPassword", []string{"password"})
|
||||
|
||||
gomock.InOrder(
|
||||
mockFactory.EXPECT().
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil),
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(nil),
|
||||
mockConn.EXPECT().
|
||||
Search(gomock.Any()).
|
||||
Return(&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: "uid=test,dc=example,dc=com",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "displayname",
|
||||
Values: []string{"John Doe"},
|
||||
},
|
||||
{
|
||||
Name: "mail",
|
||||
Values: []string{"test@example.com"},
|
||||
},
|
||||
{
|
||||
Name: "uid",
|
||||
Values: []string{"John"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil),
|
||||
mockConn.EXPECT().
|
||||
Modify(modifyRequest).
|
||||
Return(nil),
|
||||
mockConn.EXPECT().
|
||||
Close(),
|
||||
)
|
||||
|
||||
err := ldapClient.UpdatePassword("john", "password")
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestShouldCheckValidUserPassword(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
gomock.InOrder(
|
||||
mockFactory.EXPECT().
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil),
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(nil),
|
||||
mockConn.EXPECT().
|
||||
Search(gomock.Any()).
|
||||
Return(&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: "uid=test,dc=example,dc=com",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "displayname",
|
||||
Values: []string{"John Doe"},
|
||||
},
|
||||
{
|
||||
Name: "mail",
|
||||
Values: []string{"test@example.com"},
|
||||
},
|
||||
{
|
||||
Name: "uid",
|
||||
Values: []string{"John"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil),
|
||||
mockFactory.EXPECT().
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil),
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("uid=test,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(nil),
|
||||
mockConn.EXPECT().
|
||||
Close().Times(2),
|
||||
)
|
||||
|
||||
valid, err := ldapClient.CheckUserPassword("john", "password")
|
||||
|
||||
assert.True(t, valid)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestShouldCheckInvalidUserPassword(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
gomock.InOrder(
|
||||
mockFactory.EXPECT().
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil),
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(nil),
|
||||
mockConn.EXPECT().
|
||||
Search(gomock.Any()).
|
||||
Return(&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: "uid=test,dc=example,dc=com",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "displayname",
|
||||
Values: []string{"John Doe"},
|
||||
},
|
||||
{
|
||||
Name: "mail",
|
||||
Values: []string{"test@example.com"},
|
||||
},
|
||||
{
|
||||
Name: "uid",
|
||||
Values: []string{"John"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil),
|
||||
mockFactory.EXPECT().
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil),
|
||||
mockConn.EXPECT().
|
||||
Bind(gomock.Eq("uid=test,dc=example,dc=com"), gomock.Eq("password")).
|
||||
Return(errors.New("Invalid username or password")),
|
||||
mockConn.EXPECT().
|
||||
Close(),
|
||||
)
|
||||
|
||||
valid, err := ldapClient.CheckUserPassword("john", "password")
|
||||
|
||||
assert.False(t, valid)
|
||||
require.EqualError(t, err, "Authentication of user john failed. Cause: Invalid username or password")
|
||||
}
|
||||
|
||||
func TestShouldCallStartTLSWhenEnabled(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
@ -399,21 +631,24 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
|
@ -464,6 +699,36 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {
|
|||
assert.Equal(t, details.Username, "john")
|
||||
}
|
||||
|
||||
func TestShouldParseDynamicConfiguration(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "(&(|({username_attribute}={0})({mail_attribute}={0})({display_name_attribute}={0}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))",
|
||||
GroupsFilter: "(&(|(member={dn})(member={0})(member={1}))(objectClass=group))",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
AdditionalGroupsDN: "ou=groups",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
assert.Equal(t, "(&(|(uid={input})(mail={input})(displayname={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))", ldapClient.configuration.UsersFilter)
|
||||
assert.Equal(t, "(&(|(member={dn})(member={input})(member={username}))(objectClass=group))", ldapClient.configuration.GroupsFilter)
|
||||
assert.Equal(t, "ou=users,dc=example,dc=com", ldapClient.usersDN)
|
||||
assert.Equal(t, "ou=groups,dc=example,dc=com", ldapClient.groupsDN)
|
||||
}
|
||||
|
||||
func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
@ -471,22 +736,27 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
SkipVerify: true,
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldap://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
TLS: &schema.TLSConfig{
|
||||
SkipVerify: true,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
|
||||
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
|
@ -544,22 +814,27 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) {
|
|||
mockFactory := NewMockLDAPConnectionFactory(ctrl)
|
||||
mockConn := NewMockLDAPConnection(ctrl)
|
||||
|
||||
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
SkipVerify: true,
|
||||
}, mockFactory)
|
||||
ldapClient := NewLDAPUserProviderWithFactory(
|
||||
schema.LDAPAuthenticationBackendConfiguration{
|
||||
URL: "ldaps://127.0.0.1:389",
|
||||
User: "cn=admin,dc=example,dc=com",
|
||||
Password: "password",
|
||||
UsernameAttribute: "uid",
|
||||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
UsersFilter: "uid={input}",
|
||||
AdditionalUsersDN: "ou=users",
|
||||
BaseDN: "dc=example,dc=com",
|
||||
StartTLS: true,
|
||||
TLS: &schema.TLSConfig{
|
||||
SkipVerify: true,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
mockFactory)
|
||||
|
||||
mockFactory.EXPECT().
|
||||
DialTLS(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389"), gomock.Any()).
|
||||
DialURL(gomock.Eq("ldaps://127.0.0.1:389"), gomock.Any()).
|
||||
Return(mockConn, nil)
|
||||
|
||||
mockConn.EXPECT().
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/internal/configuration/validator"
|
||||
"github.com/authelia/authelia/internal/logging"
|
||||
)
|
||||
|
||||
// Read a YAML configuration and create a Configuration object out of it.
|
||||
|
@ -78,6 +79,12 @@ func Read(configPath string) (*schema.Configuration, []error) {
|
|||
return nil, val.Errors()
|
||||
}
|
||||
|
||||
if val.HasWarnings() {
|
||||
for _, warn := range val.Warnings() {
|
||||
logging.Logger().Warnf(warn.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return &configuration, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,22 +2,23 @@ package schema
|
|||
|
||||
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
|
||||
type LDAPAuthenticationBackendConfiguration struct {
|
||||
Implementation string `mapstructure:"implementation"`
|
||||
URL string `mapstructure:"url"`
|
||||
SkipVerify bool `mapstructure:"skip_verify"`
|
||||
StartTLS bool `mapstructure:"start_tls"`
|
||||
MinimumTLSVersion string `mapstructure:"minimum_tls_version"`
|
||||
BaseDN string `mapstructure:"base_dn"`
|
||||
AdditionalUsersDN string `mapstructure:"additional_users_dn"`
|
||||
UsersFilter string `mapstructure:"users_filter"`
|
||||
AdditionalGroupsDN string `mapstructure:"additional_groups_dn"`
|
||||
GroupsFilter string `mapstructure:"groups_filter"`
|
||||
GroupNameAttribute string `mapstructure:"group_name_attribute"`
|
||||
UsernameAttribute string `mapstructure:"username_attribute"`
|
||||
MailAttribute string `mapstructure:"mail_attribute"`
|
||||
DisplayNameAttribute string `mapstructure:"display_name_attribute"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Implementation string `mapstructure:"implementation"`
|
||||
URL string `mapstructure:"url"`
|
||||
BaseDN string `mapstructure:"base_dn"`
|
||||
AdditionalUsersDN string `mapstructure:"additional_users_dn"`
|
||||
UsersFilter string `mapstructure:"users_filter"`
|
||||
AdditionalGroupsDN string `mapstructure:"additional_groups_dn"`
|
||||
GroupsFilter string `mapstructure:"groups_filter"`
|
||||
GroupNameAttribute string `mapstructure:"group_name_attribute"`
|
||||
UsernameAttribute string `mapstructure:"username_attribute"`
|
||||
MailAttribute string `mapstructure:"mail_attribute"`
|
||||
DisplayNameAttribute string `mapstructure:"display_name_attribute"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
StartTLS bool `mapstructure:"start_tls"`
|
||||
TLS *TLSConfig `mapstructure:"tls"`
|
||||
SkipVerify *bool `mapstructure:"skip_verify"` // Deprecated: Replaced with LDAPAuthenticationBackendConfiguration.TLS.SkipVerify. TODO: Remove in 4.28.
|
||||
MinimumTLSVersion string `mapstructure:"minimum_tls_version"` // Deprecated: Replaced with LDAPAuthenticationBackendConfiguration.TLS.MinimumVersion. TODO: Remove in 4.28.
|
||||
}
|
||||
|
||||
// FileAuthenticationBackendConfiguration represents the configuration related to file-based backend.
|
||||
|
@ -78,7 +79,9 @@ var DefaultLDAPAuthenticationBackendConfiguration = LDAPAuthenticationBackendCon
|
|||
MailAttribute: "mail",
|
||||
DisplayNameAttribute: "displayname",
|
||||
GroupNameAttribute: "cn",
|
||||
MinimumTLSVersion: "TLS1.2",
|
||||
TLS: &TLSConfig{
|
||||
MinimumVersion: "TLS1.2",
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration represents the default LDAP config for the MSAD Implementation.
|
||||
|
|
|
@ -6,6 +6,7 @@ type Configuration struct {
|
|||
Port int `mapstructure:"port"`
|
||||
TLSCert string `mapstructure:"tls_cert"`
|
||||
TLSKey string `mapstructure:"tls_key"`
|
||||
CertificatesDirectory string `mapstructure:"certificates_directory"`
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
LogFormat string `mapstructure:"log_format"`
|
||||
LogFilePath string `mapstructure:"log_file_path"`
|
||||
|
|
|
@ -7,18 +7,19 @@ type FileSystemNotifierConfiguration struct {
|
|||
|
||||
// SMTPNotifierConfiguration represents the configuration of the SMTP server to send emails with.
|
||||
type SMTPNotifierConfiguration struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Identifier string `mapstructure:"identifier"`
|
||||
Sender string `mapstructure:"sender"`
|
||||
Subject string `mapstructure:"subject"`
|
||||
TrustedCert string `mapstructure:"trusted_cert"`
|
||||
StartupCheckAddress string `mapstructure:"startup_check_address"`
|
||||
DisableVerifyCert bool `mapstructure:"disable_verify_cert"`
|
||||
DisableRequireTLS bool `mapstructure:"disable_require_tls"`
|
||||
DisableHTMLEmails bool `mapstructure:"disable_html_emails"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Identifier string `mapstructure:"identifier"`
|
||||
Sender string `mapstructure:"sender"`
|
||||
Subject string `mapstructure:"subject"`
|
||||
StartupCheckAddress string `mapstructure:"startup_check_address"`
|
||||
DisableRequireTLS bool `mapstructure:"disable_require_tls"`
|
||||
DisableHTMLEmails bool `mapstructure:"disable_html_emails"`
|
||||
TLS *TLSConfig `mapstructure:"tls"`
|
||||
TrustedCert string `mapstructure:"trusted_cert"` // Deprecated: Replaced with Global Option CertificatesDirectory. TODO: Remove in 4.28.
|
||||
DisableVerifyCert *bool `mapstructure:"disable_verify_cert"` // Deprecated: Replaced with LDAPAuthenticationBackendConfiguration.TLS.SkipVerify. TODO: Remove in 4.28.
|
||||
}
|
||||
|
||||
// NotifierConfiguration represents the configuration of the notifier to use when sending notifications to users.
|
||||
|
@ -32,4 +33,7 @@ type NotifierConfiguration struct {
|
|||
var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{
|
||||
Subject: "[Authelia] {title}",
|
||||
Identifier: "localhost",
|
||||
TLS: &TLSConfig{
|
||||
MinimumVersion: "TLS1.2",
|
||||
},
|
||||
}
|
||||
|
|
8
internal/configuration/schema/shared.go
Normal file
8
internal/configuration/schema/shared.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package schema
|
||||
|
||||
// TLSConfig is a representation of the TLS configuration.
|
||||
type TLSConfig struct {
|
||||
MinimumVersion string `mapstructure:"minimum_version"`
|
||||
SkipVerify bool `mapstructure:"skip_verify"`
|
||||
ServerName string `mapstructure:"server_name"`
|
||||
}
|
|
@ -10,8 +10,11 @@ import (
|
|||
// ErrorContainer represents a container where we can add errors and retrieve them.
|
||||
type ErrorContainer interface {
|
||||
Push(err error)
|
||||
PushWarning(err error)
|
||||
HasErrors() bool
|
||||
HasWarnings() bool
|
||||
Errors() []error
|
||||
Warnings() []error
|
||||
}
|
||||
|
||||
// Validator represents the validator interface.
|
||||
|
@ -110,33 +113,51 @@ func (v *Validator) Errors() map[string][]error {
|
|||
|
||||
// StructValidator is a validator for structs.
|
||||
type StructValidator struct {
|
||||
errors []error
|
||||
errors []error
|
||||
warnings []error
|
||||
}
|
||||
|
||||
// NewStructValidator is a constructor of struct validator.
|
||||
func NewStructValidator() *StructValidator {
|
||||
val := new(StructValidator)
|
||||
val.errors = make([]error, 0)
|
||||
val.warnings = make([]error, 0)
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// Push an error in the validator.
|
||||
// Push an error to the validator.
|
||||
func (v *StructValidator) Push(err error) {
|
||||
v.errors = append(v.errors, err)
|
||||
}
|
||||
|
||||
// PushWarning error to the validator.
|
||||
func (v *StructValidator) PushWarning(err error) {
|
||||
v.warnings = append(v.warnings, err)
|
||||
}
|
||||
|
||||
// HasErrors checks whether the validator contains errors.
|
||||
func (v *StructValidator) HasErrors() bool {
|
||||
return len(v.errors) > 0
|
||||
}
|
||||
|
||||
// HasWarnings checks whether the validator contains warning errors.
|
||||
func (v *StructValidator) HasWarnings() bool {
|
||||
return len(v.warnings) > 0
|
||||
}
|
||||
|
||||
// Errors returns the errors.
|
||||
func (v *StructValidator) Errors() []error {
|
||||
return v.errors
|
||||
}
|
||||
|
||||
// Clear errors.
|
||||
// Warnings returns the warnings.
|
||||
func (v *StructValidator) Warnings() []error {
|
||||
return v.warnings
|
||||
}
|
||||
|
||||
// Clear errors and warnings.
|
||||
func (v *StructValidator) Clear() {
|
||||
v.errors = []error{}
|
||||
v.warnings = []error{}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
@ -20,13 +21,14 @@ func (tns *TestNestedStruct) Validate(validator *schema.StructValidator) {
|
|||
}
|
||||
|
||||
type TestStruct struct {
|
||||
MustBe10 int
|
||||
NotEmpty string
|
||||
SetDefault string
|
||||
Nested TestNestedStruct
|
||||
Nested2 TestNestedStruct
|
||||
NilPtr *int
|
||||
NestedPtr *TestNestedStruct
|
||||
MustBe10 int
|
||||
ShouldBeAbove5 int
|
||||
NotEmpty string
|
||||
SetDefault string
|
||||
Nested TestNestedStruct
|
||||
Nested2 TestNestedStruct
|
||||
NilPtr *int
|
||||
NestedPtr *TestNestedStruct
|
||||
}
|
||||
|
||||
func (ts *TestStruct) Validate(validator *schema.StructValidator) {
|
||||
|
@ -38,6 +40,10 @@ func (ts *TestStruct) Validate(validator *schema.StructValidator) {
|
|||
validator.Push(fmt.Errorf("NotEmpty must not be empty"))
|
||||
}
|
||||
|
||||
if ts.ShouldBeAbove5 <= 5 {
|
||||
validator.PushWarning(fmt.Errorf("ShouldBeAbove5 should be above 5"))
|
||||
}
|
||||
|
||||
if ts.SetDefault == "" {
|
||||
ts.SetDefault = "xyz"
|
||||
}
|
||||
|
@ -79,3 +85,32 @@ func TestValidator(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "xyz", s.SetDefault)
|
||||
}
|
||||
|
||||
func TestStructValidator(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
s := TestStruct{
|
||||
MustBe10: 5,
|
||||
ShouldBeAbove5: 2,
|
||||
NotEmpty: "",
|
||||
NestedPtr: &TestNestedStruct{},
|
||||
}
|
||||
s.Validate(validator)
|
||||
|
||||
assert.True(t, validator.HasWarnings())
|
||||
assert.True(t, validator.HasErrors())
|
||||
|
||||
require.Len(t, validator.Warnings(), 1)
|
||||
require.Len(t, validator.Errors(), 2)
|
||||
|
||||
assert.EqualError(t, validator.Warnings()[0], "ShouldBeAbove5 should be above 5")
|
||||
assert.EqualError(t, validator.Errors()[0], "MustBe10 must be 10")
|
||||
assert.EqualError(t, validator.Errors()[1], "NotEmpty must not be empty")
|
||||
|
||||
validator.Clear()
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
|
||||
assert.Len(t, validator.Warnings(), 0)
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
}
|
||||
|
|
|
@ -72,30 +72,31 @@ func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationB
|
|||
}
|
||||
}
|
||||
|
||||
func validateLdapURL(ldapURL string, validator *schema.StructValidator) string {
|
||||
u, err := url.Parse(ldapURL)
|
||||
// Wrapper for test purposes to exclude the hostname from the return.
|
||||
func validateLdapURLSimple(ldapURL string, validator *schema.StructValidator) (finalURL string) {
|
||||
finalURL, _ = validateLdapURL(ldapURL, validator)
|
||||
|
||||
return finalURL
|
||||
}
|
||||
|
||||
func validateLdapURL(ldapURL string, validator *schema.StructValidator) (finalURL string, hostname string) {
|
||||
parsedURL, err := url.Parse(ldapURL)
|
||||
|
||||
if err != nil {
|
||||
validator.Push(errors.New("Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://"))
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
|
||||
if !(u.Scheme == schemeLDAP || u.Scheme == schemeLDAPS) {
|
||||
if !(parsedURL.Scheme == schemeLDAP || parsedURL.Scheme == schemeLDAPS) {
|
||||
validator.Push(errors.New("Unknown scheme for ldap url, should be ldap:// or ldaps://"))
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
|
||||
if u.Scheme == schemeLDAP && u.Port() == "" {
|
||||
u.Host += ":389"
|
||||
} else if u.Scheme == schemeLDAPS && u.Port() == "" {
|
||||
u.Host += ":636"
|
||||
if !parsedURL.IsAbs() {
|
||||
validator.Push(fmt.Errorf("URL to LDAP %s is still not absolute, it should be something like ldap://127.0.0.1:389", parsedURL.String()))
|
||||
}
|
||||
|
||||
if !u.IsAbs() {
|
||||
validator.Push(fmt.Errorf("URL to LDAP %s is still not absolute, it should be something like ldap://127.0.0.1:389", u.String()))
|
||||
}
|
||||
|
||||
return u.String()
|
||||
return parsedURL.String(), parsedURL.Hostname()
|
||||
}
|
||||
|
||||
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting.
|
||||
|
@ -104,10 +105,35 @@ func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationB
|
|||
configuration.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
|
||||
}
|
||||
|
||||
if configuration.MinimumTLSVersion == "" {
|
||||
configuration.MinimumTLSVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.MinimumTLSVersion
|
||||
} else if _, err := utils.TLSStringToTLSConfigVersion(configuration.MinimumTLSVersion); err != nil {
|
||||
validator.Push(fmt.Errorf("error occurred validating the LDAP minimum_tls_version key with value %s: %v", configuration.MinimumTLSVersion, err))
|
||||
nilTLS := configuration.TLS == nil
|
||||
if nilTLS {
|
||||
configuration.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
||||
}
|
||||
|
||||
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28 (if block).
|
||||
if configuration.SkipVerify != nil {
|
||||
validator.PushWarning(errors.New("DEPRECATED: LDAP Auth Backend `skip_verify` option has been replaced by `authentication_backend.ldap.tls.skip_verify` (will be removed in 4.28.0)"))
|
||||
|
||||
if nilTLS {
|
||||
configuration.TLS.SkipVerify = *configuration.SkipVerify
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28 (if block).
|
||||
if configuration.MinimumTLSVersion != "" {
|
||||
validator.PushWarning(errors.New("DEPRECATED: LDAP Auth Backend `minimum_tls_version` option has been replaced by `authentication_backend.ldap.tls.minimum_version` (will be removed in 4.28.0)"))
|
||||
|
||||
if nilTLS {
|
||||
configuration.TLS.MinimumVersion = configuration.MinimumTLSVersion
|
||||
}
|
||||
}
|
||||
|
||||
if configuration.TLS.MinimumVersion == "" {
|
||||
configuration.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
|
||||
}
|
||||
|
||||
if _, err := utils.TLSStringToTLSConfigVersion(configuration.TLS.MinimumVersion); err != nil {
|
||||
validator.Push(fmt.Errorf("error occurred validating the LDAP minimum_tls_version key with value %s: %v", configuration.TLS.MinimumVersion, err))
|
||||
}
|
||||
|
||||
switch configuration.Implementation {
|
||||
|
@ -122,7 +148,13 @@ func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationB
|
|||
if configuration.URL == "" {
|
||||
validator.Push(errors.New("Please provide a URL to the LDAP server"))
|
||||
} else {
|
||||
configuration.URL = validateLdapURL(configuration.URL, validator)
|
||||
ldapURL, serverName := validateLdapURL(configuration.URL, validator)
|
||||
|
||||
configuration.URL = ldapURL
|
||||
|
||||
if configuration.TLS.ServerName == "" {
|
||||
configuration.TLS.ServerName = serverName
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
|
|
@ -41,93 +41,127 @@ func (suite *FileBasedAuthenticationBackend) SetupTest() {
|
|||
}
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldValidateCompleteConfiguration() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvided() {
|
||||
suite.configuration.File.Path = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a `path` for the users database in `authentication_backend`")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a `path` for the users database in `authentication_backend`")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenMemoryNotMoreThanEightTimesParallelism() {
|
||||
suite.configuration.File.Password.Memory = 8
|
||||
suite.configuration.File.Password.Parallelism = 2
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Memory for argon2id must be 16 or more (parallelism * 8), you configured memory as 8 and parallelism as 2")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Memory for argon2id must be 16 or more (parallelism * 8), you configured memory as 8 and parallelism as 2")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenBlank() {
|
||||
suite.configuration.File.Password = &schema.PasswordConfiguration{}
|
||||
|
||||
assert.Equal(suite.T(), 0, suite.configuration.File.Password.KeyLength)
|
||||
assert.Equal(suite.T(), 0, suite.configuration.File.Password.Iterations)
|
||||
assert.Equal(suite.T(), 0, suite.configuration.File.Password.SaltLength)
|
||||
assert.Equal(suite.T(), "", suite.configuration.File.Password.Algorithm)
|
||||
assert.Equal(suite.T(), 0, suite.configuration.File.Password.Memory)
|
||||
assert.Equal(suite.T(), 0, suite.configuration.File.Password.Parallelism)
|
||||
suite.Assert().Equal(0, suite.configuration.File.Password.KeyLength)
|
||||
suite.Assert().Equal(0, suite.configuration.File.Password.Iterations)
|
||||
suite.Assert().Equal(0, suite.configuration.File.Password.SaltLength)
|
||||
suite.Assert().Equal("", suite.configuration.File.Password.Algorithm)
|
||||
suite.Assert().Equal(0, suite.configuration.File.Password.Memory)
|
||||
suite.Assert().Equal(0, suite.configuration.File.Password.Parallelism)
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.KeyLength, suite.configuration.File.Password.KeyLength)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.configuration.File.Password.KeyLength)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenOnlySHA512Set() {
|
||||
suite.configuration.File.Password = &schema.PasswordConfiguration{}
|
||||
assert.Equal(suite.T(), "", suite.configuration.File.Password.Algorithm)
|
||||
suite.Assert().Equal("", suite.configuration.File.Password.Algorithm)
|
||||
suite.configuration.File.Password.Algorithm = "sha512"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordSHA512Configuration.KeyLength, suite.configuration.File.Password.KeyLength)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordSHA512Configuration.Iterations, suite.configuration.File.Password.Iterations)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordSHA512Configuration.SaltLength, suite.configuration.File.Password.SaltLength)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordSHA512Configuration.Algorithm, suite.configuration.File.Password.Algorithm)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordSHA512Configuration.Memory, suite.configuration.File.Password.Memory)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordSHA512Configuration.Parallelism, suite.configuration.File.Password.Parallelism)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.configuration.File.Password.KeyLength)
|
||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Iterations, suite.configuration.File.Password.Iterations)
|
||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.SaltLength, suite.configuration.File.Password.SaltLength)
|
||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Algorithm, suite.configuration.File.Password.Algorithm)
|
||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Memory, suite.configuration.File.Password.Memory)
|
||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Parallelism, suite.configuration.File.Password.Parallelism)
|
||||
}
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTooLow() {
|
||||
suite.configuration.File.Password.KeyLength = 1
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Key length for argon2id must be 16, you configured 1")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Key length for argon2id must be 16, you configured 1")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSaltLengthTooLow() {
|
||||
suite.configuration.File.Password.SaltLength = -1
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The salt length must be 2 or more, you configured -1")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "The salt length must be 2 or more, you configured -1")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorithmDefined() {
|
||||
suite.configuration.File.Password.Algorithm = "bogus"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured 'bogus'")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured 'bogus'")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenIterationsTooLow() {
|
||||
suite.configuration.File.Password.Iterations = -1
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The number of iterations specified is invalid, must be 1 or more, you configured -1")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "The number of iterations specified is invalid, must be 1 or more, you configured -1")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenParallelismTooLow() {
|
||||
suite.configuration.File.Password.Parallelism = -1
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Parallelism for argon2id must be 1 or more, you configured -1")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Parallelism for argon2id must be 1 or more, you configured -1")
|
||||
}
|
||||
|
||||
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
|
||||
|
@ -136,13 +170,17 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
|
|||
suite.configuration.File.Password.SaltLength = 0
|
||||
suite.configuration.File.Password.Memory = 0
|
||||
suite.configuration.File.Password.Parallelism = 0
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
|
||||
assert.Equal(suite.T(), schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
|
||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
|
||||
}
|
||||
|
||||
func TestFileBasedAuthenticationBackend(t *testing.T) {
|
||||
|
@ -171,158 +209,289 @@ func (suite *LdapAuthenticationBackendSuite) SetupTest() {
|
|||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldValidateCompleteConfiguration() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementationIsInvalidMSAD() {
|
||||
suite.configuration.Ldap.Implementation = "masd"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "authentication backend ldap implementation must be blank or one of the following values `custom`, `activedirectory`")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication backend ldap implementation must be blank or one of the following values `custom`, `activedirectory`")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
|
||||
suite.configuration.Ldap.URL = ""
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a URL to the LDAP server")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a URL to the LDAP server")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenUserNotProvided() {
|
||||
suite.configuration.Ldap.User = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a user name to connect to the LDAP server")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a user name to connect to the LDAP server")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordNotProvided() {
|
||||
suite.configuration.Ldap.Password = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a password to connect to the LDAP server")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a password to connect to the LDAP server")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenBaseDNNotProvided() {
|
||||
suite.configuration.Ldap.BaseDN = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a base DN to connect to the LDAP server")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a base DN to connect to the LDAP server")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnEmptyGroupsFilter() {
|
||||
suite.configuration.Ldap.GroupsFilter = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a groups filter with `groups_filter` attribute")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a groups filter with `groups_filter` attribute")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter() {
|
||||
suite.configuration.Ldap.UsersFilter = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a users filter with `users_filter` attribute")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a users filter with `users_filter` attribute")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
|
||||
suite.configuration.Ldap.UsernameAttribute = ""
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval() {
|
||||
suite.configuration.RefreshInterval = "blah"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Auth Backend `refresh_interval` is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: Could not convert the input string of blah into a duration")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Auth Backend `refresh_interval` is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: Could not convert the input string of blah into a duration")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultImplementation() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), schema.LDAPImplementationCustom, suite.configuration.Ldap.Implementation)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal(schema.LDAPImplementationCustom, suite.configuration.Ldap.Implementation)
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), "cn", suite.configuration.Ldap.GroupNameAttribute)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal("cn", suite.configuration.Ldap.GroupNameAttribute)
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), "mail", suite.configuration.Ldap.MailAttribute)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal("mail", suite.configuration.Ldap.MailAttribute)
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), "displayname", suite.configuration.Ldap.DisplayNameAttribute)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal("displayname", suite.configuration.Ldap.DisplayNameAttribute)
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), "5m", suite.configuration.RefreshInterval)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal("5m", suite.configuration.RefreshInterval)
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainEnclosingParenthesis() {
|
||||
suite.configuration.Ldap.UsersFilter = "{username_attribute}={input}"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The users filter should contain enclosing parenthesis. For instance {username_attribute}={input} should be ({username_attribute}={input})")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "The users filter should contain enclosing parenthesis. For instance {username_attribute}={input} should be ({username_attribute}={input})")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenGroupsFilterDoesNotContainEnclosingParenthesis() {
|
||||
suite.configuration.Ldap.GroupsFilter = "cn={input}"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainUsernameAttribute() {
|
||||
suite.configuration.Ldap.UsersFilter = "(&({mail_attribute}={input})(objectClass=person))"
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Unable to detect {username_attribute} placeholder in users_filter, your configuration is broken. Please review configuration options listed at https://docs.authelia.com/configuration/authentication/ldap.html")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Unable to detect {username_attribute} placeholder in users_filter, your configuration is broken. Please review configuration options listed at https://docs.authelia.com/configuration/authentication/ldap.html")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() {
|
||||
suite.configuration.Ldap.UsersFilter = "(&({username_attribute}={mail_attribute})(objectClass=person))"
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Unable to detect {input} placeholder in users_filter, your configuration might be broken. Please review configuration options listed at https://docs.authelia.com/configuration/authentication/ldap.html")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Unable to detect {input} placeholder in users_filter, your configuration might be broken. Please review configuration options listed at https://docs.authelia.com/configuration/authentication/ldap.html")
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldAdaptLDAPURL() {
|
||||
assert.Equal(suite.T(), "", validateLdapURL("127.0.0.1", suite.validator))
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Unknown scheme for ldap url, should be ldap:// or ldaps://")
|
||||
suite.Assert().Equal("", validateLdapURLSimple("127.0.0.1", suite.validator))
|
||||
|
||||
assert.Equal(suite.T(), "", validateLdapURL("127.0.0.1:636", suite.validator))
|
||||
require.Len(suite.T(), suite.validator.Errors(), 2)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[1], "Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
assert.Equal(suite.T(), "ldap://127.0.0.1:389", validateLdapURL("ldap://127.0.0.1", suite.validator))
|
||||
assert.Equal(suite.T(), "ldap://127.0.0.1:390", validateLdapURL("ldap://127.0.0.1:390", suite.validator))
|
||||
assert.Equal(suite.T(), "ldap://127.0.0.1:389/abc", validateLdapURL("ldap://127.0.0.1/abc", suite.validator))
|
||||
assert.Equal(suite.T(), "ldap://127.0.0.1:389/abc?test=abc&x=y", validateLdapURL("ldap://127.0.0.1/abc?test=abc&x=y", suite.validator))
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Unknown scheme for ldap url, should be ldap:// or ldaps://")
|
||||
|
||||
assert.Equal(suite.T(), "ldaps://127.0.0.1:390", validateLdapURL("ldaps://127.0.0.1:390", suite.validator))
|
||||
assert.Equal(suite.T(), "ldaps://127.0.0.1:636", validateLdapURL("ldaps://127.0.0.1", suite.validator))
|
||||
suite.Assert().Equal("", validateLdapURLSimple("127.0.0.1:636", suite.validator))
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 2)
|
||||
suite.Assert().EqualError(suite.validator.Errors()[1], "Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://")
|
||||
|
||||
suite.Assert().Equal("ldap://127.0.0.1", validateLdapURLSimple("ldap://127.0.0.1", suite.validator))
|
||||
suite.Assert().Equal("ldap://127.0.0.1:390", validateLdapURLSimple("ldap://127.0.0.1:390", suite.validator))
|
||||
suite.Assert().Equal("ldap://127.0.0.1/abc", validateLdapURLSimple("ldap://127.0.0.1/abc", suite.validator))
|
||||
suite.Assert().Equal("ldap://127.0.0.1/abc?test=abc&x=y", validateLdapURLSimple("ldap://127.0.0.1/abc?test=abc&x=y", suite.validator))
|
||||
|
||||
suite.Assert().Equal("ldaps://127.0.0.1:390", validateLdapURLSimple("ldaps://127.0.0.1:390", suite.validator))
|
||||
suite.Assert().Equal("ldaps://127.0.0.1", validateLdapURLSimple("ldaps://127.0.0.1", suite.validator))
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldDefaultTLS12() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
assert.Equal(suite.T(), schema.DefaultLDAPAuthenticationBackendConfiguration.MinimumTLSVersion, suite.configuration.Ldap.MinimumTLSVersion)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfiguration.MinimumTLSVersion, suite.configuration.Ldap.MinimumTLSVersion)
|
||||
}
|
||||
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldNotAllowInvalidTLSValue() {
|
||||
suite.configuration.Ldap.MinimumTLSVersion = "SSL2.0"
|
||||
suite.configuration.Ldap.TLS = &schema.TLSConfig{
|
||||
MinimumVersion: "SSL2.0",
|
||||
}
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "error occurred validating the LDAP minimum_tls_version key with value SSL2.0: supplied TLS version isn't supported")
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "error occurred validating the LDAP minimum_tls_version key with value SSL2.0: supplied TLS version isn't supported")
|
||||
}
|
||||
|
||||
// Deprecated: Temporary Test. TODO: Remove in 4.28 (Whole Test).
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldReturnDeprecationWarningsAndNoMappingFor428() {
|
||||
var skipVerify = true
|
||||
|
||||
suite.configuration.Ldap.MinimumTLSVersion = "TLS1.0"
|
||||
suite.configuration.Ldap.SkipVerify = &skipVerify
|
||||
suite.configuration.Ldap.TLS = nil
|
||||
suite.configuration.Ldap.TLS = &schema.TLSConfig{
|
||||
ServerName: "golang.org",
|
||||
MinimumVersion: "",
|
||||
}
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
|
||||
// Should not override since TLS schema is defined
|
||||
suite.Assert().Equal(false, suite.configuration.Ldap.TLS.SkipVerify)
|
||||
suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion, suite.configuration.Ldap.TLS.MinimumVersion)
|
||||
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
suite.Require().Len(suite.validator.Warnings(), 2)
|
||||
|
||||
warnings := suite.validator.Warnings()
|
||||
|
||||
suite.Assert().EqualError(warnings[0], "DEPRECATED: LDAP Auth Backend `skip_verify` option has been replaced by `authentication_backend.ldap.tls.skip_verify` (will be removed in 4.28.0)")
|
||||
suite.Assert().EqualError(warnings[1], "DEPRECATED: LDAP Auth Backend `minimum_tls_version` option has been replaced by `authentication_backend.ldap.tls.minimum_version` (will be removed in 4.28.0)")
|
||||
}
|
||||
|
||||
// Deprecated: Temporary Test. TODO: Remove in 4.28 (Whole Test).
|
||||
func (suite *LdapAuthenticationBackendSuite) TestShouldReturnDeprecationWarningsAndMappingFor428() {
|
||||
var skipVerify = true
|
||||
|
||||
tlsVersion := "TLS1.1"
|
||||
|
||||
suite.configuration.Ldap.MinimumTLSVersion = tlsVersion
|
||||
suite.configuration.Ldap.SkipVerify = &skipVerify
|
||||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
|
||||
// Should override since TLS schema is not defined
|
||||
suite.Assert().Equal(true, suite.configuration.Ldap.TLS.SkipVerify)
|
||||
suite.Assert().Equal(tlsVersion, suite.configuration.Ldap.TLS.MinimumVersion)
|
||||
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
suite.Require().Len(suite.validator.Warnings(), 2)
|
||||
|
||||
warnings := suite.validator.Warnings()
|
||||
|
||||
suite.Assert().EqualError(warnings[0], "DEPRECATED: LDAP Auth Backend `skip_verify` option has been replaced by `authentication_backend.ldap.tls.skip_verify` (will be removed in 4.28.0)")
|
||||
suite.Assert().EqualError(warnings[1], "DEPRECATED: LDAP Auth Backend `minimum_tls_version` option has been replaced by `authentication_backend.ldap.tls.minimum_version` (will be removed in 4.28.0)")
|
||||
}
|
||||
|
||||
func TestLdapAuthenticationBackend(t *testing.T) {
|
||||
|
@ -344,29 +513,31 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
|
|||
suite.configuration.Ldap.User = testLDAPUser
|
||||
suite.configuration.Ldap.Password = testLDAPPassword
|
||||
suite.configuration.Ldap.BaseDN = testLDAPBaseDN
|
||||
suite.configuration.Ldap.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
||||
}
|
||||
|
||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() {
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
|
||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
assert.Equal(suite.T(),
|
||||
suite.Assert().Equal(
|
||||
suite.configuration.Ldap.UsersFilter,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter)
|
||||
assert.Equal(suite.T(),
|
||||
suite.Assert().Equal(
|
||||
suite.configuration.Ldap.UsernameAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute)
|
||||
assert.Equal(suite.T(),
|
||||
suite.Assert().Equal(
|
||||
suite.configuration.Ldap.DisplayNameAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute)
|
||||
assert.Equal(suite.T(),
|
||||
suite.Assert().Equal(
|
||||
suite.configuration.Ldap.MailAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute)
|
||||
assert.Equal(suite.T(),
|
||||
suite.Assert().Equal(
|
||||
suite.configuration.Ldap.GroupsFilter,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter)
|
||||
assert.Equal(suite.T(),
|
||||
suite.Assert().Equal(
|
||||
suite.configuration.Ldap.GroupNameAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute)
|
||||
}
|
||||
|
@ -381,22 +552,22 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefault
|
|||
|
||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||
|
||||
assert.NotEqual(suite.T(),
|
||||
suite.Assert().NotEqual(
|
||||
suite.configuration.Ldap.UsersFilter,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter)
|
||||
assert.NotEqual(suite.T(),
|
||||
suite.Assert().NotEqual(
|
||||
suite.configuration.Ldap.UsernameAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute)
|
||||
assert.NotEqual(suite.T(),
|
||||
suite.Assert().NotEqual(
|
||||
suite.configuration.Ldap.DisplayNameAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute)
|
||||
assert.NotEqual(suite.T(),
|
||||
suite.Assert().NotEqual(
|
||||
suite.configuration.Ldap.MailAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute)
|
||||
assert.NotEqual(suite.T(),
|
||||
suite.Assert().NotEqual(
|
||||
suite.configuration.Ldap.GroupsFilter,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter)
|
||||
assert.NotEqual(suite.T(),
|
||||
suite.Assert().NotEqual(
|
||||
suite.configuration.Ldap.GroupNameAttribute,
|
||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package validator
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
@ -11,6 +12,7 @@ var defaultPort = 8080
|
|||
var defaultLogLevel = "info"
|
||||
|
||||
// ValidateConfiguration and adapt the configuration read from file.
|
||||
//nolint:gocyclo // This function is likely to always have lots of if/else statements, as long as we keep the flow clean it should be understandable.
|
||||
func ValidateConfiguration(configuration *schema.Configuration, validator *schema.StructValidator) {
|
||||
if configuration.Host == "" {
|
||||
configuration.Host = "0.0.0.0"
|
||||
|
@ -26,6 +28,15 @@ func ValidateConfiguration(configuration *schema.Configuration, validator *schem
|
|||
validator.Push(fmt.Errorf("No TLS key provided, please check the \"tls_key\" which has been configured"))
|
||||
}
|
||||
|
||||
if configuration.CertificatesDirectory != "" {
|
||||
info, err := os.Stat(configuration.CertificatesDirectory)
|
||||
if err != nil {
|
||||
validator.Push(fmt.Errorf("Error checking certificate directory: %v", err))
|
||||
} else if !info.IsDir() {
|
||||
validator.Push(fmt.Errorf("The path %s specified for certificate_directory is not a directory", configuration.CertificatesDirectory))
|
||||
}
|
||||
}
|
||||
|
||||
if configuration.LogLevel == "" {
|
||||
configuration.LogLevel = defaultLogLevel
|
||||
}
|
||||
|
|
|
@ -152,3 +152,40 @@ func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
|
|||
require.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "Unable to parse default redirection url")
|
||||
}
|
||||
|
||||
func TestShouldNotOverrideCertificatesDirectoryAndShouldPassWhenBlank(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultConfig()
|
||||
ValidateConfiguration(&config, validator)
|
||||
require.Len(t, validator.Errors(), 0)
|
||||
|
||||
require.Equal(t, "", config.CertificatesDirectory)
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultConfig()
|
||||
config.CertificatesDirectory = "not-a-real-file.go"
|
||||
|
||||
ValidateConfiguration(&config, validator)
|
||||
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "Error checking certificate directory: stat not-a-real-file.go: no such file or directory")
|
||||
|
||||
validator = schema.NewStructValidator()
|
||||
config.CertificatesDirectory = "const.go"
|
||||
ValidateConfiguration(&config, validator)
|
||||
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "The path const.go specified for certificate_directory is not a directory")
|
||||
}
|
||||
|
||||
func TestShouldNotRaiseErrorOnValidCertificatesDirectory(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultConfig()
|
||||
config.CertificatesDirectory = "../../suites/common/ssl"
|
||||
|
||||
ValidateConfiguration(&config, validator)
|
||||
|
||||
require.Len(t, validator.Errors(), 0)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ var validKeys = []string{
|
|||
"jwt_secret",
|
||||
"tls_key",
|
||||
"tls_cert",
|
||||
"certificates_directory",
|
||||
|
||||
// Server Keys.
|
||||
"server.read_buffer_size",
|
||||
|
@ -72,9 +73,12 @@ var validKeys = []string{
|
|||
"notifier.smtp.subject",
|
||||
"notifier.smtp.startup_check_address",
|
||||
"notifier.smtp.disable_require_tls",
|
||||
"notifier.smtp.disable_verify_cert",
|
||||
"notifier.smtp.trusted_cert",
|
||||
"notifier.smtp.trusted_cert", // TODO: Deprecated: Remove in 4.28.
|
||||
"notifier.smtp.disable_html_emails",
|
||||
"notifier.smtp.tls.minimum_version",
|
||||
"notifier.smtp.tls.skip_verify",
|
||||
"notifier.smtp.tls.server_name",
|
||||
"notifier.smtp.disable_verify_cert", // TODO: Deprecated: Remove in 4.28.
|
||||
|
||||
// Regulation Keys.
|
||||
"regulation.max_retries",
|
||||
|
@ -93,9 +97,6 @@ var validKeys = []string{
|
|||
// LDAP Authentication Backend Keys.
|
||||
"authentication_backend.ldap.implementation",
|
||||
"authentication_backend.ldap.url",
|
||||
"authentication_backend.ldap.skip_verify",
|
||||
"authentication_backend.ldap.start_tls",
|
||||
"authentication_backend.ldap.minimum_tls_version",
|
||||
"authentication_backend.ldap.base_dn",
|
||||
"authentication_backend.ldap.username_attribute",
|
||||
"authentication_backend.ldap.additional_users_dn",
|
||||
|
@ -107,6 +108,12 @@ var validKeys = []string{
|
|||
"authentication_backend.ldap.display_name_attribute",
|
||||
"authentication_backend.ldap.user",
|
||||
"authentication_backend.ldap.password",
|
||||
"authentication_backend.ldap.start_tls",
|
||||
"authentication_backend.ldap.tls.minimum_version",
|
||||
"authentication_backend.ldap.tls.skip_verify",
|
||||
"authentication_backend.ldap.tls.server_name",
|
||||
"authentication_backend.ldap.skip_verify", // TODO: Deprecated: Remove in 4.28.
|
||||
"authentication_backend.ldap.minimum_tls_version", // TODO: Deprecated: Remove in 4.28.
|
||||
|
||||
// File Authentication Backend Keys.
|
||||
"authentication_backend.file.path",
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
||||
// ValidateNotifier validates and update notifier configuration.
|
||||
//nolint:gocyclo // TODO: Remove in 4.28. Should be able to remove this during the removal of deprecated config.
|
||||
func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *schema.StructValidator) {
|
||||
if configuration.SMTP == nil && configuration.FileSystem == nil {
|
||||
validator.Push(fmt.Errorf("Notifier should be either `smtp` or `filesystem`"))
|
||||
|
@ -51,6 +53,26 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc
|
|||
configuration.SMTP.Identifier = schema.DefaultSMTPNotifierConfiguration.Identifier
|
||||
}
|
||||
|
||||
if configuration.SMTP.TLS == nil {
|
||||
configuration.SMTP.TLS = schema.DefaultSMTPNotifierConfiguration.TLS
|
||||
|
||||
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28.
|
||||
if configuration.SMTP.DisableVerifyCert != nil {
|
||||
validator.PushWarning(errors.New("DEPRECATED: SMTP Notifier `disable_verify_cert` option has been replaced by `notifier.smtp.tls.skip_verify` (will be removed in 4.28.0)"))
|
||||
|
||||
configuration.SMTP.TLS.SkipVerify = *configuration.SMTP.DisableVerifyCert
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28.
|
||||
if configuration.SMTP.TrustedCert != "" {
|
||||
validator.PushWarning(errors.New("DEPRECATED: SMTP Notifier `trusted_cert` option has been replaced by the global option `certificates_directory` (will be removed in 4.28.0)"))
|
||||
}
|
||||
|
||||
if configuration.SMTP.TLS.ServerName == "" {
|
||||
configuration.SMTP.TLS.ServerName = configuration.SMTP.Host
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,13 @@ import (
|
|||
|
||||
type NotifierSuite struct {
|
||||
suite.Suite
|
||||
|
||||
configuration schema.NotifierConfiguration
|
||||
validator *schema.StructValidator
|
||||
}
|
||||
|
||||
func (s *NotifierSuite) SetupTest() {
|
||||
s.configuration.SMTP = &schema.SMTPNotifierConfiguration{
|
||||
func (suite *NotifierSuite) SetupTest() {
|
||||
suite.validator = schema.NewStructValidator()
|
||||
suite.configuration.SMTP = &schema.SMTPNotifierConfiguration{
|
||||
Username: "john",
|
||||
Password: "password",
|
||||
Sender: "admin@example.com",
|
||||
|
@ -24,96 +25,129 @@ func (s *NotifierSuite) SetupTest() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() {
|
||||
validator := schema.NewStructValidator()
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() {
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
s.Require().Len(errors, 0)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
s.configuration.SMTP = nil
|
||||
suite.configuration.SMTP = nil
|
||||
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors = validator.Errors()
|
||||
s.Require().Len(errors, 1)
|
||||
s.Assert().EqualError(errors[0], "Notifier should be either `smtp` or `filesystem`")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().True(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Notifier should be either `smtp` or `filesystem`")
|
||||
}
|
||||
|
||||
func (s *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
|
||||
validator := schema.NewStructValidator()
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
s.Require().Len(errors, 0)
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
s.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
|
||||
suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
|
||||
Filename: "test",
|
||||
}
|
||||
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors = validator.Errors()
|
||||
s.Require().Len(errors, 1)
|
||||
s.Assert().EqualError(errors[0], "Notifier should be either `smtp` or `filesystem`")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().True(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Notifier should be either `smtp` or `filesystem`")
|
||||
}
|
||||
|
||||
func (s *NotifierSuite) TestShouldEnsureFilenameOfFilesystemNotifierIsProvided() {
|
||||
validator := schema.NewStructValidator()
|
||||
|
||||
s.configuration.SMTP = nil
|
||||
s.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
|
||||
func (suite *NotifierSuite) TestShouldEnsureFilenameOfFilesystemNotifierIsProvided() {
|
||||
suite.configuration.SMTP = nil
|
||||
suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
|
||||
Filename: "test",
|
||||
}
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
s.Require().Len(errors, 0)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
s.configuration.FileSystem.Filename = ""
|
||||
suite.configuration.FileSystem.Filename = ""
|
||||
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors = validator.Errors()
|
||||
s.Require().Len(errors, 1)
|
||||
s.Assert().EqualError(errors[0], "Filename of filesystem notifier must not be empty")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().True(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Filename of filesystem notifier must not be empty")
|
||||
}
|
||||
|
||||
func (s *NotifierSuite) TestShouldEnsureHostAndPortOfSMTPNotifierAreProvided() {
|
||||
s.configuration.FileSystem = nil
|
||||
validator := schema.NewStructValidator()
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
func (suite *NotifierSuite) TestShouldEnsureHostAndPortOfSMTPNotifierAreProvided() {
|
||||
suite.configuration.FileSystem = nil
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
s.Require().Len(errors, 0)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
s.configuration.SMTP.Host = ""
|
||||
s.configuration.SMTP.Port = 0
|
||||
suite.configuration.SMTP.Host = ""
|
||||
suite.configuration.SMTP.Port = 0
|
||||
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors = validator.Errors()
|
||||
s.Require().Len(errors, 2)
|
||||
s.Assert().EqualError(errors[0], "Host of SMTP notifier must be provided")
|
||||
s.Assert().EqualError(errors[1], "Port of SMTP notifier must be provided")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().True(suite.validator.HasErrors())
|
||||
|
||||
errors := suite.validator.Errors()
|
||||
|
||||
suite.Require().Len(errors, 2)
|
||||
|
||||
suite.Assert().EqualError(errors[0], "Host of SMTP notifier must be provided")
|
||||
suite.Assert().EqualError(errors[1], "Port of SMTP notifier must be provided")
|
||||
}
|
||||
|
||||
func (s *NotifierSuite) TestShouldEnsureSenderOfSMTPNotifierAreProvided() {
|
||||
s.configuration.FileSystem = nil
|
||||
func (suite *NotifierSuite) TestShouldEnsureSenderOfSMTPNotifierAreProvided() {
|
||||
suite.configuration.FileSystem = nil
|
||||
|
||||
validator := schema.NewStructValidator()
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
s.Require().Len(errors, 0)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
s.configuration.SMTP.Sender = ""
|
||||
suite.configuration.SMTP.Sender = ""
|
||||
|
||||
ValidateNotifier(&s.configuration, validator)
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
errors = validator.Errors()
|
||||
s.Require().Len(errors, 1)
|
||||
s.Assert().EqualError(errors[0], "Sender of SMTP notifier must be provided")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().True(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Len(suite.validator.Errors(), 1)
|
||||
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Sender of SMTP notifier must be provided")
|
||||
}
|
||||
|
||||
// Deprecated: Temporary Test. TODO: Remove in 4.28 (Whole Test).
|
||||
func (suite *NotifierSuite) TestShouldReturnDeprecationWarningsFor428() {
|
||||
var disableVerifyCert = true
|
||||
|
||||
suite.configuration.SMTP.TrustedCert = "/tmp"
|
||||
suite.configuration.SMTP.DisableVerifyCert = &disableVerifyCert
|
||||
|
||||
ValidateNotifier(&suite.configuration, suite.validator)
|
||||
|
||||
suite.Require().True(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
warnings := suite.validator.Warnings()
|
||||
|
||||
suite.Require().Len(warnings, 2)
|
||||
|
||||
suite.Assert().EqualError(warnings[0], "DEPRECATED: SMTP Notifier `disable_verify_cert` option has been replaced by `notifier.smtp.tls.skip_verify` (will be removed in 4.28.0)")
|
||||
suite.Assert().EqualError(warnings[1], "DEPRECATED: SMTP Notifier `trusted_cert` option has been replaced by the global option `certificates_directory` (will be removed in 4.28.0)")
|
||||
|
||||
// Should override since TLS schema is not defined
|
||||
suite.Assert().Equal(true, suite.configuration.SMTP.TLS.SkipVerify)
|
||||
}
|
||||
|
||||
func TestNotifierSuite(t *testing.T) {
|
||||
|
|
|
@ -10,80 +10,87 @@ import (
|
|||
|
||||
type StorageSuite struct {
|
||||
suite.Suite
|
||||
|
||||
configuration schema.StorageConfiguration
|
||||
validator *schema.StructValidator
|
||||
}
|
||||
|
||||
func (s *StorageSuite) SetupTest() {
|
||||
s.configuration.Local = &schema.LocalStorageConfiguration{
|
||||
func (suite *StorageSuite) SetupTest() {
|
||||
suite.validator = schema.NewStructValidator()
|
||||
suite.configuration.Local = &schema.LocalStorageConfiguration{
|
||||
Path: "/this/is/a/path",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
|
||||
validator := schema.NewStructValidator()
|
||||
s.configuration.Local = nil
|
||||
func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
|
||||
suite.configuration.Local = nil
|
||||
|
||||
ValidateStorage(s.configuration, validator)
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
s.Require().Len(validator.Errors(), 1)
|
||||
s.Assert().EqualError(validator.Errors()[0], "A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'")
|
||||
}
|
||||
|
||||
func (s *StorageSuite) TestShouldValidateLocalPathIsProvided() {
|
||||
validator := schema.NewStructValidator()
|
||||
s.configuration.Local.Path = ""
|
||||
func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() {
|
||||
suite.configuration.Local.Path = ""
|
||||
|
||||
ValidateStorage(s.configuration, validator)
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
s.Require().Len(validator.Errors(), 1)
|
||||
s.Assert().EqualError(validator.Errors()[0], "A file path must be provided with key 'path'")
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
|
||||
validator = schema.NewStructValidator()
|
||||
s.configuration.Local.Path = "/myapth"
|
||||
ValidateStorage(s.configuration, validator)
|
||||
s.Require().Len(validator.Errors(), 0)
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "A file path must be provided with key 'path'")
|
||||
|
||||
suite.validator.Clear()
|
||||
suite.configuration.Local.Path = "/myapth"
|
||||
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
}
|
||||
|
||||
func (s *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseAreProvided() {
|
||||
validator := schema.NewStructValidator()
|
||||
s.configuration.MySQL = &schema.MySQLStorageConfiguration{}
|
||||
ValidateStorage(s.configuration, validator)
|
||||
func (suite *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseAreProvided() {
|
||||
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{}
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
s.Require().Len(validator.Errors(), 2)
|
||||
s.Assert().EqualError(validator.Errors()[0], "Username and password must be provided")
|
||||
s.Assert().EqualError(validator.Errors()[1], "A database must be provided")
|
||||
suite.Require().Len(suite.validator.Errors(), 2)
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Username and password must be provided")
|
||||
suite.Assert().EqualError(suite.validator.Errors()[1], "A database must be provided")
|
||||
|
||||
validator = schema.NewStructValidator()
|
||||
s.configuration.MySQL = &schema.MySQLStorageConfiguration{
|
||||
suite.validator.Clear()
|
||||
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{
|
||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||
Username: "myuser",
|
||||
Password: "pass",
|
||||
Database: "database",
|
||||
},
|
||||
}
|
||||
ValidateStorage(s.configuration, validator)
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
s.Require().Len(validator.Errors(), 0)
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
}
|
||||
|
||||
func (s *StorageSuite) TestShouldValidatePostgresSSLModeIsDisableByDefault() {
|
||||
validator := schema.NewStructValidator()
|
||||
s.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeIsDisableByDefault() {
|
||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||
Username: "myuser",
|
||||
Password: "pass",
|
||||
Database: "database",
|
||||
},
|
||||
}
|
||||
ValidateStorage(s.configuration, validator)
|
||||
|
||||
s.Assert().Equal("disable", s.configuration.PostgreSQL.SSLMode)
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Assert().False(suite.validator.HasErrors())
|
||||
|
||||
suite.Assert().Equal("disable", suite.configuration.PostgreSQL.SSLMode)
|
||||
}
|
||||
|
||||
func (s *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
||||
validator := schema.NewStructValidator()
|
||||
s.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||
Username: "myuser",
|
||||
Password: "pass",
|
||||
|
@ -91,10 +98,12 @@ func (s *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
|||
},
|
||||
SSLMode: "unknown",
|
||||
}
|
||||
ValidateStorage(s.configuration, validator)
|
||||
|
||||
s.Require().Len(validator.Errors(), 1)
|
||||
s.Assert().EqualError(validator.Errors()[0], "SSL mode must be 'disable', 'require', 'verify-ca', or 'verify-full'")
|
||||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
suite.Assert().False(suite.validator.HasWarnings())
|
||||
suite.Require().Len(suite.validator.Errors(), 1)
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "SSL mode must be 'disable', 'require', 'verify-ca', or 'verify-full'")
|
||||
}
|
||||
|
||||
func TestShouldRunStorageSuite(t *testing.T) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -23,8 +22,6 @@ type SMTPNotifier struct {
|
|||
identifier string
|
||||
host string
|
||||
port int
|
||||
trustedCert string
|
||||
disableVerifyCert bool
|
||||
disableRequireTLS bool
|
||||
address string
|
||||
subject string
|
||||
|
@ -34,7 +31,7 @@ type SMTPNotifier struct {
|
|||
}
|
||||
|
||||
// NewSMTPNotifier creates a SMTPNotifier using the notifier configuration.
|
||||
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
|
||||
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration, certPool *x509.CertPool) *SMTPNotifier {
|
||||
notifier := &SMTPNotifier{
|
||||
username: configuration.Username,
|
||||
password: configuration.Password,
|
||||
|
@ -42,59 +39,16 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi
|
|||
identifier: configuration.Identifier,
|
||||
host: configuration.Host,
|
||||
port: configuration.Port,
|
||||
trustedCert: configuration.TrustedCert,
|
||||
disableVerifyCert: configuration.DisableVerifyCert,
|
||||
disableRequireTLS: configuration.DisableRequireTLS,
|
||||
address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
|
||||
subject: configuration.Subject,
|
||||
startupCheckAddress: configuration.StartupCheckAddress,
|
||||
tlsConfig: utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool),
|
||||
}
|
||||
notifier.initializeTLSConfig()
|
||||
|
||||
return notifier
|
||||
}
|
||||
|
||||
func (n *SMTPNotifier) initializeTLSConfig() {
|
||||
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded
|
||||
// The second part of this check happens in the Configure Cert Pool code block
|
||||
logging.Logger().Debug("Notifier SMTP client initializing TLS configuration")
|
||||
|
||||
// Configure Cert Pool
|
||||
certPool, err := x509.SystemCertPool()
|
||||
if err != nil || certPool == nil {
|
||||
certPool = x509.NewCertPool()
|
||||
}
|
||||
|
||||
if n.trustedCert != "" {
|
||||
logging.Logger().Debugf("Notifier SMTP client attempting to load certificate from %s", n.trustedCert)
|
||||
|
||||
if exists, err := utils.FileExists(n.trustedCert); exists {
|
||||
pem, err := ioutil.ReadFile(n.trustedCert)
|
||||
if err != nil {
|
||||
logging.Logger().Warnf("Notifier SMTP failed to load cert from file with error: %s", err)
|
||||
} else {
|
||||
if ok := certPool.AppendCertsFromPEM(pem); !ok {
|
||||
logging.Logger().Warn("Notifier SMTP failed to import cert loaded from file")
|
||||
} else {
|
||||
logging.Logger().Debug("Notifier SMTP successfully loaded certificate")
|
||||
if n.disableVerifyCert {
|
||||
logging.Logger().Warn("Notifier SMTP when trusted_cert is specified we force disable_verify_cert to false, if you want to disable certificate validation please comment/delete trusted_cert from your config")
|
||||
n.disableVerifyCert = false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logging.Logger().Warnf("Notifier SMTP failed to load cert from file (file does not exist) with error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
n.tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: n.disableVerifyCert, //nolint:gosec // This is an intended config, we never default true, provide alternate options, and we constantly warn the user.
|
||||
ServerName: n.host,
|
||||
RootCAs: certPool,
|
||||
}
|
||||
}
|
||||
|
||||
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
|
||||
func (n *SMTPNotifier) startTLS() error {
|
||||
// Only start if not already encrypted
|
||||
|
|
57
internal/notification/smtp_notifier_test.go
Normal file
57
internal/notification/smtp_notifier_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package notification
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/internal/configuration/validator"
|
||||
)
|
||||
|
||||
func TestShouldConfigureSMTPNotifierWithTLS11AndDefaultHostname(t *testing.T) {
|
||||
config := &schema.NotifierConfiguration{
|
||||
DisableStartupCheck: true,
|
||||
SMTP: &schema.SMTPNotifierConfiguration{
|
||||
Host: "smtp.example.com",
|
||||
Port: 25,
|
||||
TLS: &schema.TLSConfig{
|
||||
MinimumVersion: "TLS1.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sv := schema.NewStructValidator()
|
||||
validator.ValidateNotifier(config, sv)
|
||||
|
||||
notifier := NewSMTPNotifier(*config.SMTP, nil)
|
||||
|
||||
assert.Equal(t, "smtp.example.com", notifier.tlsConfig.ServerName)
|
||||
assert.Equal(t, uint16(tls.VersionTLS11), notifier.tlsConfig.MinVersion)
|
||||
assert.False(t, notifier.tlsConfig.InsecureSkipVerify)
|
||||
assert.Equal(t, "smtp.example.com:25", notifier.address)
|
||||
}
|
||||
|
||||
func TestShouldConfigureSMTPNotifierWithServerNameOverrideAndDefaultTLS12(t *testing.T) {
|
||||
config := &schema.NotifierConfiguration{
|
||||
DisableStartupCheck: true,
|
||||
SMTP: &schema.SMTPNotifierConfiguration{
|
||||
Host: "smtp.example.com",
|
||||
Port: 25,
|
||||
TLS: &schema.TLSConfig{
|
||||
ServerName: "smtp.golang.org",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sv := schema.NewStructValidator()
|
||||
validator.ValidateNotifier(config, sv)
|
||||
|
||||
notifier := NewSMTPNotifier(*config.SMTP, nil)
|
||||
|
||||
assert.Equal(t, "smtp.golang.org", notifier.tlsConfig.ServerName)
|
||||
assert.Equal(t, uint16(tls.VersionTLS12), notifier.tlsConfig.MinVersion)
|
||||
assert.False(t, notifier.tlsConfig.InsecureSkipVerify)
|
||||
assert.Equal(t, "smtp.example.com:25", notifier.address)
|
||||
}
|
|
@ -16,7 +16,8 @@ authentication_backend:
|
|||
ldap:
|
||||
implementation: activedirectory
|
||||
url: ldap://sambaldap
|
||||
skip_verify: true
|
||||
tls:
|
||||
skip_verify: true
|
||||
start_tls: true
|
||||
base_dn: DC=example,DC=com
|
||||
username_attribute: sAMAccountName
|
||||
|
|
|
@ -15,7 +15,8 @@ jwt_secret: very_important_secret
|
|||
authentication_backend:
|
||||
ldap:
|
||||
url: ldaps://openldap
|
||||
skip_verify: true
|
||||
tls:
|
||||
skip_verify: true
|
||||
base_dn: dc=example,dc=com
|
||||
username_attribute: uid
|
||||
additional_users_dn: ou=users
|
||||
|
|
|
@ -65,7 +65,7 @@ access_control:
|
|||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
find_time: 3
|
||||
find_time: 5
|
||||
ban_time: 10
|
||||
|
||||
notifier:
|
||||
|
|
|
@ -13,7 +13,8 @@ default_redirection_url: https://home.example.com:8080
|
|||
authentication_backend:
|
||||
ldap:
|
||||
url: ldaps://ldap-service
|
||||
skip_verify: true
|
||||
tls:
|
||||
skip_verify: true
|
||||
base_dn: dc=example,dc=com
|
||||
username_attribute: uid
|
||||
additional_users_dn: ou=users
|
||||
|
|
91
internal/utils/certificates.go
Normal file
91
internal/utils/certificates.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
||||
// NewTLSConfig generates a tls.Config from a schema.TLSConfig and a x509.CertPool.
|
||||
func NewTLSConfig(config *schema.TLSConfig, defaultMinVersion uint16, certPool *x509.CertPool) (tlsConfig *tls.Config) {
|
||||
minVersion, err := TLSStringToTLSConfigVersion(config.MinimumVersion)
|
||||
if err != nil {
|
||||
minVersion = defaultMinVersion
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
ServerName: config.ServerName,
|
||||
InsecureSkipVerify: config.SkipVerify, //nolint:gosec // Informed choice by user. Off by default.
|
||||
MinVersion: minVersion,
|
||||
RootCAs: certPool,
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo // TODO: Remove in 4.28. Should be able to remove the nolint during the removal of deprecated config.
|
||||
// NewX509CertPool generates a x509.CertPool from the system PKI and the directory specified.
|
||||
func NewX509CertPool(directory string, config *schema.Configuration) (certPool *x509.CertPool, errors []error, nonFatalErrors []error) {
|
||||
certPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
nonFatalErrors = append(nonFatalErrors, fmt.Errorf("could not load system certificate pool which may result in untruested certificate issues: %v", err))
|
||||
certPool = x509.NewCertPool()
|
||||
}
|
||||
|
||||
if directory != "" {
|
||||
certsFileInfo, err := ioutil.ReadDir(directory)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("could not read certificates from directory %v", err))
|
||||
} else {
|
||||
for _, certFileInfo := range certsFileInfo {
|
||||
nameLower := strings.ToLower(certFileInfo.Name())
|
||||
|
||||
if !certFileInfo.IsDir() && (strings.HasSuffix(nameLower, ".cer") || strings.HasSuffix(nameLower, ".pem")) {
|
||||
certBytes, err := ioutil.ReadFile(path.Join(directory, certFileInfo.Name()))
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("could not read certificate %v", err))
|
||||
} else if ok := certPool.AppendCertsFromPEM(certBytes); !ok {
|
||||
errors = append(errors, fmt.Errorf("could not import certificate %s", certFileInfo.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28.
|
||||
if config != nil && config.Notifier != nil && config.Notifier.SMTP != nil && config.Notifier.SMTP.TrustedCert != "" {
|
||||
nonFatalErrors = append(nonFatalErrors, fmt.Errorf("defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead"))
|
||||
|
||||
if exists, _ := FileExists(config.Notifier.SMTP.TrustedCert); exists {
|
||||
pem, err := ioutil.ReadFile(config.Notifier.SMTP.TrustedCert)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("failed to read legacy SMTP trusted_cert (see the new certificates_directory option) certificate %s with error: %s", config.Notifier.SMTP.TrustedCert, err))
|
||||
} else if ok := certPool.AppendCertsFromPEM(pem); !ok {
|
||||
errors = append(errors, fmt.Errorf("could not import legacy SMTP trusted_cert (see the new certificates_directory option) certificate %s", config.Notifier.SMTP.TrustedCert))
|
||||
}
|
||||
} else {
|
||||
errors = append(errors, fmt.Errorf("could not import legacy SMTP trusted_cert (see the new certificates_directory option) certificate %s (file does not exist)", config.Notifier.SMTP.TrustedCert))
|
||||
}
|
||||
}
|
||||
|
||||
return certPool, errors, nonFatalErrors
|
||||
}
|
||||
|
||||
// TLSStringToTLSConfigVersion returns a go crypto/tls version for a tls.Config based on string input.
|
||||
func TLSStringToTLSConfigVersion(input string) (version uint16, err error) {
|
||||
switch strings.ToUpper(input) {
|
||||
case "TLS1.3", TLS13:
|
||||
return tls.VersionTLS13, nil
|
||||
case "TLS1.2", TLS12:
|
||||
return tls.VersionTLS12, nil
|
||||
case "TLS1.1", TLS11:
|
||||
return tls.VersionTLS11, nil
|
||||
case "TLS1.0", TLS10:
|
||||
return tls.VersionTLS10, nil
|
||||
}
|
||||
|
||||
return 0, ErrTLSVersionNotSupported
|
||||
}
|
134
internal/utils/certificates_test.go
Normal file
134
internal/utils/certificates_test.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
||||
func TestShouldSetupDefaultTLSMinVersionOnErr(t *testing.T) {
|
||||
schemaTLSConfig := &schema.TLSConfig{
|
||||
MinimumVersion: "NotAVersion",
|
||||
ServerName: "golang.org",
|
||||
SkipVerify: true,
|
||||
}
|
||||
|
||||
tlsConfig := NewTLSConfig(schemaTLSConfig, tls.VersionTLS12, nil)
|
||||
|
||||
assert.Equal(t, uint16(tls.VersionTLS12), tlsConfig.MinVersion)
|
||||
assert.Equal(t, "golang.org", tlsConfig.ServerName)
|
||||
assert.True(t, tlsConfig.InsecureSkipVerify)
|
||||
}
|
||||
|
||||
func TestShouldReturnCorrectTLSVersions(t *testing.T) {
|
||||
tls13 := uint16(tls.VersionTLS13)
|
||||
tls12 := uint16(tls.VersionTLS12)
|
||||
tls11 := uint16(tls.VersionTLS11)
|
||||
tls10 := uint16(tls.VersionTLS10)
|
||||
|
||||
version, err := TLSStringToTLSConfigVersion(TLS13)
|
||||
assert.Equal(t, tls13, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS13)
|
||||
assert.Equal(t, tls13, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion(TLS12)
|
||||
assert.Equal(t, tls12, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS12)
|
||||
assert.Equal(t, tls12, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion(TLS11)
|
||||
assert.Equal(t, tls11, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS11)
|
||||
assert.Equal(t, tls11, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion(TLS10)
|
||||
assert.Equal(t, tls10, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS10)
|
||||
assert.Equal(t, tls10, version)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestShouldReturnZeroAndErrorOnInvalidTLSVersions(t *testing.T) {
|
||||
version, err := TLSStringToTLSConfigVersion("TLS1.4")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, uint16(0), version)
|
||||
assert.EqualError(t, err, "supplied TLS version isn't supported")
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("SSL3.0")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, uint16(0), version)
|
||||
assert.EqualError(t, err, "supplied TLS version isn't supported")
|
||||
}
|
||||
|
||||
func TestShouldReturnErrWhenX509DirectoryNotExist(t *testing.T) {
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp/asdfzyxabc123/not/a/real/dir", nil)
|
||||
assert.NotNil(t, pool)
|
||||
assert.Len(t, nonFatalErrs, 0)
|
||||
require.Len(t, errs, 1)
|
||||
assert.EqualError(t, errs[0], "could not read certificates from directory open /tmp/asdfzyxabc123/not/a/real/dir: no such file or directory")
|
||||
}
|
||||
|
||||
func TestShouldNotReturnErrWhenX509DirectoryExist(t *testing.T) {
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp", nil)
|
||||
assert.NotNil(t, pool)
|
||||
assert.Len(t, nonFatalErrs, 0)
|
||||
assert.Len(t, errs, 0)
|
||||
}
|
||||
|
||||
func TestShouldRaiseNonFatalErrWhenNotifierTrustedCertConfigured(t *testing.T) {
|
||||
config := &schema.Configuration{
|
||||
Notifier: &schema.NotifierConfiguration{
|
||||
SMTP: &schema.SMTPNotifierConfiguration{
|
||||
TrustedCert: "../suites/common/ssl/cert.pem",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp", config)
|
||||
assert.NotNil(t, pool)
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
assert.Len(t, errs, 0)
|
||||
|
||||
assert.EqualError(t, nonFatalErrs[0], "defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead")
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrAndNonFatalErrWhenNotifierTrustedCertConfiguredAndNotExist(t *testing.T) {
|
||||
config := &schema.Configuration{
|
||||
Notifier: &schema.NotifierConfiguration{
|
||||
SMTP: &schema.SMTPNotifierConfiguration{
|
||||
TrustedCert: "/tmp/asdfzyxabc123/not/a/real/cert.pem",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp", config)
|
||||
assert.NotNil(t, pool)
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
require.Len(t, errs, 1)
|
||||
|
||||
assert.EqualError(t, errs[0], "could not import legacy SMTP trusted_cert (see the new certificates_directory option) certificate /tmp/asdfzyxabc123/not/a/real/cert.pem (file does not exist)")
|
||||
assert.EqualError(t, nonFatalErrs[0], "defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead")
|
||||
}
|
||||
|
||||
func TestShouldReadCertsFromDirectoryButNotKeys(t *testing.T) {
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("../suites/common/ssl/", nil)
|
||||
assert.NotNil(t, pool)
|
||||
require.Len(t, errs, 1)
|
||||
assert.Len(t, nonFatalErrs, 0)
|
||||
assert.EqualError(t, errs[0], "could not import certificate key.pem")
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -104,19 +103,3 @@ func RandomString(n int, characters []rune) (randomString string) {
|
|||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// TLSStringToTLSConfigVersion returns a go crypto/tls version for a tls.Config based on string input.
|
||||
func TLSStringToTLSConfigVersion(input string) (version uint16, err error) {
|
||||
switch strings.ToUpper(input) {
|
||||
case "TLS1.3", TLS13:
|
||||
return tls.VersionTLS13, nil
|
||||
case "TLS1.2", TLS12:
|
||||
return tls.VersionTLS12, nil
|
||||
case "TLS1.1", TLS11:
|
||||
return tls.VersionTLS11, nil
|
||||
case "TLS1.0", TLS10:
|
||||
return tls.VersionTLS10, nil
|
||||
}
|
||||
|
||||
return 0, ErrTLSVersionNotSupported
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -83,54 +82,3 @@ func TestShouldNotFindStringInSliceContains(t *testing.T) {
|
|||
s := IsStringInSliceContains(a, b)
|
||||
assert.False(t, s)
|
||||
}
|
||||
|
||||
func TestShouldReturnCorrectTLSVersions(t *testing.T) {
|
||||
tls13 := uint16(tls.VersionTLS13)
|
||||
tls12 := uint16(tls.VersionTLS12)
|
||||
tls11 := uint16(tls.VersionTLS11)
|
||||
tls10 := uint16(tls.VersionTLS10)
|
||||
|
||||
version, err := TLSStringToTLSConfigVersion(TLS13)
|
||||
assert.Equal(t, tls13, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS13)
|
||||
assert.Equal(t, tls13, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion(TLS12)
|
||||
assert.Equal(t, tls12, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS12)
|
||||
assert.Equal(t, tls12, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion(TLS11)
|
||||
assert.Equal(t, tls11, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS11)
|
||||
assert.Equal(t, tls11, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion(TLS10)
|
||||
assert.Equal(t, tls10, version)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("TLS" + TLS10)
|
||||
assert.Equal(t, tls10, version)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestShouldReturnZeroAndErrorOnInvalidTLSVersions(t *testing.T) {
|
||||
version, err := TLSStringToTLSConfigVersion("TLS1.4")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, uint16(0), version)
|
||||
assert.EqualError(t, err, "supplied TLS version isn't supported")
|
||||
|
||||
version, err = TLSStringToTLSConfigVersion("SSL3.0")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, uint16(0), version)
|
||||
assert.EqualError(t, err, "supplied TLS version isn't supported")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user