mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			536 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			536 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package server
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/hashicorp/go-multierror"
 | |
| 	"github.com/hashicorp/hcl"
 | |
| 	"github.com/hashicorp/hcl/hcl/ast"
 | |
| )
 | |
| 
 | |
| // ReloadFunc are functions that are called when a reload is requested.
 | |
| type ReloadFunc func(map[string]string) error
 | |
| 
 | |
| // Config is the configuration for the vault server.
 | |
| type Config struct {
 | |
| 	Listeners []*Listener `hcl:"-"`
 | |
| 	Backend   *Backend    `hcl:"-"`
 | |
| 	HABackend *Backend    `hcl:"-"`
 | |
| 
 | |
| 	DisableCache bool `hcl:"disable_cache"`
 | |
| 	DisableMlock bool `hcl:"disable_mlock"`
 | |
| 
 | |
| 	Telemetry *Telemetry `hcl:"telemetry"`
 | |
| 
 | |
| 	MaxLeaseTTL        time.Duration `hcl:"-"`
 | |
| 	MaxLeaseTTLRaw     string        `hcl:"max_lease_ttl"`
 | |
| 	DefaultLeaseTTL    time.Duration `hcl:"-"`
 | |
| 	DefaultLeaseTTLRaw string        `hcl:"default_lease_ttl"`
 | |
| }
 | |
| 
 | |
| // DevConfig is a Config that is used for dev mode of Vault.
 | |
| func DevConfig() *Config {
 | |
| 	return &Config{
 | |
| 		DisableCache: false,
 | |
| 		DisableMlock: true,
 | |
| 
 | |
| 		Backend: &Backend{
 | |
| 			Type: "inmem",
 | |
| 		},
 | |
| 
 | |
| 		Listeners: []*Listener{
 | |
| 			&Listener{
 | |
| 				Type: "tcp",
 | |
| 				Config: map[string]string{
 | |
| 					"address":     "127.0.0.1:8200",
 | |
| 					"tls_disable": "1",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 
 | |
| 		Telemetry: &Telemetry{},
 | |
| 
 | |
| 		MaxLeaseTTL:     30 * 24 * time.Hour,
 | |
| 		DefaultLeaseTTL: 30 * 24 * time.Hour,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Listener is the listener configuration for the server.
 | |
| type Listener struct {
 | |
| 	Type   string
 | |
| 	Config map[string]string
 | |
| }
 | |
| 
 | |
| func (l *Listener) GoString() string {
 | |
| 	return fmt.Sprintf("*%#v", *l)
 | |
| }
 | |
| 
 | |
| // Backend is the backend configuration for the server.
 | |
| type Backend struct {
 | |
| 	Type          string
 | |
| 	AdvertiseAddr string
 | |
| 	Config        map[string]string
 | |
| }
 | |
| 
 | |
| func (b *Backend) GoString() string {
 | |
| 	return fmt.Sprintf("*%#v", *b)
 | |
| }
 | |
| 
 | |
| // Telemetry is the telemetry configuration for the server
 | |
| type Telemetry struct {
 | |
| 	StatsiteAddr string `hcl:"statsite_address"`
 | |
| 	StatsdAddr   string `hcl:"statsd_address"`
 | |
| 
 | |
| 	DisableHostname bool `hcl:"disable_hostname"`
 | |
| }
 | |
| 
 | |
| func (s *Telemetry) GoString() string {
 | |
| 	return fmt.Sprintf("*%#v", *s)
 | |
| }
 | |
| 
 | |
| // Merge merges two configurations.
 | |
| func (c *Config) Merge(c2 *Config) *Config {
 | |
| 	result := new(Config)
 | |
| 	for _, l := range c.Listeners {
 | |
| 		result.Listeners = append(result.Listeners, l)
 | |
| 	}
 | |
| 	for _, l := range c2.Listeners {
 | |
| 		result.Listeners = append(result.Listeners, l)
 | |
| 	}
 | |
| 
 | |
| 	result.Backend = c.Backend
 | |
| 	if c2.Backend != nil {
 | |
| 		result.Backend = c2.Backend
 | |
| 	}
 | |
| 
 | |
| 	result.HABackend = c.HABackend
 | |
| 	if c2.HABackend != nil {
 | |
| 		result.HABackend = c2.HABackend
 | |
| 	}
 | |
| 
 | |
| 	result.Telemetry = c.Telemetry
 | |
| 	if c2.Telemetry != nil {
 | |
| 		result.Telemetry = c2.Telemetry
 | |
| 	}
 | |
| 
 | |
| 	// merging these booleans via an OR operation
 | |
| 	result.DisableCache = c.DisableCache
 | |
| 	if c2.DisableCache {
 | |
| 		result.DisableCache = c2.DisableCache
 | |
| 	}
 | |
| 
 | |
| 	result.DisableMlock = c.DisableMlock
 | |
| 	if c2.DisableMlock {
 | |
| 		result.DisableMlock = c2.DisableMlock
 | |
| 	}
 | |
| 
 | |
| 	// merge these integers via a MAX operation
 | |
| 	result.MaxLeaseTTL = c.MaxLeaseTTL
 | |
| 	if c2.MaxLeaseTTL > result.MaxLeaseTTL {
 | |
| 		result.MaxLeaseTTL = c2.MaxLeaseTTL
 | |
| 	}
 | |
| 
 | |
| 	result.DefaultLeaseTTL = c.DefaultLeaseTTL
 | |
| 	if c2.DefaultLeaseTTL > result.DefaultLeaseTTL {
 | |
| 		result.DefaultLeaseTTL = c2.DefaultLeaseTTL
 | |
| 	}
 | |
| 
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // LoadConfig loads the configuration at the given path, regardless if
 | |
| // its a file or directory.
 | |
| func LoadConfig(path string) (*Config, error) {
 | |
| 	fi, err := os.Stat(path)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if fi.IsDir() {
 | |
| 		return LoadConfigDir(path)
 | |
| 	} else {
 | |
| 		return LoadConfigFile(path)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // LoadConfigFile loads the configuration from the given file.
 | |
| func LoadConfigFile(path string) (*Config, error) {
 | |
| 	// Read the file
 | |
| 	d, err := ioutil.ReadFile(path)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return ParseConfig(string(d))
 | |
| }
 | |
| 
 | |
| func ParseConfig(d string) (*Config, error) {
 | |
| 	// Parse!
 | |
| 	obj, err := hcl.Parse(d)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Start building the result
 | |
| 	var result Config
 | |
| 	if err := hcl.DecodeObject(&result, obj); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if result.MaxLeaseTTLRaw != "" {
 | |
| 		if result.MaxLeaseTTL, err = time.ParseDuration(result.MaxLeaseTTLRaw); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	if result.DefaultLeaseTTLRaw != "" {
 | |
| 		if result.DefaultLeaseTTL, err = time.ParseDuration(result.DefaultLeaseTTLRaw); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	list, ok := obj.Node.(*ast.ObjectList)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
 | |
| 	}
 | |
| 
 | |
| 	valid := []string{
 | |
| 		"atlas",
 | |
| 		"backend",
 | |
| 		"ha_backend",
 | |
| 		"listener",
 | |
| 		"disable_cache",
 | |
| 		"disable_mlock",
 | |
| 		"telemetry",
 | |
| 		"default_lease_ttl",
 | |
| 		"max_lease_ttl",
 | |
| 
 | |
| 		// TODO: Remove in 0.6.0
 | |
| 		// Deprecated keys
 | |
| 		"statsd_addr",
 | |
| 		"statsite_addr",
 | |
| 	}
 | |
| 	if err := checkHCLKeys(list, valid); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// TODO: Remove in 0.6.0
 | |
| 	// Preflight checks for deprecated keys
 | |
| 	sda := list.Filter("statsd_addr")
 | |
| 	ssa := list.Filter("statsite_addr")
 | |
| 	if len(sda.Items) > 0 || len(ssa.Items) > 0 {
 | |
| 		log.Println("[WARN] The top-level keys 'statsd_addr' and 'statsite_addr' " +
 | |
| 			"have been moved into a 'telemetry' block instead. Please update your " +
 | |
| 			"Vault configuration as this deprecation will be removed in the next " +
 | |
| 			"major release. Values specified in a 'telemetry' block will take " +
 | |
| 			"precendence.")
 | |
| 
 | |
| 		t := struct {
 | |
| 			StatsdAddr   string `hcl:"statsd_addr"`
 | |
| 			StatsiteAddr string `hcl:"statsite_addr"`
 | |
| 		}{}
 | |
| 		if err := hcl.DecodeObject(&t, list); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		result.Telemetry = &Telemetry{
 | |
| 			StatsdAddr:   t.StatsdAddr,
 | |
| 			StatsiteAddr: t.StatsiteAddr,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if o := list.Filter("backend"); len(o.Items) > 0 {
 | |
| 		if err := parseBackends(&result, o); err != nil {
 | |
| 			return nil, fmt.Errorf("error parsing 'backend': %s", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if o := list.Filter("ha_backend"); len(o.Items) > 0 {
 | |
| 		if err := parseHABackends(&result, o); err != nil {
 | |
| 			return nil, fmt.Errorf("error parsing 'ha_backend': %s", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if o := list.Filter("listener"); len(o.Items) > 0 {
 | |
| 		if err := parseListeners(&result, o); err != nil {
 | |
| 			return nil, fmt.Errorf("error parsing 'listener': %s", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if o := list.Filter("telemetry"); len(o.Items) > 0 {
 | |
| 		if err := parseTelemetry(&result, o); err != nil {
 | |
| 			return nil, fmt.Errorf("error parsing 'telemetry': %s", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return &result, nil
 | |
| }
 | |
| 
 | |
| // LoadConfigDir loads all the configurations in the given directory
 | |
| // in alphabetical order.
 | |
| func LoadConfigDir(dir string) (*Config, error) {
 | |
| 	f, err := os.Open(dir)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	fi, err := f.Stat()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if !fi.IsDir() {
 | |
| 		return nil, fmt.Errorf(
 | |
| 			"configuration path must be a directory: %s",
 | |
| 			dir)
 | |
| 	}
 | |
| 
 | |
| 	var files []string
 | |
| 	err = nil
 | |
| 	for err != io.EOF {
 | |
| 		var fis []os.FileInfo
 | |
| 		fis, err = f.Readdir(128)
 | |
| 		if err != nil && err != io.EOF {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		for _, fi := range fis {
 | |
| 			// Ignore directories
 | |
| 			if fi.IsDir() {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			// Only care about files that are valid to load.
 | |
| 			name := fi.Name()
 | |
| 			skip := true
 | |
| 			if strings.HasSuffix(name, ".hcl") {
 | |
| 				skip = false
 | |
| 			} else if strings.HasSuffix(name, ".json") {
 | |
| 				skip = false
 | |
| 			}
 | |
| 			if skip || isTemporaryFile(name) {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			path := filepath.Join(dir, name)
 | |
| 			files = append(files, path)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var result *Config
 | |
| 	for _, f := range files {
 | |
| 		config, err := LoadConfigFile(f)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("Error loading %s: %s", f, err)
 | |
| 		}
 | |
| 
 | |
| 		if result == nil {
 | |
| 			result = config
 | |
| 		} else {
 | |
| 			result = result.Merge(config)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| // isTemporaryFile returns true or false depending on whether the
 | |
| // provided file name is a temporary file for the following editors:
 | |
| // emacs or vim.
 | |
| func isTemporaryFile(name string) bool {
 | |
| 	return strings.HasSuffix(name, "~") || // vim
 | |
| 		strings.HasPrefix(name, ".#") || // emacs
 | |
| 		(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
 | |
| }
 | |
| 
 | |
| func parseBackends(result *Config, list *ast.ObjectList) error {
 | |
| 	if len(list.Items) > 1 {
 | |
| 		return fmt.Errorf("only one 'backend' block is permitted")
 | |
| 	}
 | |
| 
 | |
| 	// Get our item
 | |
| 	item := list.Items[0]
 | |
| 
 | |
| 	key := "backend"
 | |
| 	if len(item.Keys) > 0 {
 | |
| 		key = item.Keys[0].Token.Value().(string)
 | |
| 	}
 | |
| 
 | |
| 	var m map[string]string
 | |
| 	if err := hcl.DecodeObject(&m, item.Val); err != nil {
 | |
| 		return multierror.Prefix(err, fmt.Sprintf("backend.%s:", key))
 | |
| 	}
 | |
| 
 | |
| 	// Pull out the advertise address since it's common to all backends
 | |
| 	var advertiseAddr string
 | |
| 	if v, ok := m["advertise_addr"]; ok {
 | |
| 		advertiseAddr = v
 | |
| 		delete(m, "advertise_addr")
 | |
| 	}
 | |
| 
 | |
| 	result.Backend = &Backend{
 | |
| 		AdvertiseAddr: advertiseAddr,
 | |
| 		Type:          strings.ToLower(key),
 | |
| 		Config:        m,
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func parseHABackends(result *Config, list *ast.ObjectList) error {
 | |
| 	if len(list.Items) > 1 {
 | |
| 		return fmt.Errorf("only one 'ha_backend' block is permitted")
 | |
| 	}
 | |
| 
 | |
| 	// Get our item
 | |
| 	item := list.Items[0]
 | |
| 
 | |
| 	key := "backend"
 | |
| 	if len(item.Keys) > 0 {
 | |
| 		key = item.Keys[0].Token.Value().(string)
 | |
| 	}
 | |
| 
 | |
| 	var m map[string]string
 | |
| 	if err := hcl.DecodeObject(&m, item.Val); err != nil {
 | |
| 		return multierror.Prefix(err, fmt.Sprintf("ha_backend.%s:", key))
 | |
| 	}
 | |
| 
 | |
| 	// Pull out the advertise address since it's common to all backends
 | |
| 	var advertiseAddr string
 | |
| 	if v, ok := m["advertise_addr"]; ok {
 | |
| 		advertiseAddr = v
 | |
| 		delete(m, "advertise_addr")
 | |
| 	}
 | |
| 
 | |
| 	result.HABackend = &Backend{
 | |
| 		AdvertiseAddr: advertiseAddr,
 | |
| 		Type:          strings.ToLower(key),
 | |
| 		Config:        m,
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func parseListeners(result *Config, list *ast.ObjectList) error {
 | |
| 	var foundAtlas bool
 | |
| 
 | |
| 	listeners := make([]*Listener, 0, len(list.Items))
 | |
| 	for _, item := range list.Items {
 | |
| 		key := "listener"
 | |
| 		if len(item.Keys) > 0 {
 | |
| 			key = item.Keys[0].Token.Value().(string)
 | |
| 		}
 | |
| 
 | |
| 		valid := []string{
 | |
| 			"address",
 | |
| 			"endpoint",
 | |
| 			"infrastructure",
 | |
| 			"node_id",
 | |
| 			"tls_disable",
 | |
| 			"tls_cert_file",
 | |
| 			"tls_key_file",
 | |
| 			"tls_min_version",
 | |
| 			"token",
 | |
| 		}
 | |
| 		if err := checkHCLKeys(item.Val, valid); err != nil {
 | |
| 			return multierror.Prefix(err, fmt.Sprintf("listeners.%s:", key))
 | |
| 		}
 | |
| 
 | |
| 		var m map[string]string
 | |
| 		if err := hcl.DecodeObject(&m, item.Val); err != nil {
 | |
| 			return multierror.Prefix(err, fmt.Sprintf("listeners.%s:", key))
 | |
| 		}
 | |
| 
 | |
| 		lnType := strings.ToLower(key)
 | |
| 
 | |
| 		if lnType == "atlas" {
 | |
| 			if foundAtlas {
 | |
| 				return multierror.Prefix(fmt.Errorf("only one listener of type 'atlas' is permitted"), fmt.Sprintf("listeners.%s", key))
 | |
| 			} else {
 | |
| 				foundAtlas = true
 | |
| 				if m["token"] == "" {
 | |
| 					return multierror.Prefix(fmt.Errorf("'token' must be specified for an Atlas listener"), fmt.Sprintf("listeners.%s", key))
 | |
| 				}
 | |
| 				if m["infrastructure"] == "" {
 | |
| 					return multierror.Prefix(fmt.Errorf("'infrastructure' must be specified for an Atlas listener"), fmt.Sprintf("listeners.%s", key))
 | |
| 				}
 | |
| 				if m["node_id"] == "" {
 | |
| 					return multierror.Prefix(fmt.Errorf("'node_id' must be specified for an Atlas listener"), fmt.Sprintf("listeners.%s", key))
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		listeners = append(listeners, &Listener{
 | |
| 			Type:   lnType,
 | |
| 			Config: m,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	result.Listeners = listeners
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func parseTelemetry(result *Config, list *ast.ObjectList) error {
 | |
| 	if len(list.Items) > 1 {
 | |
| 		return fmt.Errorf("only one 'telemetry' block is permitted")
 | |
| 	}
 | |
| 
 | |
| 	// Get our one item
 | |
| 	item := list.Items[0]
 | |
| 
 | |
| 	// Check for invalid keys
 | |
| 	valid := []string{
 | |
| 		"statsite_address",
 | |
| 		"statsd_address",
 | |
| 		"disable_hostname",
 | |
| 	}
 | |
| 	if err := checkHCLKeys(item.Val, valid); err != nil {
 | |
| 		return multierror.Prefix(err, "telemetry:")
 | |
| 	}
 | |
| 
 | |
| 	var t Telemetry
 | |
| 	if err := hcl.DecodeObject(&t, item.Val); err != nil {
 | |
| 		return multierror.Prefix(err, "telemetry:")
 | |
| 	}
 | |
| 
 | |
| 	if result.Telemetry == nil {
 | |
| 		result.Telemetry = &Telemetry{}
 | |
| 	}
 | |
| 
 | |
| 	if err := hcl.DecodeObject(&result.Telemetry, item.Val); err != nil {
 | |
| 		return multierror.Prefix(err, "telemetry:")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func checkHCLKeys(node ast.Node, valid []string) error {
 | |
| 	var list *ast.ObjectList
 | |
| 	switch n := node.(type) {
 | |
| 	case *ast.ObjectList:
 | |
| 		list = n
 | |
| 	case *ast.ObjectType:
 | |
| 		list = n.List
 | |
| 	default:
 | |
| 		return fmt.Errorf("cannot check HCL keys of type %T", n)
 | |
| 	}
 | |
| 
 | |
| 	validMap := make(map[string]struct{}, len(valid))
 | |
| 	for _, v := range valid {
 | |
| 		validMap[v] = struct{}{}
 | |
| 	}
 | |
| 
 | |
| 	var result error
 | |
| 	for _, item := range list.Items {
 | |
| 		key := item.Keys[0].Token.Value().(string)
 | |
| 		if _, ok := validMap[key]; !ok {
 | |
| 			result = multierror.Append(result, fmt.Errorf(
 | |
| 				"invalid key '%s' on line %d", key, item.Assign.Line))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return result
 | |
| }
 | 
