mirror of
				https://github.com/minio/minio.git
				synced 2025-10-31 00:01:27 +01:00 
			
		
		
		
	This change uses the updated ldap library in minio/pkg (bumped up to v3). A new config parameter is added for LDAP configuration to specify extra user attributes to load from the LDAP server and to store them as additional claims for the user. A test is added in sts_handlers.go that shows how to access the LDAP attributes as a claim. This is in preparation for adding SSH pubkey authentication to MinIO's SFTP integration.
		
			
				
	
	
		
			1493 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1493 lines
		
	
	
		
			38 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 config
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"regexp"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/minio/madmin-go/v3"
 | |
| 	"github.com/minio/minio-go/v7/pkg/set"
 | |
| 	"github.com/minio/minio/internal/auth"
 | |
| 	"github.com/minio/pkg/v3/env"
 | |
| )
 | |
| 
 | |
| // ErrorConfig holds the config error types
 | |
| type ErrorConfig interface {
 | |
| 	ErrConfigGeneric | ErrConfigNotFound
 | |
| }
 | |
| 
 | |
| // ErrConfigGeneric is a generic config type
 | |
| type ErrConfigGeneric struct {
 | |
| 	msg string
 | |
| }
 | |
| 
 | |
| func (ge *ErrConfigGeneric) setMsg(msg string) {
 | |
| 	ge.msg = msg
 | |
| }
 | |
| 
 | |
| func (ge ErrConfigGeneric) Error() string {
 | |
| 	return ge.msg
 | |
| }
 | |
| 
 | |
| // ErrConfigNotFound is an error to indicate
 | |
| // that a config parameter is not found
 | |
| type ErrConfigNotFound struct {
 | |
| 	ErrConfigGeneric
 | |
| }
 | |
| 
 | |
| // Error creates an error message and wraps
 | |
| // it with the error type specified in the type parameter
 | |
| func Error[T ErrorConfig, PT interface {
 | |
| 	*T
 | |
| 	setMsg(string)
 | |
| }](format string, vals ...interface{},
 | |
| ) T {
 | |
| 	pt := PT(new(T))
 | |
| 	pt.setMsg(fmt.Sprintf(format, vals...))
 | |
| 	return *pt
 | |
| }
 | |
| 
 | |
| // Errorf formats an error and returns it as a generic config error
 | |
| func Errorf(format string, vals ...interface{}) ErrConfigGeneric {
 | |
| 	return Error[ErrConfigGeneric](format, vals...)
 | |
| }
 | |
| 
 | |
| // Default keys
 | |
| const (
 | |
| 	Default = madmin.Default
 | |
| 	Enable  = madmin.EnableKey
 | |
| 	Comment = madmin.CommentKey
 | |
| 
 | |
| 	EnvSeparator = "="
 | |
| 
 | |
| 	// Enable values
 | |
| 	EnableOn  = madmin.EnableOn
 | |
| 	EnableOff = madmin.EnableOff
 | |
| 
 | |
| 	RegionKey  = "region"
 | |
| 	NameKey    = "name"
 | |
| 	RegionName = "name"
 | |
| 	AccessKey  = "access_key"
 | |
| 	SecretKey  = "secret_key"
 | |
| 	License    = "license" // Deprecated Dec 2021
 | |
| 	APIKey     = "api_key"
 | |
| 	Proxy      = "proxy"
 | |
| )
 | |
| 
 | |
| // Top level config constants.
 | |
| const (
 | |
| 	PolicyOPASubSys      = madmin.PolicyOPASubSys
 | |
| 	PolicyPluginSubSys   = madmin.PolicyPluginSubSys
 | |
| 	IdentityOpenIDSubSys = madmin.IdentityOpenIDSubSys
 | |
| 	IdentityLDAPSubSys   = madmin.IdentityLDAPSubSys
 | |
| 	IdentityTLSSubSys    = madmin.IdentityTLSSubSys
 | |
| 	IdentityPluginSubSys = madmin.IdentityPluginSubSys
 | |
| 	CacheSubSys          = madmin.CacheSubSys
 | |
| 	SiteSubSys           = madmin.SiteSubSys
 | |
| 	RegionSubSys         = madmin.RegionSubSys
 | |
| 	EtcdSubSys           = madmin.EtcdSubSys
 | |
| 	StorageClassSubSys   = madmin.StorageClassSubSys
 | |
| 	APISubSys            = madmin.APISubSys
 | |
| 	CompressionSubSys    = madmin.CompressionSubSys
 | |
| 	LoggerWebhookSubSys  = madmin.LoggerWebhookSubSys
 | |
| 	AuditWebhookSubSys   = madmin.AuditWebhookSubSys
 | |
| 	AuditKafkaSubSys     = madmin.AuditKafkaSubSys
 | |
| 	HealSubSys           = madmin.HealSubSys
 | |
| 	ScannerSubSys        = madmin.ScannerSubSys
 | |
| 	CrawlerSubSys        = madmin.CrawlerSubSys
 | |
| 	SubnetSubSys         = madmin.SubnetSubSys
 | |
| 	CallhomeSubSys       = madmin.CallhomeSubSys
 | |
| 	DriveSubSys          = madmin.DriveSubSys
 | |
| 	BatchSubSys          = madmin.BatchSubSys
 | |
| 	BrowserSubSys        = madmin.BrowserSubSys
 | |
| 	ILMSubSys            = madmin.ILMSubsys
 | |
| 
 | |
| 	// Add new constants here (similar to above) if you add new fields to config.
 | |
| )
 | |
| 
 | |
| // Notification config constants.
 | |
| const (
 | |
| 	NotifyKafkaSubSys    = madmin.NotifyKafkaSubSys
 | |
| 	NotifyMQTTSubSys     = madmin.NotifyMQTTSubSys
 | |
| 	NotifyMySQLSubSys    = madmin.NotifyMySQLSubSys
 | |
| 	NotifyNATSSubSys     = madmin.NotifyNATSSubSys
 | |
| 	NotifyNSQSubSys      = madmin.NotifyNSQSubSys
 | |
| 	NotifyESSubSys       = madmin.NotifyESSubSys
 | |
| 	NotifyAMQPSubSys     = madmin.NotifyAMQPSubSys
 | |
| 	NotifyPostgresSubSys = madmin.NotifyPostgresSubSys
 | |
| 	NotifyRedisSubSys    = madmin.NotifyRedisSubSys
 | |
| 	NotifyWebhookSubSys  = madmin.NotifyWebhookSubSys
 | |
| 
 | |
| 	// Add new constants here (similar to above) if you add new fields to config.
 | |
| )
 | |
| 
 | |
| // Lambda config constants.
 | |
| const (
 | |
| 	LambdaWebhookSubSys = madmin.LambdaWebhookSubSys
 | |
| )
 | |
| 
 | |
| // NotifySubSystems - all notification sub-systems
 | |
| var NotifySubSystems = set.CreateStringSet(
 | |
| 	NotifyKafkaSubSys,
 | |
| 	NotifyMQTTSubSys,
 | |
| 	NotifyMySQLSubSys,
 | |
| 	NotifyNATSSubSys,
 | |
| 	NotifyNSQSubSys,
 | |
| 	NotifyESSubSys,
 | |
| 	NotifyAMQPSubSys,
 | |
| 	NotifyPostgresSubSys,
 | |
| 	NotifyRedisSubSys,
 | |
| 	NotifyWebhookSubSys,
 | |
| )
 | |
| 
 | |
| // LambdaSubSystems - all lambda sub-systems
 | |
| var LambdaSubSystems = set.CreateStringSet(
 | |
| 	LambdaWebhookSubSys,
 | |
| )
 | |
| 
 | |
| // LoggerSubSystems - all sub-systems related to logger
 | |
| var LoggerSubSystems = set.CreateStringSet(
 | |
| 	LoggerWebhookSubSys,
 | |
| 	AuditWebhookSubSys,
 | |
| 	AuditKafkaSubSys,
 | |
| )
 | |
| 
 | |
| // SubSystems - all supported sub-systems
 | |
| var SubSystems = madmin.SubSystems
 | |
| 
 | |
| // SubSystemsDynamic - all sub-systems that have dynamic config.
 | |
| var SubSystemsDynamic = set.CreateStringSet(
 | |
| 	APISubSys,
 | |
| 	CompressionSubSys,
 | |
| 	ScannerSubSys,
 | |
| 	HealSubSys,
 | |
| 	SubnetSubSys,
 | |
| 	CallhomeSubSys,
 | |
| 	DriveSubSys,
 | |
| 	LoggerWebhookSubSys,
 | |
| 	AuditWebhookSubSys,
 | |
| 	AuditKafkaSubSys,
 | |
| 	StorageClassSubSys,
 | |
| 	CacheSubSys,
 | |
| 	ILMSubSys,
 | |
| 	BatchSubSys,
 | |
| 	BrowserSubSys,
 | |
| )
 | |
| 
 | |
| // SubSystemsSingleTargets - subsystems which only support single target.
 | |
| var SubSystemsSingleTargets = set.CreateStringSet(
 | |
| 	SiteSubSys,
 | |
| 	RegionSubSys,
 | |
| 	EtcdSubSys,
 | |
| 	CacheSubSys,
 | |
| 	APISubSys,
 | |
| 	StorageClassSubSys,
 | |
| 	CompressionSubSys,
 | |
| 	PolicyOPASubSys,
 | |
| 	PolicyPluginSubSys,
 | |
| 	IdentityLDAPSubSys,
 | |
| 	IdentityTLSSubSys,
 | |
| 	IdentityPluginSubSys,
 | |
| 	HealSubSys,
 | |
| 	ScannerSubSys,
 | |
| 	SubnetSubSys,
 | |
| 	CallhomeSubSys,
 | |
| 	DriveSubSys,
 | |
| 	ILMSubSys,
 | |
| 	BatchSubSys,
 | |
| 	BrowserSubSys,
 | |
| )
 | |
| 
 | |
| // Constant separators
 | |
| const (
 | |
| 	SubSystemSeparator = madmin.SubSystemSeparator
 | |
| 	KvSeparator        = madmin.KvSeparator
 | |
| 	KvSpaceSeparator   = madmin.KvSpaceSeparator
 | |
| 	KvComment          = madmin.KvComment
 | |
| 	KvNewline          = madmin.KvNewline
 | |
| 	KvDoubleQuote      = madmin.KvDoubleQuote
 | |
| 	KvSingleQuote      = madmin.KvSingleQuote
 | |
| 
 | |
| 	// Env prefix used for all envs in MinIO
 | |
| 	EnvPrefix        = madmin.EnvPrefix
 | |
| 	EnvWordDelimiter = madmin.EnvWordDelimiter
 | |
| )
 | |
| 
 | |
| // DefaultKVS - default kvs for all sub-systems
 | |
| var DefaultKVS = map[string]KVS{}
 | |
| 
 | |
| // RegisterDefaultKVS - this function saves input kvsMap
 | |
| // globally, this should be called only once preferably
 | |
| // during `init()`.
 | |
| func RegisterDefaultKVS(kvsMap map[string]KVS) {
 | |
| 	for subSys, kvs := range kvsMap {
 | |
| 		DefaultKVS[subSys] = kvs
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // HelpSubSysMap - help for all individual KVS for each sub-systems
 | |
| // also carries a special empty sub-system which dumps
 | |
| // help for each sub-system key.
 | |
| var HelpSubSysMap = map[string]HelpKVS{}
 | |
| 
 | |
| // RegisterHelpSubSys - this function saves
 | |
| // input help KVS for each sub-system globally,
 | |
| // this function should be called only once
 | |
| // preferably in during `init()`.
 | |
| func RegisterHelpSubSys(helpKVSMap map[string]HelpKVS) {
 | |
| 	for subSys, hkvs := range helpKVSMap {
 | |
| 		HelpSubSysMap[subSys] = hkvs
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // HelpDeprecatedSubSysMap - help for all deprecated sub-systems, that may be
 | |
| // removed in the future.
 | |
| var HelpDeprecatedSubSysMap = map[string]HelpKV{}
 | |
| 
 | |
| // RegisterHelpDeprecatedSubSys - saves input help KVS for deprecated
 | |
| // sub-systems globally. Should be called only once at init.
 | |
| func RegisterHelpDeprecatedSubSys(helpDeprecatedKVMap map[string]HelpKV) {
 | |
| 	for k, v := range helpDeprecatedKVMap {
 | |
| 		HelpDeprecatedSubSysMap[k] = v
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // KV - is a shorthand of each key value.
 | |
| type KV struct {
 | |
| 	Key   string `json:"key"`
 | |
| 	Value string `json:"value"`
 | |
| 
 | |
| 	HiddenIfEmpty bool `json:"-"`
 | |
| }
 | |
| 
 | |
| func (kv KV) String() string {
 | |
| 	var s strings.Builder
 | |
| 	s.WriteString(kv.Key)
 | |
| 	s.WriteString(KvSeparator)
 | |
| 	spc := madmin.HasSpace(kv.Value)
 | |
| 	if spc {
 | |
| 		s.WriteString(KvDoubleQuote)
 | |
| 	}
 | |
| 	s.WriteString(kv.Value)
 | |
| 	if spc {
 | |
| 		s.WriteString(KvDoubleQuote)
 | |
| 	}
 | |
| 	return s.String()
 | |
| }
 | |
| 
 | |
| // KVS - is a shorthand for some wrapper functions
 | |
| // to operate on list of key values.
 | |
| type KVS []KV
 | |
| 
 | |
| // Empty - return if kv is empty
 | |
| func (kvs KVS) Empty() bool {
 | |
| 	return len(kvs) == 0
 | |
| }
 | |
| 
 | |
| // Clone - returns a copy of the KVS
 | |
| func (kvs KVS) Clone() KVS {
 | |
| 	return append(make(KVS, 0, len(kvs)), kvs...)
 | |
| }
 | |
| 
 | |
| // GetWithDefault - returns default value if key not set
 | |
| func (kvs KVS) GetWithDefault(key string, defaultKVS KVS) string {
 | |
| 	v := kvs.Get(key)
 | |
| 	if len(v) == 0 {
 | |
| 		return defaultKVS.Get(key)
 | |
| 	}
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| // Keys returns the list of keys for the current KVS
 | |
| func (kvs KVS) Keys() []string {
 | |
| 	keys := make([]string, len(kvs))
 | |
| 	var foundComment bool
 | |
| 	for i := range kvs {
 | |
| 		if kvs[i].Key == madmin.CommentKey {
 | |
| 			foundComment = true
 | |
| 		}
 | |
| 		keys[i] = kvs[i].Key
 | |
| 	}
 | |
| 	// Comment KV not found, add it explicitly.
 | |
| 	if !foundComment {
 | |
| 		keys = append(keys, madmin.CommentKey)
 | |
| 	}
 | |
| 	return keys
 | |
| }
 | |
| 
 | |
| func (kvs KVS) String() string {
 | |
| 	var s strings.Builder
 | |
| 	for _, kv := range kvs {
 | |
| 		s.WriteString(kv.String())
 | |
| 		s.WriteString(KvSpaceSeparator)
 | |
| 	}
 | |
| 	return s.String()
 | |
| }
 | |
| 
 | |
| // Merge environment values with on disk KVS, environment values overrides
 | |
| // anything on the disk.
 | |
| func Merge(cfgKVS map[string]KVS, envname string, defaultKVS KVS) map[string]KVS {
 | |
| 	newCfgKVS := make(map[string]KVS)
 | |
| 	for _, e := range env.List(envname) {
 | |
| 		tgt := strings.TrimPrefix(e, envname+Default)
 | |
| 		if tgt == envname {
 | |
| 			tgt = Default
 | |
| 		}
 | |
| 		newCfgKVS[tgt] = defaultKVS
 | |
| 	}
 | |
| 	for tgt, kv := range cfgKVS {
 | |
| 		newCfgKVS[tgt] = kv
 | |
| 	}
 | |
| 	return newCfgKVS
 | |
| }
 | |
| 
 | |
| // Set sets a value, if not sets a default value.
 | |
| func (kvs *KVS) Set(key, value string) {
 | |
| 	for i, kv := range *kvs {
 | |
| 		if kv.Key == key {
 | |
| 			(*kvs)[i] = KV{
 | |
| 				Key:   key,
 | |
| 				Value: value,
 | |
| 			}
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	*kvs = append(*kvs, KV{
 | |
| 		Key:   key,
 | |
| 		Value: value,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Get - returns the value of a key, if not found returns empty.
 | |
| func (kvs KVS) Get(key string) string {
 | |
| 	v, ok := kvs.Lookup(key)
 | |
| 	if ok {
 | |
| 		return v
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // Delete - deletes the key if present from the KV list.
 | |
| func (kvs *KVS) Delete(key string) {
 | |
| 	for i, kv := range *kvs {
 | |
| 		if kv.Key == key {
 | |
| 			*kvs = append((*kvs)[:i], (*kvs)[i+1:]...)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // LookupKV returns the KV by its key
 | |
| func (kvs KVS) LookupKV(key string) (KV, bool) {
 | |
| 	for _, kv := range kvs {
 | |
| 		if kv.Key == key {
 | |
| 			return kv, true
 | |
| 		}
 | |
| 	}
 | |
| 	return KV{}, false
 | |
| }
 | |
| 
 | |
| // Lookup - lookup a key in a list of KVS
 | |
| func (kvs KVS) Lookup(key string) (string, bool) {
 | |
| 	for _, kv := range kvs {
 | |
| 		if kv.Key == key {
 | |
| 			return kv.Value, true
 | |
| 		}
 | |
| 	}
 | |
| 	return "", false
 | |
| }
 | |
| 
 | |
| // Config - MinIO server config structure.
 | |
| type Config map[string]map[string]KVS
 | |
| 
 | |
| // DelFrom - deletes all keys in the input reader.
 | |
| func (c Config) DelFrom(r io.Reader) error {
 | |
| 	scanner := bufio.NewScanner(r)
 | |
| 	for scanner.Scan() {
 | |
| 		// Skip any empty lines, or comment like characters
 | |
| 		text := scanner.Text()
 | |
| 		if text == "" || strings.HasPrefix(text, KvComment) {
 | |
| 			continue
 | |
| 		}
 | |
| 		if err := c.DelKVS(text); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return scanner.Err()
 | |
| }
 | |
| 
 | |
| // ContextKeyString is type(string) for contextKey
 | |
| type ContextKeyString string
 | |
| 
 | |
| // ContextKeyForTargetFromConfig - key for context for target from config
 | |
| const ContextKeyForTargetFromConfig = ContextKeyString("ContextKeyForTargetFromConfig")
 | |
| 
 | |
| // ParseConfigTargetID - read all targetIDs from reader
 | |
| func ParseConfigTargetID(r io.Reader) (ids map[string]bool, err error) {
 | |
| 	ids = make(map[string]bool)
 | |
| 	scanner := bufio.NewScanner(r)
 | |
| 	for scanner.Scan() {
 | |
| 		// Skip any empty lines, or comment like characters
 | |
| 		text := scanner.Text()
 | |
| 		if text == "" || strings.HasPrefix(text, KvComment) {
 | |
| 			continue
 | |
| 		}
 | |
| 		_, _, tgt, err := GetSubSys(text)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		ids[tgt] = true
 | |
| 	}
 | |
| 	if err := scanner.Err(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // ReadConfig - read content from input and write into c.
 | |
| // Returns whether all parameters were dynamic.
 | |
| func (c Config) ReadConfig(r io.Reader) (dynOnly bool, err error) {
 | |
| 	var n int
 | |
| 	scanner := bufio.NewScanner(r)
 | |
| 	dynOnly = true
 | |
| 	for scanner.Scan() {
 | |
| 		// Skip any empty lines, or comment like characters
 | |
| 		text := scanner.Text()
 | |
| 		if text == "" || strings.HasPrefix(text, KvComment) {
 | |
| 			continue
 | |
| 		}
 | |
| 		dynamic, err := c.SetKVS(text, DefaultKVS)
 | |
| 		if err != nil {
 | |
| 			return false, err
 | |
| 		}
 | |
| 		dynOnly = dynOnly && dynamic
 | |
| 		n += len(text)
 | |
| 	}
 | |
| 	if err := scanner.Err(); err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	return dynOnly, nil
 | |
| }
 | |
| 
 | |
| // RedactSensitiveInfo - removes sensitive information
 | |
| // like urls and credentials from the configuration
 | |
| func (c Config) RedactSensitiveInfo() Config {
 | |
| 	nc := c.Clone()
 | |
| 
 | |
| 	for configName, configVals := range nc {
 | |
| 		for _, helpKV := range HelpSubSysMap[configName] {
 | |
| 			if helpKV.Sensitive {
 | |
| 				for name, kvs := range configVals {
 | |
| 					for i := range kvs {
 | |
| 						if kvs[i].Key == helpKV.Key && len(kvs[i].Value) > 0 {
 | |
| 							kvs[i].Value = "*redacted*"
 | |
| 						}
 | |
| 					}
 | |
| 					configVals[name] = kvs
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nc
 | |
| }
 | |
| 
 | |
| // Default KV configs for worm and region
 | |
| var (
 | |
| 	DefaultCredentialKVS = KVS{
 | |
| 		KV{
 | |
| 			Key:   AccessKey,
 | |
| 			Value: auth.DefaultAccessKey,
 | |
| 		},
 | |
| 		KV{
 | |
| 			Key:   SecretKey,
 | |
| 			Value: auth.DefaultSecretKey,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	DefaultSiteKVS = KVS{
 | |
| 		KV{
 | |
| 			Key:   NameKey,
 | |
| 			Value: "",
 | |
| 		},
 | |
| 		KV{
 | |
| 			Key:   RegionKey,
 | |
| 			Value: "",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	DefaultRegionKVS = KVS{
 | |
| 		KV{
 | |
| 			Key:   RegionName,
 | |
| 			Value: "",
 | |
| 		},
 | |
| 	}
 | |
| )
 | |
| 
 | |
| var siteLK sync.RWMutex
 | |
| 
 | |
| // Site - holds site info - name and region.
 | |
| type Site struct {
 | |
| 	name   string
 | |
| 	region string
 | |
| }
 | |
| 
 | |
| // Update safe update the new site name and region
 | |
| func (s *Site) Update(n Site) {
 | |
| 	siteLK.Lock()
 | |
| 	s.name = n.name
 | |
| 	s.region = n.region
 | |
| 	siteLK.Unlock()
 | |
| }
 | |
| 
 | |
| // Name returns currently configured site name
 | |
| func (s *Site) Name() string {
 | |
| 	siteLK.RLock()
 | |
| 	defer siteLK.RUnlock()
 | |
| 
 | |
| 	return s.name
 | |
| }
 | |
| 
 | |
| // Region returns currently configured site region
 | |
| func (s *Site) Region() string {
 | |
| 	siteLK.RLock()
 | |
| 	defer siteLK.RUnlock()
 | |
| 
 | |
| 	return s.region
 | |
| }
 | |
| 
 | |
| var validRegionRegex = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-_-]+$")
 | |
| 
 | |
| // validSiteNameRegex - allows lowercase letters, digits and '-', starts with
 | |
| // letter. At least 2 characters long.
 | |
| var validSiteNameRegex = regexp.MustCompile("^[a-z][a-z0-9-]+$")
 | |
| 
 | |
| // LookupSite - get site related configuration. Loads configuration from legacy
 | |
| // region sub-system as well.
 | |
| func LookupSite(siteKV KVS, regionKV KVS) (s Site, err error) {
 | |
| 	if err = CheckValidKeys(SiteSubSys, siteKV, DefaultSiteKVS); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	region := env.Get(EnvRegion, "")
 | |
| 	if region == "" {
 | |
| 		env.Get(EnvRegionName, "")
 | |
| 	}
 | |
| 	if region == "" {
 | |
| 		region = env.Get(EnvSiteRegion, siteKV.Get(RegionKey))
 | |
| 	}
 | |
| 	if region == "" {
 | |
| 		// No region config found in the site-subsystem. So lookup the legacy
 | |
| 		// region sub-system.
 | |
| 		if err = CheckValidKeys(RegionSubSys, regionKV, DefaultRegionKVS); err != nil {
 | |
| 			// An invalid key was found in the region sub-system.
 | |
| 			// Since the region sub-system cannot be (re)set as it
 | |
| 			// is legacy, we return an error to tell the user to
 | |
| 			// reset the region via the new command.
 | |
| 			err = Errorf("could not load region from legacy configuration as it was invalid - use 'mc admin config set myminio site region=myregion name=myname' to set a region and name (%v)", err)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		region = regionKV.Get(RegionName)
 | |
| 	}
 | |
| 	if region != "" {
 | |
| 		if !validRegionRegex.MatchString(region) {
 | |
| 			err = Errorf(
 | |
| 				"region '%s' is invalid, expected simple characters such as [us-east-1, myregion...]",
 | |
| 				region)
 | |
| 			return
 | |
| 		}
 | |
| 		s.region = region
 | |
| 	}
 | |
| 
 | |
| 	name := env.Get(EnvSiteName, siteKV.Get(NameKey))
 | |
| 	if name != "" {
 | |
| 		if !validSiteNameRegex.MatchString(name) {
 | |
| 			err = Errorf(
 | |
| 				"site name '%s' is invalid, expected simple characters such as [cal-rack0, myname...]",
 | |
| 				name)
 | |
| 			return
 | |
| 		}
 | |
| 		s.name = name
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // CheckValidKeys - checks if inputs KVS has the necessary keys,
 | |
| // returns error if it find extra or superfluous keys.
 | |
| func CheckValidKeys(subSys string, kv KVS, validKVS KVS, deprecatedKeys ...string) error {
 | |
| 	nkv := KVS{}
 | |
| 	for _, kv := range kv {
 | |
| 		// Comment is a valid key, its also fully optional
 | |
| 		// ignore it since it is a valid key for all
 | |
| 		// sub-systems.
 | |
| 		if kv.Key == Comment {
 | |
| 			continue
 | |
| 		}
 | |
| 		var skip bool
 | |
| 		for _, deprecatedKey := range deprecatedKeys {
 | |
| 			if kv.Key == deprecatedKey {
 | |
| 				skip = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if skip {
 | |
| 			continue
 | |
| 		}
 | |
| 		if _, ok := validKVS.Lookup(kv.Key); !ok {
 | |
| 			nkv = append(nkv, kv)
 | |
| 		}
 | |
| 	}
 | |
| 	if len(nkv) > 0 {
 | |
| 		return Errorf(
 | |
| 			"found invalid keys (%s) for '%s' sub-system, use 'mc admin config reset myminio %s' to fix invalid keys", nkv.String(), subSys, subSys)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // LookupWorm - check if worm is enabled
 | |
| func LookupWorm() (bool, error) {
 | |
| 	return ParseBool(env.Get(EnvWorm, EnableOff))
 | |
| }
 | |
| 
 | |
| // Carries all the renamed sub-systems from their
 | |
| // previously known names
 | |
| var renamedSubsys = map[string]string{
 | |
| 	CrawlerSubSys: ScannerSubSys,
 | |
| 	// Add future sub-system renames
 | |
| }
 | |
| 
 | |
| const ( // deprecated keys
 | |
| 	apiReplicationWorkers       = "replication_workers"
 | |
| 	apiReplicationFailedWorkers = "replication_failed_workers"
 | |
| )
 | |
| 
 | |
| // map of subsystem to deleted keys
 | |
| var deletedSubSysKeys = map[string][]string{
 | |
| 	APISubSys: {apiReplicationWorkers, apiReplicationFailedWorkers},
 | |
| 	// Add future sub-system deleted keys
 | |
| }
 | |
| 
 | |
| // Merge - merges a new config with all the
 | |
| // missing values for default configs,
 | |
| // returns a config.
 | |
| func (c Config) Merge() Config {
 | |
| 	cp := New()
 | |
| 	for subSys, tgtKV := range c {
 | |
| 		for tgt := range tgtKV {
 | |
| 			ckvs := c[subSys][tgt]
 | |
| 			for _, kv := range cp[subSys][Default] {
 | |
| 				_, ok := c[subSys][tgt].Lookup(kv.Key)
 | |
| 				if !ok {
 | |
| 					ckvs.Set(kv.Key, kv.Value)
 | |
| 				}
 | |
| 			}
 | |
| 			if _, ok := cp[subSys]; !ok {
 | |
| 				rnSubSys, ok := renamedSubsys[subSys]
 | |
| 				if !ok {
 | |
| 					// A config subsystem was removed or server was downgraded.
 | |
| 					continue
 | |
| 				}
 | |
| 				// Copy over settings from previous sub-system
 | |
| 				// to newly renamed sub-system
 | |
| 				for _, kv := range cp[rnSubSys][Default] {
 | |
| 					_, ok := c[subSys][tgt].Lookup(kv.Key)
 | |
| 					if !ok {
 | |
| 						ckvs.Set(kv.Key, kv.Value)
 | |
| 					}
 | |
| 				}
 | |
| 				subSys = rnSubSys
 | |
| 			}
 | |
| 			// Delete deprecated keys for subsystem if any
 | |
| 			if keys, ok := deletedSubSysKeys[subSys]; ok {
 | |
| 				for _, key := range keys {
 | |
| 					ckvs.Delete(key)
 | |
| 				}
 | |
| 			}
 | |
| 			cp[subSys][tgt] = ckvs
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return cp
 | |
| }
 | |
| 
 | |
| // New - initialize a new server config.
 | |
| func New() Config {
 | |
| 	srvCfg := make(Config)
 | |
| 	for _, k := range SubSystems.ToSlice() {
 | |
| 		srvCfg[k] = map[string]KVS{}
 | |
| 		srvCfg[k][Default] = DefaultKVS[k]
 | |
| 	}
 | |
| 	return srvCfg
 | |
| }
 | |
| 
 | |
| // Target signifies an individual target
 | |
| type Target struct {
 | |
| 	SubSystem string
 | |
| 	KVS       KVS
 | |
| }
 | |
| 
 | |
| // Targets sub-system targets
 | |
| type Targets []Target
 | |
| 
 | |
| // GetKVS - get kvs from specific subsystem.
 | |
| func (c Config) GetKVS(s string, defaultKVS map[string]KVS) (Targets, error) {
 | |
| 	if len(s) == 0 {
 | |
| 		return nil, Errorf("input cannot be empty")
 | |
| 	}
 | |
| 	inputs := strings.Fields(s)
 | |
| 	if len(inputs) > 1 {
 | |
| 		return nil, Errorf("invalid number of arguments %s", s)
 | |
| 	}
 | |
| 	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
 | |
| 	if len(subSystemValue) == 0 {
 | |
| 		return nil, Errorf("invalid number of arguments %s", s)
 | |
| 	}
 | |
| 	found := SubSystems.Contains(subSystemValue[0])
 | |
| 	if !found {
 | |
| 		// Check for sub-prefix only if the input value is only a
 | |
| 		// single value, this rejects invalid inputs if any.
 | |
| 		found = !SubSystems.FuncMatch(strings.HasPrefix, subSystemValue[0]).IsEmpty() && len(subSystemValue) == 1
 | |
| 	}
 | |
| 	if !found {
 | |
| 		return nil, Errorf("unknown sub-system %s", s)
 | |
| 	}
 | |
| 
 | |
| 	targets := Targets{}
 | |
| 	subSysPrefix := subSystemValue[0]
 | |
| 	if len(subSystemValue) == 2 {
 | |
| 		if len(subSystemValue[1]) == 0 {
 | |
| 			return nil, Errorf("sub-system target '%s' cannot be empty", s)
 | |
| 		}
 | |
| 		kvs, ok := c[subSysPrefix][subSystemValue[1]]
 | |
| 		if !ok {
 | |
| 			return nil, Errorf("sub-system target '%s' doesn't exist", s)
 | |
| 		}
 | |
| 		for _, kv := range defaultKVS[subSysPrefix] {
 | |
| 			_, ok = kvs.Lookup(kv.Key)
 | |
| 			if !ok {
 | |
| 				kvs.Set(kv.Key, kv.Value)
 | |
| 			}
 | |
| 		}
 | |
| 		targets = append(targets, Target{
 | |
| 			SubSystem: inputs[0],
 | |
| 			KVS:       kvs,
 | |
| 		})
 | |
| 	} else {
 | |
| 		// Use help for sub-system to preserve the order. Add deprecated
 | |
| 		// keys at the end (in some order).
 | |
| 		kvsOrder := append([]HelpKV{}, HelpSubSysMap[""]...)
 | |
| 		for _, v := range HelpDeprecatedSubSysMap {
 | |
| 			kvsOrder = append(kvsOrder, v)
 | |
| 		}
 | |
| 
 | |
| 		for _, hkv := range kvsOrder {
 | |
| 			if !strings.HasPrefix(hkv.Key, subSysPrefix) {
 | |
| 				continue
 | |
| 			}
 | |
| 			if c[hkv.Key][Default].Empty() {
 | |
| 				targets = append(targets, Target{
 | |
| 					SubSystem: hkv.Key,
 | |
| 					KVS:       defaultKVS[hkv.Key],
 | |
| 				})
 | |
| 			}
 | |
| 			for k, kvs := range c[hkv.Key] {
 | |
| 				for _, dkv := range defaultKVS[hkv.Key] {
 | |
| 					_, ok := kvs.Lookup(dkv.Key)
 | |
| 					if !ok {
 | |
| 						kvs.Set(dkv.Key, dkv.Value)
 | |
| 					}
 | |
| 				}
 | |
| 				if k != Default {
 | |
| 					targets = append(targets, Target{
 | |
| 						SubSystem: hkv.Key + SubSystemSeparator + k,
 | |
| 						KVS:       kvs,
 | |
| 					})
 | |
| 				} else {
 | |
| 					targets = append(targets, Target{
 | |
| 						SubSystem: hkv.Key,
 | |
| 						KVS:       kvs,
 | |
| 					})
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return targets, nil
 | |
| }
 | |
| 
 | |
| // DelKVS - delete a specific key.
 | |
| func (c Config) DelKVS(s string) error {
 | |
| 	subSys, inputs, tgt, err := GetSubSys(s)
 | |
| 	if err != nil {
 | |
| 		if !SubSystems.Contains(subSys) && len(inputs) == 1 {
 | |
| 			// Unknown sub-system found try to remove it anyways.
 | |
| 			delete(c, subSys)
 | |
| 			return nil
 | |
| 		}
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ck, ok := c[subSys][tgt]
 | |
| 	if !ok {
 | |
| 		return Error[ErrConfigNotFound]("sub-system %s:%s already deleted or does not exist", subSys, tgt)
 | |
| 	}
 | |
| 
 | |
| 	if len(inputs) == 2 {
 | |
| 		currKVS := ck.Clone()
 | |
| 		defKVS := DefaultKVS[subSys]
 | |
| 		for _, delKey := range strings.Fields(inputs[1]) {
 | |
| 			_, ok := currKVS.Lookup(delKey)
 | |
| 			if !ok {
 | |
| 				return Error[ErrConfigNotFound]("key %s doesn't exist", delKey)
 | |
| 			}
 | |
| 			defVal, isDef := defKVS.Lookup(delKey)
 | |
| 			if isDef {
 | |
| 				currKVS.Set(delKey, defVal)
 | |
| 			} else {
 | |
| 				currKVS.Delete(delKey)
 | |
| 			}
 | |
| 		}
 | |
| 		c[subSys][tgt] = currKVS
 | |
| 	} else {
 | |
| 		delete(c[subSys], tgt)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Clone - clones a config map entirely.
 | |
| func (c Config) Clone() Config {
 | |
| 	cp := New()
 | |
| 	for subSys, tgtKV := range c {
 | |
| 		cp[subSys] = make(map[string]KVS)
 | |
| 		for tgt, kv := range tgtKV {
 | |
| 			cp[subSys][tgt] = append(cp[subSys][tgt], kv...)
 | |
| 		}
 | |
| 	}
 | |
| 	return cp
 | |
| }
 | |
| 
 | |
| // GetSubSys - extracts subssystem info from given config string
 | |
| func GetSubSys(s string) (subSys string, inputs []string, tgt string, e error) {
 | |
| 	tgt = Default
 | |
| 	if len(s) == 0 {
 | |
| 		return subSys, inputs, tgt, Errorf("input arguments cannot be empty")
 | |
| 	}
 | |
| 	inputs = strings.SplitN(s, KvSpaceSeparator, 2)
 | |
| 
 | |
| 	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
 | |
| 	subSys = subSystemValue[0]
 | |
| 	if !SubSystems.Contains(subSys) {
 | |
| 		return subSys, inputs, tgt, Errorf("unknown sub-system %s", s)
 | |
| 	}
 | |
| 
 | |
| 	if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
 | |
| 		return subSys, inputs, tgt, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
 | |
| 	}
 | |
| 
 | |
| 	if len(subSystemValue) == 2 {
 | |
| 		tgt = subSystemValue[1]
 | |
| 	}
 | |
| 
 | |
| 	return subSys, inputs, tgt, e
 | |
| }
 | |
| 
 | |
| // kvFields - converts an input string of form "k1=v1 k2=v2" into
 | |
| // fields of ["k1=v1", "k2=v2"], the tokenization of each `k=v`
 | |
| // happens with the right number of input keys, if keys
 | |
| // input is empty returned value is empty slice as well.
 | |
| func kvFields(input string, keys []string) []string {
 | |
| 	valueIndexes := make([]int, 0, len(keys))
 | |
| 	for _, key := range keys {
 | |
| 		i := strings.Index(input, key+KvSeparator)
 | |
| 		if i == -1 {
 | |
| 			continue
 | |
| 		}
 | |
| 		valueIndexes = append(valueIndexes, i)
 | |
| 	}
 | |
| 
 | |
| 	sort.Ints(valueIndexes)
 | |
| 	fields := make([]string, len(valueIndexes))
 | |
| 	for i := range valueIndexes {
 | |
| 		j := i + 1
 | |
| 		if j < len(valueIndexes) {
 | |
| 			fields[i] = strings.TrimSpace(input[valueIndexes[i]:valueIndexes[j]])
 | |
| 		} else {
 | |
| 			fields[i] = strings.TrimSpace(input[valueIndexes[i]:])
 | |
| 		}
 | |
| 	}
 | |
| 	return fields
 | |
| }
 | |
| 
 | |
| // SetKVS - set specific key values per sub-system.
 | |
| func (c Config) SetKVS(s string, defaultKVS map[string]KVS) (dynamic bool, err error) {
 | |
| 	subSys, inputs, tgt, err := GetSubSys(s)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	dynamic = SubSystemsDynamic.Contains(subSys)
 | |
| 
 | |
| 	fields := kvFields(inputs[1], defaultKVS[subSys].Keys())
 | |
| 	if len(fields) == 0 {
 | |
| 		return false, Errorf("sub-system '%s' cannot have empty keys", subSys)
 | |
| 	}
 | |
| 
 | |
| 	kvs := KVS{}
 | |
| 	var prevK string
 | |
| 	for _, v := range fields {
 | |
| 		kv := strings.SplitN(v, KvSeparator, 2)
 | |
| 		if len(kv) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if len(kv) == 1 && prevK != "" {
 | |
| 			value := strings.Join([]string{
 | |
| 				kvs.Get(prevK),
 | |
| 				madmin.SanitizeValue(kv[0]),
 | |
| 			}, KvSpaceSeparator)
 | |
| 			kvs.Set(prevK, value)
 | |
| 			continue
 | |
| 		}
 | |
| 		if len(kv) == 2 {
 | |
| 			prevK = kv[0]
 | |
| 			kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
 | |
| 			continue
 | |
| 		}
 | |
| 		return false, Errorf("key '%s', cannot have empty value", kv[0])
 | |
| 	}
 | |
| 
 | |
| 	_, ok := kvs.Lookup(Enable)
 | |
| 	// Check if state is required
 | |
| 	_, enableRequired := defaultKVS[subSys].Lookup(Enable)
 | |
| 	if !ok && enableRequired {
 | |
| 		// implicit state "on" if not specified.
 | |
| 		kvs.Set(Enable, EnableOn)
 | |
| 	}
 | |
| 
 | |
| 	var currKVS KVS
 | |
| 	ck, ok := c[subSys][tgt]
 | |
| 	if !ok {
 | |
| 		currKVS = defaultKVS[subSys].Clone()
 | |
| 	} else {
 | |
| 		currKVS = ck.Clone()
 | |
| 		for _, kv := range defaultKVS[subSys] {
 | |
| 			if _, ok = currKVS.Lookup(kv.Key); !ok {
 | |
| 				currKVS.Set(kv.Key, kv.Value)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, kv := range kvs {
 | |
| 		if kv.Key == Comment {
 | |
| 			// Skip comment and add it later.
 | |
| 			continue
 | |
| 		}
 | |
| 		currKVS.Set(kv.Key, kv.Value)
 | |
| 	}
 | |
| 
 | |
| 	v, ok := kvs.Lookup(Comment)
 | |
| 	if ok {
 | |
| 		currKVS.Set(Comment, v)
 | |
| 	}
 | |
| 
 | |
| 	hkvs := HelpSubSysMap[subSys]
 | |
| 	for _, hkv := range hkvs {
 | |
| 		var enabled bool
 | |
| 		if enableRequired {
 | |
| 			enabled = currKVS.Get(Enable) == EnableOn
 | |
| 		} else {
 | |
| 			// when enable arg is not required
 | |
| 			// then it is implicit on for the sub-system.
 | |
| 			enabled = true
 | |
| 		}
 | |
| 		v, _ := currKVS.Lookup(hkv.Key)
 | |
| 		if v == "" && !hkv.Optional && enabled {
 | |
| 			// Return error only if the
 | |
| 			// key is enabled, for state=off
 | |
| 			// let it be empty.
 | |
| 			return false, Errorf(
 | |
| 				"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
 | |
| 				hkv.Key, subSys, subSys)
 | |
| 		}
 | |
| 	}
 | |
| 	c[subSys][tgt] = currKVS
 | |
| 	return dynamic, nil
 | |
| }
 | |
| 
 | |
| // CheckValidKeys - checks if the config parameters for the given subsystem and
 | |
| // target are valid. It checks both the configuration store as well as
 | |
| // environment variables.
 | |
| func (c Config) CheckValidKeys(subSys string, deprecatedKeys []string) error {
 | |
| 	defKVS, ok := DefaultKVS[subSys]
 | |
| 	if !ok {
 | |
| 		return Errorf("Subsystem %s does not exist", subSys)
 | |
| 	}
 | |
| 
 | |
| 	// Make a list of valid keys for the subsystem including the `comment`
 | |
| 	// key.
 | |
| 	validKeys := make([]string, 0, len(defKVS)+1)
 | |
| 	for _, param := range defKVS {
 | |
| 		validKeys = append(validKeys, param.Key)
 | |
| 	}
 | |
| 	validKeys = append(validKeys, Comment)
 | |
| 
 | |
| 	subSysEnvVars := env.List(fmt.Sprintf("%s%s", EnvPrefix, strings.ToUpper(subSys)))
 | |
| 
 | |
| 	// Set of env vars for the sub-system to validate.
 | |
| 	candidates := set.CreateStringSet(subSysEnvVars...)
 | |
| 
 | |
| 	// Remove all default target env vars from the candidates set (as they
 | |
| 	// are valid).
 | |
| 	for _, param := range validKeys {
 | |
| 		paramEnvName := getEnvVarName(subSys, Default, param)
 | |
| 		candidates.Remove(paramEnvName)
 | |
| 	}
 | |
| 
 | |
| 	isSingleTarget := SubSystemsSingleTargets.Contains(subSys)
 | |
| 	if isSingleTarget && len(candidates) > 0 {
 | |
| 		return Errorf("The following environment variables are unknown: %s",
 | |
| 			strings.Join(candidates.ToSlice(), ", "))
 | |
| 	}
 | |
| 
 | |
| 	if !isSingleTarget {
 | |
| 		// Validate other env vars for all targets.
 | |
| 		envVars := candidates.ToSlice()
 | |
| 		for _, envVar := range envVars {
 | |
| 			for _, param := range validKeys {
 | |
| 				pEnvName := getEnvVarName(subSys, Default, param) + Default
 | |
| 				if len(envVar) > len(pEnvName) && strings.HasPrefix(envVar, pEnvName) {
 | |
| 					// This envVar is valid - it has a
 | |
| 					// non-empty target.
 | |
| 					candidates.Remove(envVar)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Whatever remains are invalid env vars - return an error.
 | |
| 		if len(candidates) > 0 {
 | |
| 			return Errorf("The following environment variables are unknown: %s",
 | |
| 				strings.Join(candidates.ToSlice(), ", "))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	validKeysSet := set.CreateStringSet(validKeys...)
 | |
| 	validKeysSet = validKeysSet.Difference(set.CreateStringSet(deprecatedKeys...))
 | |
| 	kvsMap := c[subSys]
 | |
| 	for tgt, kvs := range kvsMap {
 | |
| 		invalidKV := KVS{}
 | |
| 		for _, kv := range kvs {
 | |
| 			if !validKeysSet.Contains(kv.Key) {
 | |
| 				invalidKV = append(invalidKV, kv)
 | |
| 			}
 | |
| 		}
 | |
| 		if len(invalidKV) > 0 {
 | |
| 			return Errorf(
 | |
| 				"found invalid keys (%s) for '%s:%s' sub-system, use 'mc admin config reset myminio %s:%s' to fix invalid keys",
 | |
| 				invalidKV.String(), subSys, tgt, subSys, tgt)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // GetAvailableTargets - returns a list of targets configured for the given
 | |
| // subsystem (whether they are enabled or not). A target could be configured via
 | |
| // environment variables or via the configuration store. The default target is
 | |
| // `_` and is always returned. The result is sorted so that the default target
 | |
| // is the first one and the remaining entries are sorted in ascending order.
 | |
| func (c Config) GetAvailableTargets(subSys string) ([]string, error) {
 | |
| 	if SubSystemsSingleTargets.Contains(subSys) {
 | |
| 		return []string{Default}, nil
 | |
| 	}
 | |
| 
 | |
| 	defKVS, ok := DefaultKVS[subSys]
 | |
| 	if !ok {
 | |
| 		return nil, Errorf("Subsystem %s does not exist", subSys)
 | |
| 	}
 | |
| 
 | |
| 	kvsMap := c[subSys]
 | |
| 	seen := set.NewStringSet()
 | |
| 
 | |
| 	// Add all targets that are configured in the config store.
 | |
| 	for k := range kvsMap {
 | |
| 		seen.Add(k)
 | |
| 	}
 | |
| 
 | |
| 	// env:prefix
 | |
| 	filterMap := map[string]string{}
 | |
| 	// Add targets that are configured via environment variables.
 | |
| 	for _, param := range defKVS {
 | |
| 		envVarPrefix := getEnvVarName(subSys, Default, param.Key) + Default
 | |
| 		envsWithPrefix := env.List(envVarPrefix)
 | |
| 		for _, k := range envsWithPrefix {
 | |
| 			tgtName := strings.TrimPrefix(k, envVarPrefix)
 | |
| 			if tgtName != "" {
 | |
| 				if v, ok := filterMap[k]; ok {
 | |
| 					if strings.HasPrefix(envVarPrefix, v) {
 | |
| 						filterMap[k] = envVarPrefix
 | |
| 					}
 | |
| 				} else {
 | |
| 					filterMap[k] = envVarPrefix
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for k, v := range filterMap {
 | |
| 		seen.Add(strings.TrimPrefix(k, v))
 | |
| 	}
 | |
| 
 | |
| 	seen.Remove(Default)
 | |
| 	targets := seen.ToSlice()
 | |
| 	sort.Strings(targets)
 | |
| 	targets = append([]string{Default}, targets...)
 | |
| 
 | |
| 	return targets, nil
 | |
| }
 | |
| 
 | |
| func getEnvVarName(subSys, target, param string) string {
 | |
| 	if target == Default {
 | |
| 		return fmt.Sprintf("%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param))
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Sprintf("%s%s%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param),
 | |
| 		Default, target)
 | |
| }
 | |
| 
 | |
| var resolvableSubsystems = set.CreateStringSet(IdentityOpenIDSubSys, IdentityLDAPSubSys, PolicyPluginSubSys)
 | |
| 
 | |
| // ValueSource represents the source of a config parameter value.
 | |
| type ValueSource uint8
 | |
| 
 | |
| // Constants for ValueSource
 | |
| const (
 | |
| 	ValueSourceAbsent ValueSource = iota // this is an error case
 | |
| 	ValueSourceDef
 | |
| 	ValueSourceCfg
 | |
| 	ValueSourceEnv
 | |
| )
 | |
| 
 | |
| // ResolveConfigParam returns the effective value of a configuration parameter,
 | |
| // within a subsystem and subsystem target. The effective value is, in order of
 | |
| // decreasing precedence:
 | |
| //
 | |
| // 1. the value of the corresponding environment variable if set,
 | |
| // 2. the value of the parameter in the config store if set,
 | |
| // 3. the default value,
 | |
| //
 | |
| // This function only works for a subset of sub-systems, others return
 | |
| // `ValueSourceAbsent`. FIXME: some parameters have custom environment
 | |
| // variables for which support needs to be added.
 | |
| //
 | |
| // When redactSecrets is true, the returned value is empty if the configuration
 | |
| // parameter is a secret, and the returned isRedacted flag is set.
 | |
| func (c Config) ResolveConfigParam(subSys, target, cfgParam string, redactSecrets bool,
 | |
| ) (value string, cs ValueSource, isRedacted bool) {
 | |
| 	// cs = ValueSourceAbsent initially as it is iota by default.
 | |
| 
 | |
| 	// Initially only support OpenID
 | |
| 	if !resolvableSubsystems.Contains(subSys) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Check if config param requested is valid.
 | |
| 	defKVS, ok := DefaultKVS[subSys]
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	defValue, isFound := defKVS.Lookup(cfgParam)
 | |
| 	// Comments usually are absent from `defKVS`, so we handle it specially.
 | |
| 	if !isFound && cfgParam == Comment {
 | |
| 		defValue, isFound = "", true
 | |
| 	}
 | |
| 	if !isFound {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if target == "" {
 | |
| 		target = Default
 | |
| 	}
 | |
| 
 | |
| 	if redactSecrets {
 | |
| 		// If the configuration parameter is a secret, make sure to redact it when
 | |
| 		// we return.
 | |
| 		helpKV, _ := HelpSubSysMap[subSys].Lookup(cfgParam)
 | |
| 		if helpKV.Secret {
 | |
| 			defer func() {
 | |
| 				value = ""
 | |
| 				isRedacted = true
 | |
| 			}()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	envVar := getEnvVarName(subSys, target, cfgParam)
 | |
| 
 | |
| 	// Lookup Env var.
 | |
| 	value = env.Get(envVar, "")
 | |
| 	if value != "" {
 | |
| 		cs = ValueSourceEnv
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Lookup config store.
 | |
| 	if subSysStore, ok := c[subSys]; ok {
 | |
| 		if kvs, ok2 := subSysStore[target]; ok2 {
 | |
| 			var ok3 bool
 | |
| 			value, ok3 = kvs.Lookup(cfgParam)
 | |
| 			if ok3 {
 | |
| 				cs = ValueSourceCfg
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Return the default value.
 | |
| 	value = defValue
 | |
| 	cs = ValueSourceDef
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // KVSrc represents a configuration parameter key and value along with the
 | |
| // source of the value.
 | |
| type KVSrc struct {
 | |
| 	Key   string
 | |
| 	Value string
 | |
| 	Src   ValueSource
 | |
| }
 | |
| 
 | |
| // GetResolvedConfigParams returns all applicable config parameters with their
 | |
| // value sources.
 | |
| func (c Config) GetResolvedConfigParams(subSys, target string, redactSecrets bool) ([]KVSrc, error) {
 | |
| 	if !resolvableSubsystems.Contains(subSys) {
 | |
| 		return nil, Errorf("unsupported subsystem: %s", subSys)
 | |
| 	}
 | |
| 
 | |
| 	// Check if config param requested is valid.
 | |
| 	defKVS, ok := DefaultKVS[subSys]
 | |
| 	if !ok {
 | |
| 		return nil, Errorf("unknown subsystem: %s", subSys)
 | |
| 	}
 | |
| 
 | |
| 	r := make([]KVSrc, 0, len(defKVS)+1)
 | |
| 	for _, kv := range defKVS {
 | |
| 		v, vs, isRedacted := c.ResolveConfigParam(subSys, target, kv.Key, redactSecrets)
 | |
| 
 | |
| 		// Fix `vs` when default.
 | |
| 		if v == kv.Value {
 | |
| 			vs = ValueSourceDef
 | |
| 		}
 | |
| 
 | |
| 		if redactSecrets && isRedacted {
 | |
| 			// Skip adding redacted secrets to the output.
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		r = append(r, KVSrc{
 | |
| 			Key:   kv.Key,
 | |
| 			Value: v,
 | |
| 			Src:   vs,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// Add the comment key as well if non-empty (and comments are never
 | |
| 	// redacted).
 | |
| 	v, vs, _ := c.ResolveConfigParam(subSys, target, Comment, redactSecrets)
 | |
| 	if vs != ValueSourceDef {
 | |
| 		r = append(r, KVSrc{
 | |
| 			Key:   Comment,
 | |
| 			Value: v,
 | |
| 			Src:   vs,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| // getTargetKVS returns configuration KVs for the given subsystem and target. It
 | |
| // does not return any secrets in the configuration values when `redactSecrets`
 | |
| // is set.
 | |
| func (c Config) getTargetKVS(subSys, target string, redactSecrets bool) KVS {
 | |
| 	store, ok := c[subSys]
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Lookup will succeed, because this function only works with valid subSys
 | |
| 	// values.
 | |
| 	resultKVS := make([]KV, 0, len(store[target]))
 | |
| 	hkvs := HelpSubSysMap[subSys]
 | |
| 	for _, kv := range store[target] {
 | |
| 		hkv, _ := hkvs.Lookup(kv.Key)
 | |
| 		if hkv.Secret && redactSecrets && kv.Value != "" {
 | |
| 			// Skip returning secrets.
 | |
| 			continue
 | |
| 			// clonedKV := kv
 | |
| 			// clonedKV.Value = redactedSecret
 | |
| 			// resultKVS = append(resultKVS, clonedKV)
 | |
| 		}
 | |
| 		resultKVS = append(resultKVS, kv)
 | |
| 	}
 | |
| 
 | |
| 	return resultKVS
 | |
| }
 | |
| 
 | |
| // getTargetEnvs returns configured environment variable settings for the given
 | |
| // subsystem and target.
 | |
| func (c Config) getTargetEnvs(subSys, target string, defKVS KVS, redactSecrets bool) map[string]EnvPair {
 | |
| 	hkvs := HelpSubSysMap[subSys]
 | |
| 	envMap := make(map[string]EnvPair)
 | |
| 
 | |
| 	// Add all env vars that are set.
 | |
| 	for _, kv := range defKVS {
 | |
| 		envName := getEnvVarName(subSys, target, kv.Key)
 | |
| 		envPair := EnvPair{
 | |
| 			Name:  envName,
 | |
| 			Value: env.Get(envName, ""),
 | |
| 		}
 | |
| 		if envPair.Value != "" {
 | |
| 			hkv, _ := hkvs.Lookup(kv.Key)
 | |
| 			if hkv.Secret && redactSecrets {
 | |
| 				// Skip adding any secret to the returned value.
 | |
| 				continue
 | |
| 				// envPair.Value = redactedSecret
 | |
| 			}
 | |
| 			envMap[kv.Key] = envPair
 | |
| 		}
 | |
| 	}
 | |
| 	return envMap
 | |
| }
 | |
| 
 | |
| // EnvPair represents an environment variable and its value.
 | |
| type EnvPair struct {
 | |
| 	Name, Value string
 | |
| }
 | |
| 
 | |
| // SubsysInfo holds config info for a subsystem target.
 | |
| type SubsysInfo struct {
 | |
| 	SubSys, Target string
 | |
| 	Defaults       KVS
 | |
| 	Config         KVS
 | |
| 
 | |
| 	// map of config parameter name to EnvPair.
 | |
| 	EnvMap map[string]EnvPair
 | |
| }
 | |
| 
 | |
| // GetSubsysInfo returns `SubsysInfo`s for all targets for the subsystem, when
 | |
| // target is empty. Otherwise returns `SubsysInfo` for the desired target only.
 | |
| // To request the default target only, target must be set to `Default`.
 | |
| func (c Config) GetSubsysInfo(subSys, target string, redactSecrets bool) ([]SubsysInfo, error) {
 | |
| 	// Check if config param requested is valid.
 | |
| 	defKVS1, ok := DefaultKVS[subSys]
 | |
| 	if !ok {
 | |
| 		return nil, Errorf("unknown subsystem: %s", subSys)
 | |
| 	}
 | |
| 
 | |
| 	targets, err := c.GetAvailableTargets(subSys)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if target != "" {
 | |
| 		found := false
 | |
| 		for _, t := range targets {
 | |
| 			if t == target {
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			return nil, Errorf("there is no target `%s` for subsystem `%s`", target, subSys)
 | |
| 		}
 | |
| 		targets = []string{target}
 | |
| 	}
 | |
| 
 | |
| 	// The `Comment` configuration variable is optional but is available to be
 | |
| 	// set for all sub-systems. It is not present in the `DefaultKVS` map's
 | |
| 	// values. To enable fetching a configured comment value from the
 | |
| 	// environment we add it to the list of default keys for the subsystem.
 | |
| 	defKVS := make([]KV, len(defKVS1), len(defKVS1)+1)
 | |
| 	copy(defKVS, defKVS1)
 | |
| 	defKVS = append(defKVS, KV{Key: Comment})
 | |
| 
 | |
| 	r := make([]SubsysInfo, 0, len(targets))
 | |
| 	for _, target := range targets {
 | |
| 		r = append(r, SubsysInfo{
 | |
| 			SubSys:   subSys,
 | |
| 			Target:   target,
 | |
| 			Defaults: defKVS,
 | |
| 			Config:   c.getTargetKVS(subSys, target, redactSecrets),
 | |
| 			EnvMap:   c.getTargetEnvs(subSys, target, defKVS, redactSecrets),
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| // AddEnvString adds env vars to the given string builder.
 | |
| func (cs *SubsysInfo) AddEnvString(b *strings.Builder) {
 | |
| 	for _, v := range cs.Defaults {
 | |
| 		if ep, ok := cs.EnvMap[v.Key]; ok {
 | |
| 			b.WriteString(KvComment)
 | |
| 			b.WriteString(KvSpaceSeparator)
 | |
| 			b.WriteString(ep.Name)
 | |
| 			b.WriteString(EnvSeparator)
 | |
| 			b.WriteString(ep.Value)
 | |
| 			b.WriteString(KvNewline)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WriteTo writes the string representation of the configuration to the given
 | |
| // builder. When off is true, adds a comment character before the config system
 | |
| // output. It also ignores values when empty and deprecated.
 | |
| func (cs *SubsysInfo) WriteTo(b *strings.Builder, off bool) {
 | |
| 	cs.AddEnvString(b)
 | |
| 	if off {
 | |
| 		b.WriteString(KvComment)
 | |
| 		b.WriteString(KvSpaceSeparator)
 | |
| 	}
 | |
| 	b.WriteString(cs.SubSys)
 | |
| 	if cs.Target != Default {
 | |
| 		b.WriteString(SubSystemSeparator)
 | |
| 		b.WriteString(cs.Target)
 | |
| 	}
 | |
| 	b.WriteString(KvSpaceSeparator)
 | |
| 	for _, kv := range cs.Config {
 | |
| 		dkv, ok := cs.Defaults.LookupKV(kv.Key)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		// Ignore empty and deprecated values
 | |
| 		if dkv.HiddenIfEmpty && kv.Value == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		// Do not need to print if state is on
 | |
| 		if kv.Key == Enable && kv.Value == EnableOn {
 | |
| 			continue
 | |
| 		}
 | |
| 		b.WriteString(kv.String())
 | |
| 		b.WriteString(KvSpaceSeparator)
 | |
| 	}
 | |
| 
 | |
| 	b.WriteString(KvNewline)
 | |
| }
 |