1package server
2
3import (
4	"errors"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"strconv"
11	"strings"
12	"time"
13
14	"github.com/hashicorp/errwrap"
15
16	"github.com/hashicorp/go-multierror"
17	"github.com/hashicorp/hcl"
18	"github.com/hashicorp/hcl/hcl/ast"
19	"github.com/hashicorp/vault/sdk/helper/parseutil"
20)
21
22const (
23	prometheusDefaultRetentionTime = 24 * time.Hour
24)
25
26// Config is the configuration for the vault server.
27type Config struct {
28	Listeners []*Listener `hcl:"-"`
29	Storage   *Storage    `hcl:"-"`
30	HAStorage *Storage    `hcl:"-"`
31
32	Seals []*Seal `hcl:"-"`
33
34	CacheSize                int         `hcl:"cache_size"`
35	DisableCache             bool        `hcl:"-"`
36	DisableCacheRaw          interface{} `hcl:"disable_cache"`
37	DisableMlock             bool        `hcl:"-"`
38	DisableMlockRaw          interface{} `hcl:"disable_mlock"`
39	DisablePrintableCheck    bool        `hcl:"-"`
40	DisablePrintableCheckRaw interface{} `hcl:"disable_printable_check"`
41
42	EnableUI    bool        `hcl:"-"`
43	EnableUIRaw interface{} `hcl:"ui"`
44
45	Telemetry *Telemetry `hcl:"telemetry"`
46
47	MaxLeaseTTL        time.Duration `hcl:"-"`
48	MaxLeaseTTLRaw     interface{}   `hcl:"max_lease_ttl"`
49	DefaultLeaseTTL    time.Duration `hcl:"-"`
50	DefaultLeaseTTLRaw interface{}   `hcl:"default_lease_ttl"`
51
52	DefaultMaxRequestDuration    time.Duration `hcl:"-"`
53	DefaultMaxRequestDurationRaw interface{}   `hcl:"default_max_request_duration"`
54
55	ClusterName         string `hcl:"cluster_name"`
56	ClusterCipherSuites string `hcl:"cluster_cipher_suites"`
57
58	PluginDirectory string `hcl:"plugin_directory"`
59
60	LogLevel string `hcl:"log_level"`
61
62	// LogFormat specifies the log format.  Valid values are "standard" and "json".  The values are case-insenstive.
63	// If no log format is specified, then standard format will be used.
64	LogFormat string `hcl:"log_format"`
65
66	PidFile              string      `hcl:"pid_file"`
67	EnableRawEndpoint    bool        `hcl:"-"`
68	EnableRawEndpointRaw interface{} `hcl:"raw_storage_endpoint"`
69
70	APIAddr              string      `hcl:"api_addr"`
71	ClusterAddr          string      `hcl:"cluster_addr"`
72	DisableClustering    bool        `hcl:"-"`
73	DisableClusteringRaw interface{} `hcl:"disable_clustering"`
74
75	DisablePerformanceStandby    bool        `hcl:"-"`
76	DisablePerformanceStandbyRaw interface{} `hcl:"disable_performance_standby"`
77
78	DisableSealWrap    bool        `hcl:"-"`
79	DisableSealWrapRaw interface{} `hcl:"disable_sealwrap"`
80
81	DisableIndexing    bool        `hcl:"-"`
82	DisableIndexingRaw interface{} `hcl:"disable_indexing"`
83}
84
85// DevConfig is a Config that is used for dev mode of Vault.
86func DevConfig(storageType string) *Config {
87	ret := &Config{
88		DisableMlock:      true,
89		EnableRawEndpoint: true,
90
91		Storage: &Storage{
92			Type: storageType,
93		},
94
95		Listeners: []*Listener{
96			&Listener{
97				Type: "tcp",
98				Config: map[string]interface{}{
99					"address":                         "127.0.0.1:8200",
100					"tls_disable":                     true,
101					"proxy_protocol_behavior":         "allow_authorized",
102					"proxy_protocol_authorized_addrs": "127.0.0.1:8200",
103				},
104			},
105		},
106
107		EnableUI: true,
108
109		Telemetry: &Telemetry{
110			PrometheusRetentionTime: prometheusDefaultRetentionTime,
111			DisableHostname:         true,
112		},
113	}
114
115	return ret
116}
117
118// Listener is the listener configuration for the server.
119type Listener struct {
120	Type   string
121	Config map[string]interface{}
122}
123
124func (l *Listener) GoString() string {
125	return fmt.Sprintf("*%#v", *l)
126}
127
128// Storage is the underlying storage configuration for the server.
129type Storage struct {
130	Type              string
131	RedirectAddr      string
132	ClusterAddr       string
133	DisableClustering bool
134	Config            map[string]string
135}
136
137func (b *Storage) GoString() string {
138	return fmt.Sprintf("*%#v", *b)
139}
140
141// Seal contains Seal configuration for the server
142type Seal struct {
143	Type     string
144	Disabled bool
145	Config   map[string]string
146}
147
148func (h *Seal) GoString() string {
149	return fmt.Sprintf("*%#v", *h)
150}
151
152// Telemetry is the telemetry configuration for the server
153type Telemetry struct {
154	StatsiteAddr string `hcl:"statsite_address"`
155	StatsdAddr   string `hcl:"statsd_address"`
156
157	DisableHostname bool `hcl:"disable_hostname"`
158
159	// Circonus: see https://github.com/circonus-labs/circonus-gometrics
160	// for more details on the various configuration options.
161	// Valid configuration combinations:
162	//    - CirconusAPIToken
163	//      metric management enabled (search for existing check or create a new one)
164	//    - CirconusSubmissionUrl
165	//      metric management disabled (use check with specified submission_url,
166	//      broker must be using a public SSL certificate)
167	//    - CirconusAPIToken + CirconusCheckSubmissionURL
168	//      metric management enabled (use check with specified submission_url)
169	//    - CirconusAPIToken + CirconusCheckID
170	//      metric management enabled (use check with specified id)
171
172	// CirconusAPIToken is a valid API Token used to create/manage check. If provided,
173	// metric management is enabled.
174	// Default: none
175	CirconusAPIToken string `hcl:"circonus_api_token"`
176	// CirconusAPIApp is an app name associated with API token.
177	// Default: "consul"
178	CirconusAPIApp string `hcl:"circonus_api_app"`
179	// CirconusAPIURL is the base URL to use for contacting the Circonus API.
180	// Default: "https://api.circonus.com/v2"
181	CirconusAPIURL string `hcl:"circonus_api_url"`
182	// CirconusSubmissionInterval is the interval at which metrics are submitted to Circonus.
183	// Default: 10s
184	CirconusSubmissionInterval string `hcl:"circonus_submission_interval"`
185	// CirconusCheckSubmissionURL is the check.config.submission_url field from a
186	// previously created HTTPTRAP check.
187	// Default: none
188	CirconusCheckSubmissionURL string `hcl:"circonus_submission_url"`
189	// CirconusCheckID is the check id (not check bundle id) from a previously created
190	// HTTPTRAP check. The numeric portion of the check._cid field.
191	// Default: none
192	CirconusCheckID string `hcl:"circonus_check_id"`
193	// CirconusCheckForceMetricActivation will force enabling metrics, as they are encountered,
194	// if the metric already exists and is NOT active. If check management is enabled, the default
195	// behavior is to add new metrics as they are encountered. If the metric already exists in the
196	// check, it will *NOT* be activated. This setting overrides that behavior.
197	// Default: "false"
198	CirconusCheckForceMetricActivation string `hcl:"circonus_check_force_metric_activation"`
199	// CirconusCheckInstanceID serves to uniquely identify the metrics coming from this "instance".
200	// It can be used to maintain metric continuity with transient or ephemeral instances as
201	// they move around within an infrastructure.
202	// Default: hostname:app
203	CirconusCheckInstanceID string `hcl:"circonus_check_instance_id"`
204	// CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to
205	// narrow down the search results when neither a Submission URL or Check ID is provided.
206	// Default: service:app (e.g. service:consul)
207	CirconusCheckSearchTag string `hcl:"circonus_check_search_tag"`
208	// CirconusCheckTags is a comma separated list of tags to apply to the check. Note that
209	// the value of CirconusCheckSearchTag will always be added to the check.
210	// Default: none
211	CirconusCheckTags string `mapstructure:"circonus_check_tags"`
212	// CirconusCheckDisplayName is the name for the check which will be displayed in the Circonus UI.
213	// Default: value of CirconusCheckInstanceID
214	CirconusCheckDisplayName string `mapstructure:"circonus_check_display_name"`
215	// CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion
216	// of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID
217	// is provided, an attempt will be made to search for an existing check using Instance ID and
218	// Search Tag. If one is not found, a new HTTPTRAP check will be created.
219	// Default: use Select Tag if provided, otherwise, a random Enterprise Broker associated
220	// with the specified API token or the default Circonus Broker.
221	// Default: none
222	CirconusBrokerID string `hcl:"circonus_broker_id"`
223	// CirconusBrokerSelectTag is a special tag which will be used to select a broker when
224	// a Broker ID is not provided. The best use of this is to as a hint for which broker
225	// should be used based on *where* this particular instance is running.
226	// (e.g. a specific geo location or datacenter, dc:sfo)
227	// Default: none
228	CirconusBrokerSelectTag string `hcl:"circonus_broker_select_tag"`
229
230	// Dogstats:
231	// DogStatsdAddr is the address of a dogstatsd instance. If provided,
232	// metrics will be sent to that instance
233	DogStatsDAddr string `hcl:"dogstatsd_addr"`
234
235	// DogStatsdTags are the global tags that should be sent with each packet to dogstatsd
236	// It is a list of strings, where each string looks like "my_tag_name:my_tag_value"
237	DogStatsDTags []string `hcl:"dogstatsd_tags"`
238
239	// Prometheus:
240	// PrometheusRetentionTime is the retention time for prometheus metrics if greater than 0.
241	// Default: 24h
242	PrometheusRetentionTime    time.Duration `hcl:-`
243	PrometheusRetentionTimeRaw interface{}   `hcl:"prometheus_retention_time"`
244}
245
246func (s *Telemetry) GoString() string {
247	return fmt.Sprintf("*%#v", *s)
248}
249
250// Merge merges two configurations.
251func (c *Config) Merge(c2 *Config) *Config {
252	if c2 == nil {
253		return c
254	}
255
256	result := new(Config)
257	for _, l := range c.Listeners {
258		result.Listeners = append(result.Listeners, l)
259	}
260	for _, l := range c2.Listeners {
261		result.Listeners = append(result.Listeners, l)
262	}
263
264	result.Storage = c.Storage
265	if c2.Storage != nil {
266		result.Storage = c2.Storage
267	}
268
269	result.HAStorage = c.HAStorage
270	if c2.HAStorage != nil {
271		result.HAStorage = c2.HAStorage
272	}
273
274	for _, s := range c.Seals {
275		result.Seals = append(result.Seals, s)
276	}
277	for _, s := range c2.Seals {
278		result.Seals = append(result.Seals, s)
279	}
280
281	result.Telemetry = c.Telemetry
282	if c2.Telemetry != nil {
283		result.Telemetry = c2.Telemetry
284	}
285
286	result.CacheSize = c.CacheSize
287	if c2.CacheSize != 0 {
288		result.CacheSize = c2.CacheSize
289	}
290
291	// merging these booleans via an OR operation
292	result.DisableCache = c.DisableCache
293	if c2.DisableCache {
294		result.DisableCache = c2.DisableCache
295	}
296
297	result.DisableMlock = c.DisableMlock
298	if c2.DisableMlock {
299		result.DisableMlock = c2.DisableMlock
300	}
301
302	result.DisablePrintableCheck = c.DisablePrintableCheck
303	if c2.DisablePrintableCheckRaw != nil {
304		result.DisablePrintableCheck = c2.DisablePrintableCheck
305	}
306
307	// merge these integers via a MAX operation
308	result.MaxLeaseTTL = c.MaxLeaseTTL
309	if c2.MaxLeaseTTL > result.MaxLeaseTTL {
310		result.MaxLeaseTTL = c2.MaxLeaseTTL
311	}
312
313	result.DefaultLeaseTTL = c.DefaultLeaseTTL
314	if c2.DefaultLeaseTTL > result.DefaultLeaseTTL {
315		result.DefaultLeaseTTL = c2.DefaultLeaseTTL
316	}
317
318	result.DefaultMaxRequestDuration = c.DefaultMaxRequestDuration
319	if c2.DefaultMaxRequestDuration > result.DefaultMaxRequestDuration {
320		result.DefaultMaxRequestDuration = c2.DefaultMaxRequestDuration
321	}
322
323	result.LogLevel = c.LogLevel
324	if c2.LogLevel != "" {
325		result.LogLevel = c2.LogLevel
326	}
327
328	result.LogFormat = c.LogFormat
329	if c2.LogFormat != "" {
330		result.LogFormat = c2.LogFormat
331	}
332
333	result.ClusterName = c.ClusterName
334	if c2.ClusterName != "" {
335		result.ClusterName = c2.ClusterName
336	}
337
338	result.ClusterCipherSuites = c.ClusterCipherSuites
339	if c2.ClusterCipherSuites != "" {
340		result.ClusterCipherSuites = c2.ClusterCipherSuites
341	}
342
343	result.EnableUI = c.EnableUI
344	if c2.EnableUI {
345		result.EnableUI = c2.EnableUI
346	}
347
348	result.EnableRawEndpoint = c.EnableRawEndpoint
349	if c2.EnableRawEndpoint {
350		result.EnableRawEndpoint = c2.EnableRawEndpoint
351	}
352
353	result.APIAddr = c.APIAddr
354	if c2.APIAddr != "" {
355		result.APIAddr = c2.APIAddr
356	}
357
358	result.ClusterAddr = c.ClusterAddr
359	if c2.ClusterAddr != "" {
360		result.ClusterAddr = c2.ClusterAddr
361	}
362
363	// Retain raw value so that it can be assigned to storage objects
364	result.DisableClustering = c.DisableClustering
365	result.DisableClusteringRaw = c.DisableClusteringRaw
366	if c2.DisableClusteringRaw != nil {
367		result.DisableClustering = c2.DisableClustering
368		result.DisableClusteringRaw = c2.DisableClusteringRaw
369	}
370
371	result.PluginDirectory = c.PluginDirectory
372	if c2.PluginDirectory != "" {
373		result.PluginDirectory = c2.PluginDirectory
374	}
375
376	result.PidFile = c.PidFile
377	if c2.PidFile != "" {
378		result.PidFile = c2.PidFile
379	}
380
381	result.DisablePerformanceStandby = c.DisablePerformanceStandby
382	if c2.DisablePerformanceStandby {
383		result.DisablePerformanceStandby = c2.DisablePerformanceStandby
384	}
385
386	result.DisableSealWrap = c.DisableSealWrap
387	if c2.DisableSealWrap {
388		result.DisableSealWrap = c2.DisableSealWrap
389	}
390
391	result.DisableIndexing = c.DisableIndexing
392	if c2.DisableIndexing {
393		result.DisableIndexing = c2.DisableIndexing
394	}
395
396	// Use values from top-level configuration for storage if set
397	if storage := result.Storage; storage != nil {
398		if result.APIAddr != "" {
399			storage.RedirectAddr = result.APIAddr
400		}
401		if result.ClusterAddr != "" {
402			storage.ClusterAddr = result.ClusterAddr
403		}
404		if result.DisableClusteringRaw != nil {
405			storage.DisableClustering = result.DisableClustering
406		}
407	}
408
409	if haStorage := result.HAStorage; haStorage != nil {
410		if result.APIAddr != "" {
411			haStorage.RedirectAddr = result.APIAddr
412		}
413		if result.ClusterAddr != "" {
414			haStorage.ClusterAddr = result.ClusterAddr
415		}
416		if result.DisableClusteringRaw != nil {
417			haStorage.DisableClustering = result.DisableClustering
418		}
419	}
420
421	return result
422}
423
424// LoadConfig loads the configuration at the given path, regardless if
425// its a file or directory.
426func LoadConfig(path string) (*Config, error) {
427	fi, err := os.Stat(path)
428	if err != nil {
429		return nil, err
430	}
431
432	if fi.IsDir() {
433		return LoadConfigDir(path)
434	}
435	return LoadConfigFile(path)
436}
437
438// LoadConfigFile loads the configuration from the given file.
439func LoadConfigFile(path string) (*Config, error) {
440	// Read the file
441	d, err := ioutil.ReadFile(path)
442	if err != nil {
443		return nil, err
444	}
445	return ParseConfig(string(d))
446}
447
448func ParseConfig(d string) (*Config, error) {
449	// Parse!
450	obj, err := hcl.Parse(d)
451	if err != nil {
452		return nil, err
453	}
454
455	// Start building the result
456	var result Config
457	if err := hcl.DecodeObject(&result, obj); err != nil {
458		return nil, err
459	}
460
461	if result.MaxLeaseTTLRaw != nil {
462		if result.MaxLeaseTTL, err = parseutil.ParseDurationSecond(result.MaxLeaseTTLRaw); err != nil {
463			return nil, err
464		}
465	}
466	if result.DefaultLeaseTTLRaw != nil {
467		if result.DefaultLeaseTTL, err = parseutil.ParseDurationSecond(result.DefaultLeaseTTLRaw); err != nil {
468			return nil, err
469		}
470	}
471
472	if result.DefaultMaxRequestDurationRaw != nil {
473		if result.DefaultMaxRequestDuration, err = parseutil.ParseDurationSecond(result.DefaultMaxRequestDurationRaw); err != nil {
474			return nil, err
475		}
476	}
477
478	if result.EnableUIRaw != nil {
479		if result.EnableUI, err = parseutil.ParseBool(result.EnableUIRaw); err != nil {
480			return nil, err
481		}
482	}
483
484	if result.DisableCacheRaw != nil {
485		if result.DisableCache, err = parseutil.ParseBool(result.DisableCacheRaw); err != nil {
486			return nil, err
487		}
488	}
489
490	if result.DisableMlockRaw != nil {
491		if result.DisableMlock, err = parseutil.ParseBool(result.DisableMlockRaw); err != nil {
492			return nil, err
493		}
494	}
495
496	if result.DisablePrintableCheckRaw != nil {
497		if result.DisablePrintableCheck, err = parseutil.ParseBool(result.DisablePrintableCheckRaw); err != nil {
498			return nil, err
499		}
500	}
501
502	if result.EnableRawEndpointRaw != nil {
503		if result.EnableRawEndpoint, err = parseutil.ParseBool(result.EnableRawEndpointRaw); err != nil {
504			return nil, err
505		}
506	}
507
508	if result.DisableClusteringRaw != nil {
509		if result.DisableClustering, err = parseutil.ParseBool(result.DisableClusteringRaw); err != nil {
510			return nil, err
511		}
512	}
513
514	if result.DisablePerformanceStandbyRaw != nil {
515		if result.DisablePerformanceStandby, err = parseutil.ParseBool(result.DisablePerformanceStandbyRaw); err != nil {
516			return nil, err
517		}
518	}
519
520	if result.DisableSealWrapRaw != nil {
521		if result.DisableSealWrap, err = parseutil.ParseBool(result.DisableSealWrapRaw); err != nil {
522			return nil, err
523		}
524	}
525
526	if result.DisableIndexingRaw != nil {
527		if result.DisableIndexing, err = parseutil.ParseBool(result.DisableIndexingRaw); err != nil {
528			return nil, err
529		}
530	}
531
532	list, ok := obj.Node.(*ast.ObjectList)
533	if !ok {
534		return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
535	}
536
537	// Look for storage but still support old backend
538	if o := list.Filter("storage"); len(o.Items) > 0 {
539		if err := ParseStorage(&result, o, "storage"); err != nil {
540			return nil, errwrap.Wrapf("error parsing 'storage': {{err}}", err)
541		}
542	} else {
543		if o := list.Filter("backend"); len(o.Items) > 0 {
544			if err := ParseStorage(&result, o, "backend"); err != nil {
545				return nil, errwrap.Wrapf("error parsing 'backend': {{err}}", err)
546			}
547		}
548	}
549
550	if o := list.Filter("ha_storage"); len(o.Items) > 0 {
551		if err := parseHAStorage(&result, o, "ha_storage"); err != nil {
552			return nil, errwrap.Wrapf("error parsing 'ha_storage': {{err}}", err)
553		}
554	} else {
555		if o := list.Filter("ha_backend"); len(o.Items) > 0 {
556			if err := parseHAStorage(&result, o, "ha_backend"); err != nil {
557				return nil, errwrap.Wrapf("error parsing 'ha_backend': {{err}}", err)
558			}
559		}
560	}
561
562	if o := list.Filter("hsm"); len(o.Items) > 0 {
563		if err := parseSeals(&result, o, "hsm"); err != nil {
564			return nil, errwrap.Wrapf("error parsing 'hsm': {{err}}", err)
565		}
566	}
567
568	if o := list.Filter("seal"); len(o.Items) > 0 {
569		if err := parseSeals(&result, o, "seal"); err != nil {
570			return nil, errwrap.Wrapf("error parsing 'seal': {{err}}", err)
571		}
572	}
573
574	if o := list.Filter("listener"); len(o.Items) > 0 {
575		if err := parseListeners(&result, o); err != nil {
576			return nil, errwrap.Wrapf("error parsing 'listener': {{err}}", err)
577		}
578	}
579
580	if o := list.Filter("telemetry"); len(o.Items) > 0 {
581		if err := parseTelemetry(&result, o); err != nil {
582			return nil, errwrap.Wrapf("error parsing 'telemetry': {{err}}", err)
583		}
584	}
585
586	return &result, nil
587}
588
589// LoadConfigDir loads all the configurations in the given directory
590// in alphabetical order.
591func LoadConfigDir(dir string) (*Config, error) {
592	f, err := os.Open(dir)
593	if err != nil {
594		return nil, err
595	}
596	defer f.Close()
597
598	fi, err := f.Stat()
599	if err != nil {
600		return nil, err
601	}
602	if !fi.IsDir() {
603		return nil, fmt.Errorf("configuration path must be a directory: %q", dir)
604	}
605
606	var files []string
607	err = nil
608	for err != io.EOF {
609		var fis []os.FileInfo
610		fis, err = f.Readdir(128)
611		if err != nil && err != io.EOF {
612			return nil, err
613		}
614
615		for _, fi := range fis {
616			// Ignore directories
617			if fi.IsDir() {
618				continue
619			}
620
621			// Only care about files that are valid to load.
622			name := fi.Name()
623			skip := true
624			if strings.HasSuffix(name, ".hcl") {
625				skip = false
626			} else if strings.HasSuffix(name, ".json") {
627				skip = false
628			}
629			if skip || isTemporaryFile(name) {
630				continue
631			}
632
633			path := filepath.Join(dir, name)
634			files = append(files, path)
635		}
636	}
637
638	var result *Config
639	for _, f := range files {
640		config, err := LoadConfigFile(f)
641		if err != nil {
642			return nil, errwrap.Wrapf(fmt.Sprintf("error loading %q: {{err}}", f), err)
643		}
644
645		if result == nil {
646			result = config
647		} else {
648			result = result.Merge(config)
649		}
650	}
651
652	return result, nil
653}
654
655// isTemporaryFile returns true or false depending on whether the
656// provided file name is a temporary file for the following editors:
657// emacs or vim.
658func isTemporaryFile(name string) bool {
659	return strings.HasSuffix(name, "~") || // vim
660		strings.HasPrefix(name, ".#") || // emacs
661		(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
662}
663
664func ParseStorage(result *Config, list *ast.ObjectList, name string) error {
665	if len(list.Items) > 1 {
666		return fmt.Errorf("only one %q block is permitted", name)
667	}
668
669	// Get our item
670	item := list.Items[0]
671
672	key := name
673	if len(item.Keys) > 0 {
674		key = item.Keys[0].Token.Value().(string)
675	}
676
677	var m map[string]string
678	if err := hcl.DecodeObject(&m, item.Val); err != nil {
679		return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
680	}
681
682	// Pull out the redirect address since it's common to all backends
683	var redirectAddr string
684	if v, ok := m["redirect_addr"]; ok {
685		redirectAddr = v
686		delete(m, "redirect_addr")
687	} else if v, ok := m["advertise_addr"]; ok {
688		redirectAddr = v
689		delete(m, "advertise_addr")
690	}
691
692	// Pull out the cluster address since it's common to all backends
693	var clusterAddr string
694	if v, ok := m["cluster_addr"]; ok {
695		clusterAddr = v
696		delete(m, "cluster_addr")
697	}
698
699	var disableClustering bool
700	var err error
701	if v, ok := m["disable_clustering"]; ok {
702		disableClustering, err = strconv.ParseBool(v)
703		if err != nil {
704			return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
705		}
706		delete(m, "disable_clustering")
707	}
708
709	// Override with top-level values if they are set
710	if result.APIAddr != "" {
711		redirectAddr = result.APIAddr
712	}
713
714	if result.ClusterAddr != "" {
715		clusterAddr = result.ClusterAddr
716	}
717
718	if result.DisableClusteringRaw != nil {
719		disableClustering = result.DisableClustering
720	}
721
722	result.Storage = &Storage{
723		RedirectAddr:      redirectAddr,
724		ClusterAddr:       clusterAddr,
725		DisableClustering: disableClustering,
726		Type:              strings.ToLower(key),
727		Config:            m,
728	}
729	return nil
730}
731
732func parseHAStorage(result *Config, list *ast.ObjectList, name string) error {
733	if len(list.Items) > 1 {
734		return fmt.Errorf("only one %q block is permitted", name)
735	}
736
737	// Get our item
738	item := list.Items[0]
739
740	key := name
741	if len(item.Keys) > 0 {
742		key = item.Keys[0].Token.Value().(string)
743	}
744
745	var m map[string]string
746	if err := hcl.DecodeObject(&m, item.Val); err != nil {
747		return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
748	}
749
750	// Pull out the redirect address since it's common to all backends
751	var redirectAddr string
752	if v, ok := m["redirect_addr"]; ok {
753		redirectAddr = v
754		delete(m, "redirect_addr")
755	} else if v, ok := m["advertise_addr"]; ok {
756		redirectAddr = v
757		delete(m, "advertise_addr")
758	}
759
760	// Pull out the cluster address since it's common to all backends
761	var clusterAddr string
762	if v, ok := m["cluster_addr"]; ok {
763		clusterAddr = v
764		delete(m, "cluster_addr")
765	}
766
767	var disableClustering bool
768	var err error
769	if v, ok := m["disable_clustering"]; ok {
770		disableClustering, err = strconv.ParseBool(v)
771		if err != nil {
772			return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
773		}
774		delete(m, "disable_clustering")
775	}
776
777	// Override with top-level values if they are set
778	if result.APIAddr != "" {
779		redirectAddr = result.APIAddr
780	}
781
782	if result.ClusterAddr != "" {
783		clusterAddr = result.ClusterAddr
784	}
785
786	if result.DisableClusteringRaw != nil {
787		disableClustering = result.DisableClustering
788	}
789
790	result.HAStorage = &Storage{
791		RedirectAddr:      redirectAddr,
792		ClusterAddr:       clusterAddr,
793		DisableClustering: disableClustering,
794		Type:              strings.ToLower(key),
795		Config:            m,
796	}
797	return nil
798}
799
800func parseSeals(result *Config, list *ast.ObjectList, blockName string) error {
801	if len(list.Items) > 2 {
802		return fmt.Errorf("only two or less %q blocks are permitted", blockName)
803	}
804
805	seals := make([]*Seal, 0, len(list.Items))
806	for _, item := range list.Items {
807		key := "seal"
808		if len(item.Keys) > 0 {
809			key = item.Keys[0].Token.Value().(string)
810		}
811
812		var m map[string]string
813		if err := hcl.DecodeObject(&m, item.Val); err != nil {
814			return multierror.Prefix(err, fmt.Sprintf("seal.%s:", key))
815		}
816
817		var disabled bool
818		var err error
819		if v, ok := m["disabled"]; ok {
820			disabled, err = strconv.ParseBool(v)
821			if err != nil {
822				return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
823			}
824			delete(m, "disabled")
825		}
826		seals = append(seals, &Seal{
827			Type:     strings.ToLower(key),
828			Disabled: disabled,
829			Config:   m,
830		})
831	}
832
833	if len(seals) == 2 &&
834		(seals[0].Disabled && seals[1].Disabled || !seals[0].Disabled && !seals[1].Disabled) {
835		return errors.New("seals: two seals provided but both are disabled or neither are disabled")
836	}
837
838	result.Seals = seals
839
840	return nil
841}
842
843func parseListeners(result *Config, list *ast.ObjectList) error {
844	listeners := make([]*Listener, 0, len(list.Items))
845	for _, item := range list.Items {
846		key := "listener"
847		if len(item.Keys) > 0 {
848			key = item.Keys[0].Token.Value().(string)
849		}
850
851		var m map[string]interface{}
852		if err := hcl.DecodeObject(&m, item.Val); err != nil {
853			return multierror.Prefix(err, fmt.Sprintf("listeners.%s:", key))
854		}
855
856		lnType := strings.ToLower(key)
857
858		listeners = append(listeners, &Listener{
859			Type:   lnType,
860			Config: m,
861		})
862	}
863
864	result.Listeners = listeners
865	return nil
866}
867
868func parseTelemetry(result *Config, list *ast.ObjectList) error {
869	if len(list.Items) > 1 {
870		return fmt.Errorf("only one 'telemetry' block is permitted")
871	}
872
873	// Get our one item
874	item := list.Items[0]
875
876	var t Telemetry
877	if err := hcl.DecodeObject(&t, item.Val); err != nil {
878		return multierror.Prefix(err, "telemetry:")
879	}
880
881	if result.Telemetry == nil {
882		result.Telemetry = &Telemetry{}
883	}
884
885	if err := hcl.DecodeObject(&result.Telemetry, item.Val); err != nil {
886		return multierror.Prefix(err, "telemetry:")
887	}
888
889	if result.Telemetry.PrometheusRetentionTimeRaw != nil {
890		var err error
891		if result.Telemetry.PrometheusRetentionTime, err = parseutil.ParseDurationSecond(result.Telemetry.PrometheusRetentionTimeRaw); err != nil {
892			return err
893		}
894	} else {
895		result.Telemetry.PrometheusRetentionTime = prometheusDefaultRetentionTime
896	}
897
898	return nil
899}
900