mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-01-01 04:52:29 +00:00
LDAP: Add mTLS / client certificate authentication support (#509)
* ldap: Add mTLS authentication support to LDAP backend * ldap: Reuse BindService() for initial bind attempt * ldap: Make LdapService.config private Now that we have ldap.BindService(), we don't need to access any members of LdapService.config externally. * ldap: Add TODO note about STARTTLS/SASL authentication * ldap: Add TODO note about mTLS and extra CA certificates * chore: fix typo Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Stavros <steveiliop56@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -101,7 +101,7 @@ func (auth *AuthService) VerifyUser(search config.UserSearch, password string) b
|
||||
return false
|
||||
}
|
||||
|
||||
err = auth.ldap.Bind(auth.ldap.Config.BindDN, auth.ldap.Config.BindPassword)
|
||||
err = auth.ldap.BindService(true)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
|
||||
return false
|
||||
|
||||
@@ -19,21 +19,44 @@ type LdapServiceConfig struct {
|
||||
BaseDN string
|
||||
Insecure bool
|
||||
SearchFilter string
|
||||
AuthCert string
|
||||
AuthKey string
|
||||
}
|
||||
|
||||
type LdapService struct {
|
||||
Config LdapServiceConfig // exported so as the auth service can use it
|
||||
config LdapServiceConfig
|
||||
conn *ldapgo.Conn
|
||||
mutex sync.RWMutex
|
||||
cert *tls.Certificate
|
||||
}
|
||||
|
||||
func NewLdapService(config LdapServiceConfig) *LdapService {
|
||||
return &LdapService{
|
||||
Config: config,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (ldap *LdapService) Init() error {
|
||||
// Check whether authentication with client certificate is possible
|
||||
if ldap.config.AuthCert != "" && ldap.config.AuthKey != "" {
|
||||
cert, err := tls.LoadX509KeyPair(ldap.config.AuthCert, ldap.config.AuthKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize LDAP with mTLS authentication: %w", err)
|
||||
}
|
||||
ldap.cert = &cert
|
||||
log.Info().Msg("Using LDAP with mTLS authentication")
|
||||
|
||||
// TODO: Add optional extra CA certificates, instead of `InsecureSkipVerify`
|
||||
/*
|
||||
caCert, _ := ioutil.ReadFile(*caFile)
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
tlsConfig := &tls.Config{
|
||||
...
|
||||
RootCAs: caCertPool,
|
||||
}
|
||||
*/
|
||||
}
|
||||
_, err := ldap.connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to LDAP server: %w", err)
|
||||
@@ -60,31 +83,46 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
|
||||
ldap.mutex.Lock()
|
||||
defer ldap.mutex.Unlock()
|
||||
|
||||
conn, err := ldapgo.DialURL(ldap.Config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: ldap.Config.Insecure,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}))
|
||||
var conn *ldapgo.Conn
|
||||
var err error
|
||||
|
||||
// TODO: There's also STARTTLS (or SASL)-based mTLS authentication
|
||||
// scenario, where we first connect to plain text port (389) and
|
||||
// continue with a STARTTLS negotiation:
|
||||
// 1. conn = ldap.DialURL("ldap://ldap.example.com:389")
|
||||
// 2. conn.StartTLS(tlsConfig)
|
||||
// 3. conn.externalBind()
|
||||
if ldap.cert != nil {
|
||||
conn, err = ldapgo.DialURL(ldap.config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{*ldap.cert},
|
||||
}))
|
||||
} else {
|
||||
conn, err = ldapgo.DialURL(ldap.config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: ldap.config.Insecure,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(ldap.Config.BindDN, ldap.Config.BindPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set and return the connection
|
||||
ldap.conn = conn
|
||||
return conn, nil
|
||||
|
||||
err = ldap.BindService(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ldap.conn, nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) Search(username string) (string, error) {
|
||||
// Escape the username to prevent LDAP injection
|
||||
escapedUsername := ldapgo.EscapeFilter(username)
|
||||
filter := fmt.Sprintf(ldap.Config.SearchFilter, escapedUsername)
|
||||
filter := fmt.Sprintf(ldap.config.SearchFilter, escapedUsername)
|
||||
|
||||
searchRequest := ldapgo.NewSearchRequest(
|
||||
ldap.Config.BaseDN,
|
||||
ldap.config.BaseDN,
|
||||
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
||||
filter,
|
||||
[]string{"dn"},
|
||||
@@ -107,6 +145,19 @@ func (ldap *LdapService) Search(username string) (string, error) {
|
||||
return userDN, nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) BindService(rebind bool) error {
|
||||
// Locks must not be used for initial binding attempt
|
||||
if rebind {
|
||||
ldap.mutex.Lock()
|
||||
defer ldap.mutex.Unlock()
|
||||
}
|
||||
|
||||
if ldap.cert != nil {
|
||||
return ldap.conn.ExternalBind()
|
||||
}
|
||||
return ldap.conn.Bind(ldap.config.BindDN, ldap.config.BindPassword)
|
||||
}
|
||||
|
||||
func (ldap *LdapService) Bind(userDN string, password string) error {
|
||||
ldap.mutex.Lock()
|
||||
defer ldap.mutex.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user