0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/api/ldap.go

245 lines
5.9 KiB
Go
Raw Normal View History

// Package ldap provides a simple ldap client to authenticate,
// retrieve basic information and groups for a user.
2019-08-15 11:34:54 -05:00
package api
import (
"crypto/tls"
"crypto/x509"
"fmt"
"sync"
"time"
"github.com/go-ldap/ldap/v3"
"zotregistry.dev/zot/errors"
"zotregistry.dev/zot/pkg/log"
2019-08-15 11:34:54 -05:00
)
type LDAPClient struct {
InsecureSkipVerify bool
UseSSL bool
SkipTLS bool
SubtreeSearch bool
Port int
Attributes []string
Base string
BindDN string
BindPassword string
GroupFilter string // e.g. "(memberUid=%s)"
UserGroupAttribute string // e.g. "memberOf"
Host string
ServerName string
UserFilter string // e.g. "(uid=%s)"
Conn *ldap.Conn
ClientCertificates []tls.Certificate // Adding client certificates
ClientCAs *x509.CertPool
Log log.Logger
lock sync.Mutex
2019-08-15 11:34:54 -05:00
}
// Connect connects to the ldap backend.
func (lc *LDAPClient) Connect() error {
if lc.Conn == nil {
var l *ldap.Conn
2019-08-15 11:34:54 -05:00
var err error
2019-08-15 11:34:54 -05:00
address := fmt.Sprintf("%s:%d", lc.Host, lc.Port)
2019-08-15 11:34:54 -05:00
if !lc.UseSSL {
l, err = ldap.Dial("tcp", address) //nolint:staticcheck
2019-08-15 11:34:54 -05:00
if err != nil {
lc.Log.Error().Err(err).Str("address", address).Msg("failed to establish a TCP connection")
2019-08-15 11:34:54 -05:00
return err
}
// Reconnect with TLS
if !lc.SkipTLS {
config := &tls.Config{
InsecureSkipVerify: lc.InsecureSkipVerify, //nolint: gosec // InsecureSkipVerify is not true by default
RootCAs: lc.ClientCAs,
2019-08-15 11:34:54 -05:00
}
if len(lc.ClientCertificates) > 0 {
2019-08-15 11:34:54 -05:00
config.Certificates = lc.ClientCertificates
}
2019-08-15 11:34:54 -05:00
err = l.StartTLS(config)
if err != nil {
lc.Log.Error().Err(err).Str("address", address).Msg("failed to establish a TLS connection")
2019-08-15 11:34:54 -05:00
return err
}
}
} else {
config := &tls.Config{
InsecureSkipVerify: lc.InsecureSkipVerify, //nolint: gosec // InsecureSkipVerify is not true by default
2019-08-15 11:34:54 -05:00
ServerName: lc.ServerName,
RootCAs: lc.ClientCAs,
2019-08-15 11:34:54 -05:00
}
if len(lc.ClientCertificates) > 0 {
2019-08-15 11:34:54 -05:00
config.Certificates = lc.ClientCertificates
}
ci(deps): upgrade golangci-lint (#2556) * ci(deps): upgrade golangci-lint Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de> * build(deps): removed disabled linters Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de> * build(deps): go run github.com/daixiang0/gci@latest write . Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run golang.org/x/tools/cmd/goimports@latest -l -w . Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run github.com/bombsimon/wsl/v4/cmd...@latest -strict-append -test=true -fix ./... Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run github.com/catenacyber/perfsprint@latest -fix ./... Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): replace gomnd by mnd Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): make gqlgen Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build: Revert "build(deps): go run github.com/daixiang0/gci@latest write ." This reverts commit 5bf8c42e1f48c7daf8d1a4dbcfbb8ddef8d0bbbf. Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run github.com/daixiang0/gci@latest write -s 'standard' -s default -s 'prefix(zotregistry.dev/zot)' . Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): make gqlgen Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: check-log issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: gci issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: tests Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> --------- Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de> Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>
2024-07-29 12:32:51 -05:00
l, err = ldap.DialTLS("tcp", address, config) //nolint:staticcheck
2019-08-15 11:34:54 -05:00
if err != nil {
lc.Log.Error().Err(err).Str("address", address).Msg("failed to establish a TLS connection")
2019-08-15 11:34:54 -05:00
return err
}
}
lc.Conn = l
}
2019-08-15 11:34:54 -05:00
return nil
}
// Close closes the ldap backend connection.
func (lc *LDAPClient) Close() {
if lc.Conn != nil {
lc.Conn.Close()
lc.Conn = nil
}
}
const maxRetries = 8
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
}
2019-08-15 11:34:54 -05:00
// Authenticate authenticates the user against the ldap backend.
func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]string, []string, error) {
// serialize LDAP calls since some LDAP servers don't allow searches when binds are in flight
lc.lock.Lock()
defer lc.lock.Unlock()
2019-08-15 11:34:54 -05:00
if password == "" {
// RFC 4513 section 5.1.2
return false, nil, nil, errors.ErrLDAPEmptyPassphrase
2019-08-15 11:34:54 -05:00
}
connected := false
for retries := 0; !connected && sleepAndRetry(retries, maxRetries); retries++ {
err := lc.Connect()
2019-08-15 11:34:54 -05:00
if err != nil {
continue
2019-08-15 11:34:54 -05:00
}
// First bind with a read only user
if lc.BindPassword != "" {
err := lc.Conn.Bind(lc.BindDN, lc.BindPassword)
if err != nil {
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Msg("failed to bind")
// clean up the cached conn, so we can retry
lc.Conn.Close()
lc.Conn = nil
continue
}
} else {
err := lc.Conn.UnauthenticatedBind(lc.BindDN)
if err != nil {
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Msg("failed to bind")
// 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("failed to authenticate, exhausted all retries")
return false, nil, nil, errors.ErrLDAPBadConn
2019-08-15 11:34:54 -05:00
}
attributes := lc.Attributes
attributes = append(attributes, "dn")
if lc.UserGroupAttribute != "" {
attributes = append(attributes, lc.UserGroupAttribute)
}
searchScope := ldap.ScopeSingleLevel
if lc.SubtreeSearch {
searchScope = ldap.ScopeWholeSubtree
2019-08-15 11:34:54 -05:00
}
// Search for the given username
searchRequest := ldap.NewSearchRequest(
2019-08-15 11:34:54 -05:00
lc.Base,
searchScope, ldap.NeverDerefAliases, 0, 0, false,
2019-08-15 11:34:54 -05:00
fmt.Sprintf(lc.UserFilter, username),
attributes,
nil,
)
search, err := lc.Conn.Search(searchRequest)
2019-08-15 11:34:54 -05:00
if err != nil {
fmt.Printf("%v\n", err)
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Str("username", username).
Str("baseDN", lc.Base).Msg("failed to perform a search request")
return false, nil, nil, err
2019-08-15 11:34:54 -05:00
}
if len(search.Entries) < 1 {
2019-08-15 11:34:54 -05:00
err := errors.ErrBadUser
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Str("username", username).
Str("baseDN", lc.Base).Msg("failed to find entry")
return false, nil, nil, err
2019-08-15 11:34:54 -05:00
}
if len(search.Entries) > 1 {
2019-08-15 11:34:54 -05:00
err := errors.ErrEntriesExceeded
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Str("username", username).
Str("baseDN", lc.Base).Msg("failed to retrieve due to an excessive amount of entries")
return false, nil, nil, err
2019-08-15 11:34:54 -05:00
}
userDN := search.Entries[0].DN
var userGroups []string
if lc.UserGroupAttribute != "" && len(search.Entries[0].Attributes) > 0 {
userAttributes := search.Entries[0].Attributes[0]
userGroups = userAttributes.Values
}
ci(deps): upgrade golangci-lint (#2556) * ci(deps): upgrade golangci-lint Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de> * build(deps): removed disabled linters Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de> * build(deps): go run github.com/daixiang0/gci@latest write . Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run golang.org/x/tools/cmd/goimports@latest -l -w . Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run github.com/bombsimon/wsl/v4/cmd...@latest -strict-append -test=true -fix ./... Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run github.com/catenacyber/perfsprint@latest -fix ./... Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): replace gomnd by mnd Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): make gqlgen Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build: Revert "build(deps): go run github.com/daixiang0/gci@latest write ." This reverts commit 5bf8c42e1f48c7daf8d1a4dbcfbb8ddef8d0bbbf. Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): go run github.com/daixiang0/gci@latest write -s 'standard' -s default -s 'prefix(zotregistry.dev/zot)' . Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * build(deps): make gqlgen Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: wsl issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: check-log issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: gci issues Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> * fix: tests Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de> --------- Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de> Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>
2024-07-29 12:32:51 -05:00
2019-08-15 11:34:54 -05:00
user := map[string]string{}
2019-08-15 11:34:54 -05:00
for _, attr := range lc.Attributes {
user[attr] = search.Entries[0].GetAttributeValue(attr)
2019-08-15 11:34:54 -05:00
}
// Bind as the user to verify their password
err = lc.Conn.Bind(userDN, password)
if err != nil {
lc.Log.Error().Err(err).Str("bindDN", userDN).Msg("failed to bind user")
return false, user, userGroups, err
2019-08-15 11:34:54 -05:00
}
return true, user, userGroups, nil
2019-08-15 11:34:54 -05:00
}