minio/internal/config/identity/ldap/config.go
Aditya Manthramurthy 0c855638de
fix: LDAP init. issue when LDAP server is down (#19619)
At server startup, LDAP configuration is validated against the LDAP
server. If the LDAP server is down at that point, we need to cleanly
disable LDAP configuration. Previously, LDAP would remain configured but
error out in strange ways because initialization did not complete
without errors.
2024-04-25 14:28:16 -07:00

308 lines
7.9 KiB
Go

// 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/>.
package ldap
import (
"crypto/x509"
"errors"
"sort"
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/config"
"github.com/minio/pkg/v2/ldap"
)
const (
defaultLDAPExpiry = time.Hour * 1
minLDAPExpiry time.Duration = 15 * time.Minute
maxLDAPExpiry time.Duration = 365 * 24 * time.Hour
)
// Config contains AD/LDAP server connectivity information.
type Config struct {
LDAP ldap.Config
stsExpiryDuration time.Duration // contains converted value
}
// Enabled returns if LDAP is enabled.
func (l *Config) Enabled() bool {
return l.LDAP.Enabled
}
// Clone returns a cloned copy of LDAP config.
func (l *Config) Clone() Config {
if l == nil {
return Config{}
}
cfg := Config{
LDAP: l.LDAP.Clone(),
stsExpiryDuration: l.stsExpiryDuration,
}
return cfg
}
// LDAP keys and envs.
const (
ServerAddr = "server_addr"
SRVRecordName = "srv_record_name"
LookupBindDN = "lookup_bind_dn"
LookupBindPassword = "lookup_bind_password"
UserDNSearchBaseDN = "user_dn_search_base_dn"
UserDNSearchFilter = "user_dn_search_filter"
GroupSearchFilter = "group_search_filter"
GroupSearchBaseDN = "group_search_base_dn"
TLSSkipVerify = "tls_skip_verify"
ServerInsecure = "server_insecure"
ServerStartTLS = "server_starttls"
EnvServerAddr = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
EnvSRVRecordName = "MINIO_IDENTITY_LDAP_SRV_RECORD_NAME"
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"
EnvUserDNSearchBaseDN = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"
EnvUserDNSearchFilter = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"
EnvGroupSearchFilter = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"
EnvGroupSearchBaseDN = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"
EnvLookupBindDN = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"
EnvLookupBindPassword = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"
)
var removedKeys = []string{
"sts_expiry",
"username_format",
"username_search_filter",
"username_search_base_dn",
"group_name_attribute",
}
// DefaultKVS - default config for LDAP config
var (
DefaultKVS = config.KVS{
config.KV{
Key: config.Enable,
Value: "",
},
config.KV{
Key: ServerAddr,
Value: "",
},
config.KV{
Key: SRVRecordName,
Value: "",
},
config.KV{
Key: UserDNSearchBaseDN,
Value: "",
},
config.KV{
Key: UserDNSearchFilter,
Value: "",
},
config.KV{
Key: GroupSearchFilter,
Value: "",
},
config.KV{
Key: GroupSearchBaseDN,
Value: "",
},
config.KV{
Key: TLSSkipVerify,
Value: config.EnableOff,
},
config.KV{
Key: ServerInsecure,
Value: config.EnableOff,
},
config.KV{
Key: ServerStartTLS,
Value: config.EnableOff,
},
config.KV{
Key: LookupBindDN,
Value: "",
},
config.KV{
Key: LookupBindPassword,
Value: "",
},
}
)
// Enabled returns if LDAP config is enabled.
func Enabled(kvs config.KVS) bool {
return kvs.Get(ServerAddr) != ""
}
// Lookup - initializes LDAP config, overrides config, if any ENV values are set.
func Lookup(s config.Config, rootCAs *x509.CertPool) (l Config, err error) {
l = Config{}
// Purge all removed keys first
kvs := s[config.IdentityLDAPSubSys][config.Default]
if len(kvs) > 0 {
for _, k := range removedKeys {
kvs.Delete(k)
}
s[config.IdentityLDAPSubSys][config.Default] = kvs
}
if err := s.CheckValidKeys(config.IdentityLDAPSubSys, removedKeys); err != nil {
return l, err
}
getCfgVal := func(cfgParam string) string {
// As parameters are already validated, we skip checking
// if the config param was found.
val, _, _ := s.ResolveConfigParam(config.IdentityLDAPSubSys, config.Default, cfgParam, false)
return val
}
ldapServer := getCfgVal(ServerAddr)
if ldapServer == "" {
return l, nil
}
l.LDAP = ldap.Config{
RootCAs: rootCAs,
ServerAddr: ldapServer,
SRVRecordName: getCfgVal(SRVRecordName),
}
// Parse explicitly set enable=on/off flag.
isEnableFlagExplicitlySet := false
if v := getCfgVal(config.Enable); v != "" {
isEnableFlagExplicitlySet = true
l.LDAP.Enabled, err = config.ParseBool(v)
if err != nil {
return l, err
}
}
l.stsExpiryDuration = defaultLDAPExpiry
// LDAP connection configuration
if v := getCfgVal(ServerInsecure); v != "" {
l.LDAP.ServerInsecure, err = config.ParseBool(v)
if err != nil {
return l, err
}
}
if v := getCfgVal(ServerStartTLS); v != "" {
l.LDAP.ServerStartTLS, err = config.ParseBool(v)
if err != nil {
return l, err
}
}
if v := getCfgVal(TLSSkipVerify); v != "" {
l.LDAP.TLSSkipVerify, err = config.ParseBool(v)
if err != nil {
return l, err
}
}
// Lookup bind user configuration
l.LDAP.LookupBindDN = getCfgVal(LookupBindDN)
l.LDAP.LookupBindPassword = getCfgVal(LookupBindPassword)
// User DN search configuration
l.LDAP.UserDNSearchFilter = getCfgVal(UserDNSearchFilter)
l.LDAP.UserDNSearchBaseDistName = getCfgVal(UserDNSearchBaseDN)
// Group search params configuration
l.LDAP.GroupSearchFilter = getCfgVal(GroupSearchFilter)
l.LDAP.GroupSearchBaseDistName = getCfgVal(GroupSearchBaseDN)
// If enable flag was not explicitly set, we treat it as implicitly set at
// this point as necessary configuration is available.
if !isEnableFlagExplicitlySet && !l.LDAP.Enabled {
l.LDAP.Enabled = true
}
// Validate and test configuration.
valResult := l.LDAP.Validate()
if !valResult.IsOk() {
// Set to false if configuration fails to validate.
l.LDAP.Enabled = false
return l, valResult
}
return l, nil
}
// GetConfigList - returns a list of LDAP configurations.
func (l *Config) GetConfigList(s config.Config) ([]madmin.IDPListItem, error) {
ldapConfigs, err := s.GetAvailableTargets(config.IdentityLDAPSubSys)
if err != nil {
return nil, err
}
// For now, ldapConfigs will only have a single entry for the default
// configuration.
var res []madmin.IDPListItem
for _, cfg := range ldapConfigs {
res = append(res, madmin.IDPListItem{
Type: "ldap",
Name: cfg,
Enabled: l.Enabled(),
})
}
return res, nil
}
// ErrProviderConfigNotFound - represents a non-existing provider error.
var ErrProviderConfigNotFound = errors.New("provider configuration not found")
// GetConfigInfo - returns config details for an LDAP configuration.
func (l *Config) GetConfigInfo(s config.Config, cfgName string) ([]madmin.IDPCfgInfo, error) {
// For now only a single LDAP config is supported.
if cfgName != madmin.Default {
return nil, ErrProviderConfigNotFound
}
kvsrcs, err := s.GetResolvedConfigParams(config.IdentityLDAPSubSys, cfgName, true)
if err != nil {
return nil, err
}
res := make([]madmin.IDPCfgInfo, 0, len(kvsrcs))
for _, kvsrc := range kvsrcs {
// skip default values.
if kvsrc.Src == config.ValueSourceDef {
continue
}
res = append(res, madmin.IDPCfgInfo{
Key: kvsrc.Key,
Value: kvsrc.Value,
IsCfg: true,
IsEnv: kvsrc.Src == config.ValueSourceEnv,
})
}
// sort the structs by the key
sort.Slice(res, func(i, j int) bool {
return res[i].Key < res[j].Key
})
return res, nil
}