Skip to content

Commit

Permalink
Merge pull request #36 from rchincha/ldap
Browse files Browse the repository at this point in the history
ldap: improve recovery when connection failures
  • Loading branch information
hallyn authored Nov 18, 2019
2 parents 0550752 + 5447ec5 commit 181fe59
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 13 deletions.
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ var (
ErrBadUser = errors.New("ldap: non-existent user")
ErrEntriesExceeded = errors.New("ldap: too many entries returned")
ErrLDAPEmptyPassphrase = errors.New("ldap: empty passphrase")
ErrLDAPBadConn = errors.New("ldap: bad connection")
ErrLDAPConfig = errors.New("config: invalid LDAP configuration")
)
50 changes: 37 additions & 13 deletions pkg/api/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"time"

"github.com/anuvu/zot/errors"
"github.com/jtblin/go-ldap-client"
"github.com/rs/zerolog"
goldap "gopkg.in/ldap.v2"
)

const maxRetries = 8

type LDAPClient struct {
ldap.LDAPClient
subtreeSearch bool
Expand Down Expand Up @@ -69,28 +72,49 @@ func (lc *LDAPClient) Connect() error {
return nil
}

func sleepAndRetry(retries, maxRetries int) bool {
if retries > maxRetries {
return false
}
if retries < maxRetries {
time.Sleep(time.Duration(retries) * time.Second) // gradually backoff
return true
}
return false
}

// Authenticate authenticates the user against the ldap backend.
func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]string, error) {
if password == "" {
// RFC 4513 section 5.1.2
return false, nil, errors.ErrLDAPEmptyPassphrase
}

err := lc.Connect()
if err != nil {
return false, nil, err
}

// First bind with a read only user
if lc.BindDN != "" && lc.BindPassword != "" {
err := lc.Conn.Bind(lc.BindDN, lc.BindPassword)
connected := false
for retries := 0; !connected && sleepAndRetry(retries, maxRetries); retries++ {
err := lc.Connect()
if err != nil {
lc.log.Error().Err(err).Str("bindDN", lc.BindDN).Msg("bind failed")
// clean up the cached conn, so we can retry
lc.Conn.Close()
lc.Conn = nil
return false, nil, err
continue
}

// First bind with a read only user
if lc.BindDN != "" && lc.BindPassword != "" {
err := lc.Conn.Bind(lc.BindDN, lc.BindPassword)
if err != nil {
lc.log.Error().Err(err).Str("bindDN", lc.BindDN).Msg("bind failed")
// clean up the cached conn, so we can retry
lc.Conn.Close()
lc.Conn = nil
continue
}
}
connected = true
}

// exhausted all retries?
if !connected {
lc.log.Error().Err(errors.ErrLDAPBadConn).Msg("exhausted all retries")
return false, nil, errors.ErrLDAPBadConn
}

attributes := append(lc.Attributes, "dn")
Expand Down

0 comments on commit 181fe59

Please sign in to comment.