2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2019-10-04 13:35:33 -04:00
package ldap
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
2021-03-04 15:17:36 -05:00
"math/rand"
2020-03-22 01:47:26 -04:00
"net"
2021-07-22 15:13:21 -04:00
"strconv"
2020-03-22 01:47:26 -04:00
"strings"
2019-10-04 13:35:33 -04:00
"time"
2021-02-04 16:49:52 -05:00
ldap "github.com/go-ldap/ldap/v3"
2021-07-24 14:57:36 -04:00
"github.com/minio/minio-go/v7/pkg/set"
2021-07-22 15:13:21 -04:00
"github.com/minio/minio/internal/auth"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/config"
2021-07-22 19:43:57 -04:00
"github.com/minio/minio/internal/logger"
2021-05-28 18:17:01 -04:00
"github.com/minio/pkg/env"
2019-10-04 13:35:33 -04:00
)
const (
defaultLDAPExpiry = time . Hour * 1
2021-01-25 17:26:10 -05:00
dnDelimiter = ";"
2021-07-22 15:13:21 -04:00
minLDAPExpiry time . Duration = 15 * time . Minute
maxLDAPExpiry time . Duration = 365 * 24 * time . Hour
2019-10-04 13:35:33 -04:00
)
// Config contains AD/LDAP server connectivity information.
type Config struct {
2019-10-23 01:59:13 -04:00
Enabled bool ` json:"enabled" `
2019-10-04 13:35:33 -04:00
// E.g. "ldap.minio.io:636"
ServerAddr string ` json:"serverAddr" `
// STS credentials expiry duration
2019-10-23 01:59:13 -04:00
STSExpiryDuration string ` json:"stsExpiryDuration" `
2019-10-04 13:35:33 -04:00
// Format string for usernames
2021-01-18 00:54:32 -05:00
UsernameFormat string ` json:"usernameFormat" `
UsernameFormats [ ] string ` json:"-" `
2019-10-04 13:35:33 -04:00
2021-01-25 17:26:10 -05:00
// User DN search parameters
UserDNSearchBaseDN string ` json:"userDNSearchBaseDN" `
UserDNSearchFilter string ` json:"userDNSearchFilter" `
// Group search parameters
2021-01-18 00:54:32 -05:00
GroupSearchBaseDistName string ` json:"groupSearchBaseDN" `
GroupSearchBaseDistNames [ ] string ` json:"-" `
GroupSearchFilter string ` json:"groupSearchFilter" `
2019-10-23 01:59:13 -04:00
2021-01-25 17:26:10 -05:00
// Lookup bind LDAP service account
LookupBindDN string ` json:"lookupBindDN" `
LookupBindPassword string ` json:"lookupBindPassword" `
2019-10-23 01:59:13 -04:00
stsExpiryDuration time . Duration // contains converted value
tlsSkipVerify bool // allows skipping TLS verification
2021-01-25 17:26:10 -05:00
serverInsecure bool // allows plain text connection to LDAP server
serverStartTLS bool // allows using StartTLS connection to LDAP server
isUsingLookupBind bool
2019-10-23 01:59:13 -04:00
rootCAs * x509 . CertPool
2019-10-04 13:35:33 -04:00
}
// LDAP keys and envs.
const (
2021-01-25 17:26:10 -05:00
ServerAddr = "server_addr"
STSExpiry = "sts_expiry"
LookupBindDN = "lookup_bind_dn"
LookupBindPassword = "lookup_bind_password"
UserDNSearchBaseDN = "user_dn_search_base_dn"
UserDNSearchFilter = "user_dn_search_filter"
UsernameFormat = "username_format"
GroupSearchFilter = "group_search_filter"
GroupSearchBaseDN = "group_search_base_dn"
TLSSkipVerify = "tls_skip_verify"
ServerInsecure = "server_insecure"
ServerStartTLS = "server_starttls"
2020-03-22 01:47:26 -04:00
2021-01-18 00:54:32 -05:00
EnvServerAddr = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
EnvSTSExpiry = "MINIO_IDENTITY_LDAP_STS_EXPIRY"
EnvTLSSkipVerify = "MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"
EnvServerInsecure = "MINIO_IDENTITY_LDAP_SERVER_INSECURE"
EnvServerStartTLS = "MINIO_IDENTITY_LDAP_SERVER_STARTTLS"
EnvUsernameFormat = "MINIO_IDENTITY_LDAP_USERNAME_FORMAT"
2021-01-25 17:26:10 -05:00
EnvUserDNSearchBaseDN = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"
EnvUserDNSearchFilter = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"
2021-01-18 00:54:32 -05:00
EnvGroupSearchFilter = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"
EnvGroupSearchBaseDN = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"
2021-01-25 17:26:10 -05:00
EnvLookupBindDN = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"
EnvLookupBindPassword = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"
2019-10-04 13:35:33 -04:00
)
2021-01-26 12:53:29 -05:00
var removedKeys = [ ] string {
"username_search_filter" ,
"username_search_base_dn" ,
2021-02-10 19:52:49 -05:00
"group_name_attribute" ,
2021-01-26 12:53:29 -05:00
}
2019-10-23 01:59:13 -04:00
// DefaultKVS - default config for LDAP config
var (
DefaultKVS = config . KVS {
2019-11-20 18:10:24 -05:00
config . KV {
Key : ServerAddr ,
Value : "" ,
} ,
config . KV {
2020-03-22 01:47:26 -04:00
Key : UsernameFormat ,
Value : "" ,
2019-11-20 18:10:24 -05:00
} ,
config . KV {
2021-01-25 17:26:10 -05:00
Key : UserDNSearchBaseDN ,
2020-03-22 01:47:26 -04:00
Value : "" ,
} ,
config . KV {
2021-01-25 17:26:10 -05:00
Key : UserDNSearchFilter ,
2019-11-20 18:10:24 -05:00
Value : "" ,
} ,
config . KV {
Key : GroupSearchFilter ,
Value : "" ,
} ,
config . KV {
Key : GroupSearchBaseDN ,
Value : "" ,
} ,
2020-03-22 01:47:26 -04:00
config . KV {
Key : STSExpiry ,
Value : "1h" ,
} ,
2019-11-20 18:10:24 -05:00
config . KV {
Key : TLSSkipVerify ,
2019-12-04 18:32:37 -05:00
Value : config . EnableOff ,
2019-11-20 18:10:24 -05:00
} ,
2020-03-19 22:20:51 -04:00
config . KV {
Key : ServerInsecure ,
Value : config . EnableOff ,
} ,
2020-05-07 18:08:33 -04:00
config . KV {
Key : ServerStartTLS ,
Value : config . EnableOff ,
} ,
2021-01-25 17:26:10 -05:00
config . KV {
Key : LookupBindDN ,
Value : "" ,
} ,
config . KV {
Key : LookupBindPassword ,
Value : "" ,
} ,
2019-10-23 01:59:13 -04:00
}
)
2020-03-22 01:47:26 -04:00
func getGroups ( conn * ldap . Conn , sreq * ldap . SearchRequest ) ( [ ] string , error ) {
var groups [ ] string
sres , err := conn . Search ( sreq )
if err != nil {
2021-01-25 17:26:10 -05:00
// Check if there is no matching result and return empty slice.
// Ref: https://ldap.com/ldap-result-code-reference/
if ldap . IsErrorWithCode ( err , 32 ) {
return nil , nil
}
2020-03-22 01:47:26 -04:00
return nil , err
}
for _ , entry := range sres . Entries {
// We only queried one attribute,
// so we only look up the first one.
2021-02-10 19:52:49 -05:00
groups = append ( groups , entry . DN )
2020-03-22 01:47:26 -04:00
}
return groups , nil
}
2021-01-25 17:26:10 -05:00
func ( l * Config ) lookupBind ( conn * ldap . Conn ) error {
2021-03-04 15:17:36 -05:00
var err error
2021-02-28 15:57:31 -05:00
if l . LookupBindPassword == "" {
2021-03-04 15:17:36 -05:00
err = conn . UnauthenticatedBind ( l . LookupBindDN )
} else {
err = conn . Bind ( l . LookupBindDN , l . LookupBindPassword )
}
if ldap . IsErrorWithCode ( err , 49 ) {
2021-05-25 17:17:33 -04:00
return fmt . Errorf ( "LDAP Lookup Bind user invalid credentials error: %w" , err )
2021-02-28 15:57:31 -05:00
}
2021-03-04 15:17:36 -05:00
return err
2021-01-25 17:26:10 -05:00
}
// usernameFormatsBind - Iterates over all given username formats and expects
// that only one will succeed if the credentials are valid. The succeeding
// bindDN is returned or an error.
2021-01-18 00:54:32 -05:00
//
// In the rare case that multiple username formats succeed, implying that two
// (or more) distinct users in the LDAP directory have the same username and
// password, we return an error as we cannot identify the account intended by
// the user.
2021-01-25 17:26:10 -05:00
func ( l * Config ) usernameFormatsBind ( conn * ldap . Conn , username , password string ) ( string , error ) {
2021-01-18 00:54:32 -05:00
var bindDistNames [ ] string
var errs = make ( [ ] error , len ( l . UsernameFormats ) )
var successCount = 0
2020-03-22 01:47:26 -04:00
for i , usernameFormat := range l . UsernameFormats {
bindDN := fmt . Sprintf ( usernameFormat , username )
// Bind with user credentials to validate the password
2021-01-18 00:54:32 -05:00
errs [ i ] = conn . Bind ( bindDN , password )
if errs [ i ] == nil {
bindDistNames = append ( bindDistNames , bindDN )
successCount ++
2021-03-04 15:17:36 -05:00
} else if ! ldap . IsErrorWithCode ( errs [ i ] , 49 ) {
2021-05-25 17:17:33 -04:00
return "" , fmt . Errorf ( "LDAP Bind request failed with unexpected error: %w" , errs [ i ] )
2020-03-22 01:47:26 -04:00
}
}
2021-01-18 00:54:32 -05:00
if successCount == 0 {
2021-01-25 17:26:10 -05:00
var errStrings [ ] string
2021-01-18 00:54:32 -05:00
for _ , err := range errs {
if err != nil {
errStrings = append ( errStrings , err . Error ( ) )
}
}
2021-03-04 15:17:36 -05:00
outErr := fmt . Sprintf ( "All username formats failed due to invalid credentials: %s" , strings . Join ( errStrings , "; " ) )
2021-01-18 00:54:32 -05:00
return "" , errors . New ( outErr )
}
if successCount > 1 {
successDistNames := strings . Join ( bindDistNames , ", " )
errMsg := fmt . Sprintf ( "Multiple username formats succeeded - ambiguous user login (succeeded for: %s)" , successDistNames )
return "" , errors . New ( errMsg )
}
return bindDistNames [ 0 ] , nil
2020-03-22 01:47:26 -04:00
}
2021-01-25 17:26:10 -05:00
// lookupUserDN searches for the DN of the user given their username. conn is
// assumed to be using the lookup bind service account. It is required that the
// search result in at most one result.
func ( l * Config ) lookupUserDN ( conn * ldap . Conn , username string ) ( string , error ) {
filter := strings . Replace ( l . UserDNSearchFilter , "%s" , ldap . EscapeFilter ( username ) , - 1 )
searchRequest := ldap . NewSearchRequest (
l . UserDNSearchBaseDN ,
ldap . ScopeWholeSubtree , ldap . NeverDerefAliases , 0 , 0 , false ,
filter ,
[ ] string { } , // only need DN, so no pass no attributes here
nil ,
)
searchResult , err := conn . Search ( searchRequest )
if err != nil {
return "" , err
}
if len ( searchResult . Entries ) == 0 {
return "" , fmt . Errorf ( "User DN for %s not found" , username )
}
if len ( searchResult . Entries ) != 1 {
return "" , fmt . Errorf ( "Multiple DNs for %s found - please fix the search filter" , username )
}
return searchResult . Entries [ 0 ] . DN , nil
}
2021-04-15 01:51:14 -04:00
func ( l * Config ) searchForUserGroups ( conn * ldap . Conn , username , bindDN string ) ( [ ] string , error ) {
// User groups lookup.
var groups [ ] string
if l . GroupSearchFilter != "" {
for _ , groupSearchBase := range l . GroupSearchBaseDistNames {
filter := strings . Replace ( l . GroupSearchFilter , "%s" , ldap . EscapeFilter ( username ) , - 1 )
filter = strings . Replace ( filter , "%d" , ldap . EscapeFilter ( bindDN ) , - 1 )
searchRequest := ldap . NewSearchRequest (
groupSearchBase ,
ldap . ScopeWholeSubtree , ldap . NeverDerefAliases , 0 , 0 , false ,
filter ,
nil ,
nil ,
)
var newGroups [ ] string
newGroups , err := getGroups ( conn , searchRequest )
if err != nil {
2021-05-25 17:17:33 -04:00
errRet := fmt . Errorf ( "Error finding groups of %s: %w" , bindDN , err )
2021-04-15 01:51:14 -04:00
return nil , errRet
}
groups = append ( groups , newGroups ... )
}
}
return groups , nil
}
2021-07-24 14:57:36 -04:00
// LookupUserDN searches for the full DN and groups of a given username
2021-04-15 01:51:14 -04:00
func ( l * Config ) LookupUserDN ( username string ) ( string , [ ] string , error ) {
if ! l . isUsingLookupBind {
return "" , nil , errors . New ( "current lookup mode does not support searching for User DN" )
}
conn , err := l . Connect ( )
if err != nil {
return "" , nil , err
}
defer conn . Close ( )
// Bind to the lookup user account
if err = l . lookupBind ( conn ) ; err != nil {
return "" , nil , err
}
// Lookup user DN
bindDN , err := l . lookupUserDN ( conn , username )
if err != nil {
errRet := fmt . Errorf ( "Unable to find user DN: %w" , err )
return "" , nil , errRet
}
groups , err := l . searchForUserGroups ( conn , username , bindDN )
if err != nil {
return "" , nil , err
}
return bindDN , groups , nil
}
2021-01-18 00:54:32 -05:00
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
// user and the list of groups.
func ( l * Config ) Bind ( username , password string ) ( string , [ ] string , error ) {
2020-03-22 01:47:26 -04:00
conn , err := l . Connect ( )
if err != nil {
2021-01-18 00:54:32 -05:00
return "" , nil , err
2020-03-22 01:47:26 -04:00
}
defer conn . Close ( )
2021-01-25 17:26:10 -05:00
var bindDN string
if l . isUsingLookupBind {
// Bind to the lookup user account
if err = l . lookupBind ( conn ) ; err != nil {
return "" , nil , err
}
// Lookup user DN
bindDN , err = l . lookupUserDN ( conn , username )
if err != nil {
2021-05-25 17:17:33 -04:00
errRet := fmt . Errorf ( "Unable to find user DN: %w" , err )
2021-01-25 17:26:10 -05:00
return "" , nil , errRet
}
// Authenticate the user credentials.
err = conn . Bind ( bindDN , password )
if err != nil {
2021-05-25 17:17:33 -04:00
errRet := fmt . Errorf ( "LDAP auth failed for DN %s: %w" , bindDN , err )
2021-01-25 17:26:10 -05:00
return "" , nil , errRet
}
2021-01-27 20:31:21 -05:00
// Bind to the lookup user account again to perform group search.
if err = l . lookupBind ( conn ) ; err != nil {
return "" , nil , err
}
2021-01-25 17:26:10 -05:00
} else {
// Verify login credentials by checking the username formats.
bindDN , err = l . usernameFormatsBind ( conn , username , password )
if err != nil {
return "" , nil , err
}
// Bind to the successful bindDN again.
err = conn . Bind ( bindDN , password )
if err != nil {
2021-05-25 17:17:33 -04:00
errRet := fmt . Errorf ( "LDAP conn failed though auth for DN %s succeeded: %w" , bindDN , err )
2021-01-25 17:26:10 -05:00
return "" , nil , errRet
}
2020-03-22 01:47:26 -04:00
}
2021-01-25 17:26:10 -05:00
// User groups lookup.
2021-04-15 01:51:14 -04:00
groups , err := l . searchForUserGroups ( conn , username , bindDN )
if err != nil {
return "" , nil , err
2020-03-22 01:47:26 -04:00
}
2021-01-18 00:54:32 -05:00
return bindDN , groups , nil
2020-03-22 01:47:26 -04:00
}
2019-10-04 13:35:33 -04:00
// Connect connect to ldap server.
func ( l * Config ) Connect ( ) ( ldapConn * ldap . Conn , err error ) {
if l == nil {
2020-03-22 01:47:26 -04:00
return nil , errors . New ( "LDAP is not configured" )
}
2021-05-04 23:13:24 -04:00
serverHost , _ , err := net . SplitHostPort ( l . ServerAddr )
if err != nil {
serverHost = l . ServerAddr
2020-03-22 01:47:26 -04:00
// User default LDAP port if none specified "636"
l . ServerAddr = net . JoinHostPort ( l . ServerAddr , "636" )
2019-10-04 13:35:33 -04:00
}
2020-03-19 22:20:51 -04:00
if l . serverInsecure {
return ldap . Dial ( "tcp" , l . ServerAddr )
}
2020-03-22 01:47:26 -04:00
2021-05-04 23:13:24 -04:00
tlsConfig := & tls . Config {
InsecureSkipVerify : l . tlsSkipVerify ,
RootCAs : l . rootCAs ,
ServerName : serverHost ,
}
2020-05-07 18:08:33 -04:00
if l . serverStartTLS {
conn , err := ldap . Dial ( "tcp" , l . ServerAddr )
if err != nil {
return nil , err
}
2021-05-04 23:13:24 -04:00
err = conn . StartTLS ( tlsConfig )
2020-05-07 18:08:33 -04:00
return conn , err
}
2021-05-04 23:13:24 -04:00
return ldap . DialTLS ( "tcp" , l . ServerAddr , tlsConfig )
2019-10-04 13:35:33 -04:00
}
// GetExpiryDuration - return parsed expiry duration.
2021-07-22 15:13:21 -04:00
func ( l Config ) GetExpiryDuration ( dsecs string ) ( time . Duration , error ) {
if dsecs == "" {
return l . stsExpiryDuration , nil
}
d , err := strconv . Atoi ( dsecs )
if err != nil {
return 0 , auth . ErrInvalidDuration
}
dur := time . Duration ( d ) * time . Second
if dur < minLDAPExpiry || dur > maxLDAPExpiry {
return 0 , auth . ErrInvalidDuration
}
return dur , nil
2019-10-04 13:35:33 -04:00
}
2021-03-04 15:17:36 -05:00
func ( l Config ) testConnection ( ) error {
conn , err := l . Connect ( )
if err != nil {
2021-05-25 17:17:33 -04:00
return fmt . Errorf ( "Error creating connection to LDAP server: %w" , err )
2021-03-04 15:17:36 -05:00
}
defer conn . Close ( )
if l . isUsingLookupBind {
if err = l . lookupBind ( conn ) ; err != nil {
2021-05-25 17:17:33 -04:00
return fmt . Errorf ( "Error connecting as LDAP Lookup Bind user: %w" , err )
2021-03-04 15:17:36 -05:00
}
return nil
}
// Generate some random user credentials for username formats mode test.
username := fmt . Sprintf ( "sometestuser%09d" , rand . Int31n ( 1000000000 ) )
charset := [ ] byte ( "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" )
rand . Shuffle ( len ( charset ) , func ( i , j int ) {
charset [ i ] , charset [ j ] = charset [ j ] , charset [ i ]
} )
password := string ( charset [ : 20 ] )
_ , err = l . usernameFormatsBind ( conn , username , password )
if err == nil {
// We don't expect to successfully guess a credential in this
// way.
return fmt . Errorf ( "Unexpected random credentials success for user=%s password=%s" , username , password )
} else if strings . HasPrefix ( err . Error ( ) , "All username formats failed due to invalid credentials: " ) {
return nil
}
2021-05-25 17:17:33 -04:00
return fmt . Errorf ( "LDAP connection test error: %w" , err )
2021-03-04 15:17:36 -05:00
}
2021-07-21 02:33:12 -04:00
// IsLDAPUserDN determines if the given string could be a user DN from LDAP.
func ( l Config ) IsLDAPUserDN ( user string ) bool {
return strings . HasSuffix ( user , "," + l . UserDNSearchBaseDN )
}
2021-07-24 14:57:36 -04:00
// GetNonExistentUserDistNames - find user accounts (DNs) that are no longer
// present in the LDAP server.
func ( l * Config ) GetNonExistentUserDistNames ( userDistNames [ ] string ) ( [ ] string , error ) {
2021-07-21 02:33:12 -04:00
if ! l . isUsingLookupBind {
return nil , errors . New ( "current LDAP configuration does not permit looking for expired user accounts" )
}
conn , err := l . Connect ( )
if err != nil {
return nil , err
}
defer conn . Close ( )
// Bind to the lookup user account
if err = l . lookupBind ( conn ) ; err != nil {
return nil , err
}
nonExistentUsers := [ ] string { }
2021-07-24 14:57:36 -04:00
for _ , dn := range userDistNames {
2021-07-21 02:33:12 -04:00
searchRequest := ldap . NewSearchRequest (
dn ,
ldap . ScopeBaseObject , ldap . NeverDerefAliases , 0 , 0 , false ,
"(objectclass=*)" ,
[ ] string { } , // only need DN, so no pass no attributes here
nil ,
)
searchResult , err := conn . Search ( searchRequest )
if err != nil {
// Object does not exist error?
if ldap . IsErrorWithCode ( err , 32 ) {
nonExistentUsers = append ( nonExistentUsers , dn )
continue
}
return nil , err
}
if len ( searchResult . Entries ) == 0 {
// DN was not found - this means this user account is
// expired.
nonExistentUsers = append ( nonExistentUsers , dn )
}
}
return nonExistentUsers , nil
}
2021-07-24 14:57:36 -04:00
// LookupGroupMemberships - for each DN finds the set of LDAP groups they are a
// member of.
func ( l * Config ) LookupGroupMemberships ( userDistNames [ ] string , userDNToUsernameMap map [ string ] string ) ( map [ string ] set . StringSet , error ) {
if ! l . isUsingLookupBind {
return nil , errors . New ( "current LDAP configuration does not permit this lookup" )
}
conn , err := l . Connect ( )
if err != nil {
return nil , err
}
defer conn . Close ( )
// Bind to the lookup user account
if err = l . lookupBind ( conn ) ; err != nil {
return nil , err
}
res := make ( map [ string ] set . StringSet , len ( userDistNames ) )
for _ , userDistName := range userDistNames {
username := userDNToUsernameMap [ userDistName ]
groups , err := l . searchForUserGroups ( conn , username , userDistName )
if err != nil {
return nil , err
}
res [ userDistName ] = set . CreateStringSet ( groups ... )
}
return res , nil
}
2021-07-21 02:33:12 -04:00
// EnabledWithLookupBind - checks if ldap IDP is enabled in lookup bind mode.
func ( l Config ) EnabledWithLookupBind ( ) bool {
return l . Enabled && l . isUsingLookupBind
}
2019-12-04 18:32:37 -05:00
// Enabled returns if jwks is enabled.
func Enabled ( kvs config . KVS ) bool {
return kvs . Get ( ServerAddr ) != ""
}
2019-10-04 13:35:33 -04:00
// Lookup - initializes LDAP config, overrides config, if any ENV values are set.
2019-10-23 01:59:13 -04:00
func Lookup ( kvs config . KVS , rootCAs * x509 . CertPool ) ( l Config , err error ) {
l = Config { }
2021-01-26 12:53:29 -05:00
// Purge all removed keys first
for _ , k := range removedKeys {
kvs . Delete ( k )
}
2019-10-23 01:59:13 -04:00
if err = config . CheckValidKeys ( config . IdentityLDAPSubSys , kvs , DefaultKVS ) ; err != nil {
return l , err
}
ldapServer := env . Get ( EnvServerAddr , kvs . Get ( ServerAddr ) )
2019-10-04 13:35:33 -04:00
if ldapServer == "" {
return l , nil
}
2019-10-23 01:59:13 -04:00
l . Enabled = true
2021-07-01 20:41:01 -04:00
l . rootCAs = rootCAs
2019-10-04 13:35:33 -04:00
l . ServerAddr = ldapServer
l . stsExpiryDuration = defaultLDAPExpiry
2019-10-23 01:59:13 -04:00
if v := env . Get ( EnvSTSExpiry , kvs . Get ( STSExpiry ) ) ; v != "" {
2021-08-02 21:20:06 -04:00
logger . Info ( "DEPRECATION WARNING: Support for configuring the default LDAP credentials expiry duration will be removed by October 2021. Please use the `DurationSeconds` parameter in the LDAP STS API instead." )
2019-10-04 13:35:33 -04:00
expDur , err := time . ParseDuration ( v )
if err != nil {
return l , errors . New ( "LDAP expiry time err:" + err . Error ( ) )
}
2021-07-22 15:13:21 -04:00
if expDur < minLDAPExpiry {
return l , fmt . Errorf ( "LDAP expiry time must be at least %s" , minLDAPExpiry )
}
if expDur > maxLDAPExpiry {
return l , fmt . Errorf ( "LDAP expiry time may not exceed %s" , maxLDAPExpiry )
2019-10-04 13:35:33 -04:00
}
l . STSExpiryDuration = v
l . stsExpiryDuration = expDur
}
2021-01-25 17:26:10 -05:00
// LDAP connection configuration
2020-03-19 22:20:51 -04:00
if v := env . Get ( EnvServerInsecure , kvs . Get ( ServerInsecure ) ) ; v != "" {
l . serverInsecure , err = config . ParseBool ( v )
if err != nil {
return l , err
}
}
2020-05-07 18:08:33 -04:00
if v := env . Get ( EnvServerStartTLS , kvs . Get ( ServerStartTLS ) ) ; v != "" {
l . serverStartTLS , err = config . ParseBool ( v )
if err != nil {
return l , err
}
}
2019-10-23 01:59:13 -04:00
if v := env . Get ( EnvTLSSkipVerify , kvs . Get ( TLSSkipVerify ) ) ; v != "" {
l . tlsSkipVerify , err = config . ParseBool ( v )
if err != nil {
return l , err
}
}
2021-01-25 17:26:10 -05:00
// Lookup bind user configuration
lookupBindDN := env . Get ( EnvLookupBindDN , kvs . Get ( LookupBindDN ) )
lookupBindPassword := env . Get ( EnvLookupBindPassword , kvs . Get ( LookupBindPassword ) )
2021-02-28 15:57:31 -05:00
if lookupBindDN != "" {
2021-01-25 17:26:10 -05:00
l . LookupBindDN = lookupBindDN
l . LookupBindPassword = lookupBindPassword
l . isUsingLookupBind = true
// User DN search configuration
userDNSearchBaseDN := env . Get ( EnvUserDNSearchBaseDN , kvs . Get ( UserDNSearchBaseDN ) )
2021-02-04 14:07:29 -05:00
userDNSearchFilter := env . Get ( EnvUserDNSearchFilter , kvs . Get ( UserDNSearchFilter ) )
2021-01-25 17:26:10 -05:00
if userDNSearchFilter == "" || userDNSearchBaseDN == "" {
return l , errors . New ( "In lookup bind mode, userDN search base DN and userDN search filter are both required" )
}
l . UserDNSearchBaseDN = userDNSearchBaseDN
l . UserDNSearchFilter = userDNSearchFilter
}
// Username format configuration.
2019-10-23 01:59:13 -04:00
if v := env . Get ( EnvUsernameFormat , kvs . Get ( UsernameFormat ) ) ; v != "" {
2020-03-22 01:47:26 -04:00
if ! strings . Contains ( v , "%s" ) {
2021-08-02 21:20:06 -04:00
return l , errors . New ( "LDAP username format does not support '%s' substitution" )
2019-10-04 13:35:33 -04:00
}
2020-03-22 01:47:26 -04:00
l . UsernameFormats = strings . Split ( v , dnDelimiter )
2019-10-04 13:35:33 -04:00
}
2021-08-02 21:20:06 -04:00
if len ( l . UsernameFormats ) > 0 {
logger . Info ( "DEPRECATION WARNING: Support for %s will be removed by October 2021, please migrate your LDAP settings to lookup bind mode" , UsernameFormat )
}
// Either lookup bind mode or username format is supported, but not both.
2021-01-25 17:26:10 -05:00
if l . isUsingLookupBind && len ( l . UsernameFormats ) > 0 {
return l , errors . New ( "Lookup Bind mode and Username Format mode are not supported at the same time" )
}
// At least one of bind mode or username format must be used.
if ! l . isUsingLookupBind && len ( l . UsernameFormats ) == 0 {
2021-08-02 21:20:06 -04:00
return l , errors . New ( "Either Lookup Bind mode or Username Format mode is required" )
2021-01-25 17:26:10 -05:00
}
2021-03-04 15:17:36 -05:00
// Test connection to LDAP server.
if err := l . testConnection ( ) ; err != nil {
2021-05-25 17:17:33 -04:00
return l , fmt . Errorf ( "Connection test for LDAP server failed: %w" , err )
2021-03-04 15:17:36 -05:00
}
2021-01-25 17:26:10 -05:00
// Group search params configuration
2019-10-23 01:59:13 -04:00
grpSearchFilter := env . Get ( EnvGroupSearchFilter , kvs . Get ( GroupSearchFilter ) )
grpSearchBaseDN := env . Get ( EnvGroupSearchBaseDN , kvs . Get ( GroupSearchBaseDN ) )
2019-10-04 13:35:33 -04:00
// Either all group params must be set or none must be set.
2021-02-10 19:52:49 -05:00
if ( grpSearchFilter != "" && grpSearchBaseDN == "" ) || ( grpSearchFilter == "" && grpSearchBaseDN != "" ) {
return l , errors . New ( "All group related parameters must be set" )
2019-10-04 13:35:33 -04:00
}
2021-02-10 19:52:49 -05:00
if grpSearchFilter != "" {
2019-10-04 13:35:33 -04:00
l . GroupSearchFilter = grpSearchFilter
2021-01-25 17:26:10 -05:00
l . GroupSearchBaseDistName = grpSearchBaseDN
2021-01-18 00:54:32 -05:00
l . GroupSearchBaseDistNames = strings . Split ( l . GroupSearchBaseDistName , dnDelimiter )
2019-10-04 13:35:33 -04:00
}
2019-10-23 01:59:13 -04:00
return l , nil
2019-10-04 13:35:33 -04:00
}