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	"github.com/hashicorp/go-multierror"
16	"github.com/hashicorp/hcl"
17	"github.com/hashicorp/hcl/hcl/ast"
18	"github.com/hashicorp/vault/sdk/helper/parseutil"
19)
20
21const (
22	prometheusDefaultRetentionTime = 24 * time.Hour
23)
24
25// Config is the configuration for the vault server.
26type Config struct {
27	Listeners []*Listener `hcl:"-"`
28	Storage   *Storage    `hcl:"-"`
29	HAStorage *Storage    `hcl:"-"`
30
31	Seals   []*Seal  `hcl:"-"`
32	Entropy *Entropy `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// Entropy contains Entropy configuration for the server
129type EntropyMode int
130
131const (
132	Unknown EntropyMode = iota
133	Augmentation
134)
135
136type Entropy struct {
137	Mode EntropyMode
138}
139
140// Storage is the underlying storage configuration for the server.
141type Storage struct {
142	Type              string
143	RedirectAddr      string
144	ClusterAddr       string
145	DisableClustering bool
146	Config            map[string]string
147}
148
149func (b *Storage) GoString() string {
150	return fmt.Sprintf("*%#v", *b)
151}
152
153// Seal contains Seal configuration for the server
154type Seal struct {
155	Type     string
156	Disabled bool
157	Config   map[string]string
158}
159
160func (h *Seal) GoString() string {
161	return fmt.Sprintf("*%#v", *h)
162}
163
164// Telemetry is the telemetry configuration for the server
165type Telemetry struct {
166	StatsiteAddr string `hcl:"statsite_address"`
167	StatsdAddr   string `hcl:"statsd_address"`
168
169	DisableHostname bool `hcl:"disable_hostname"`
170
171	// Circonus: see https://github.com/circonus-labs/circonus-gometrics
172	// for more details on the various configuration options.
173	// Valid configuration combinations:
174	//    - CirconusAPIToken
175	//      metric management enabled (search for existing check or create a new one)
176	//    - CirconusSubmissionUrl
177	//      metric management disabled (use check with specified submission_url,
178	//      broker must be using a public SSL certificate)
179	//    - CirconusAPIToken + CirconusCheckSubmissionURL
180	//      metric management enabled (use check with specified submission_url)
181	//    - CirconusAPIToken + CirconusCheckID
182	//      metric management enabled (use check with specified id)
183
184	// CirconusAPIToken is a valid API Token used to create/manage check. If provided,
185	// metric management is enabled.
186	// Default: none
187	CirconusAPIToken string `hcl:"circonus_api_token"`
188	// CirconusAPIApp is an app name associated with API token.
189	// Default: "consul"
190	CirconusAPIApp string `hcl:"circonus_api_app"`
191	// CirconusAPIURL is the base URL to use for contacting the Circonus API.
192	// Default: "https://api.circonus.com/v2"
193	CirconusAPIURL string `hcl:"circonus_api_url"`
194	// CirconusSubmissionInterval is the interval at which metrics are submitted to Circonus.
195	// Default: 10s
196	CirconusSubmissionInterval string `hcl:"circonus_submission_interval"`
197	// CirconusCheckSubmissionURL is the check.config.submission_url field from a
198	// previously created HTTPTRAP check.
199	// Default: none
200	CirconusCheckSubmissionURL string `hcl:"circonus_submission_url"`
201	// CirconusCheckID is the check id (not check bundle id) from a previously created
202	// HTTPTRAP check. The numeric portion of the check._cid field.
203	// Default: none
204	CirconusCheckID string `hcl:"circonus_check_id"`
205	// CirconusCheckForceMetricActivation will force enabling metrics, as they are encountered,
206	// if the metric already exists and is NOT active. If check management is enabled, the default
207	// behavior is to add new metrics as they are encountered. If the metric already exists in the
208	// check, it will *NOT* be activated. This setting overrides that behavior.
209	// Default: "false"
210	CirconusCheckForceMetricActivation string `hcl:"circonus_check_force_metric_activation"`
211	// CirconusCheckInstanceID serves to uniquely identify the metrics coming from this "instance".
212	// It can be used to maintain metric continuity with transient or ephemeral instances as
213	// they move around within an infrastructure.
214	// Default: hostname:app
215	CirconusCheckInstanceID string `hcl:"circonus_check_instance_id"`
216	// CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to
217	// narrow down the search results when neither a Submission URL or Check ID is provided.
218	// Default: service:app (e.g. service:consul)
219	CirconusCheckSearchTag string `hcl:"circonus_check_search_tag"`
220	// CirconusCheckTags is a comma separated list of tags to apply to the check. Note that
221	// the value of CirconusCheckSearchTag will always be added to the check.
222	// Default: none
223	CirconusCheckTags string `hcl:"circonus_check_tags"`
224	// CirconusCheckDisplayName is the name for the check which will be displayed in the Circonus UI.
225	// Default: value of CirconusCheckInstanceID
226	CirconusCheckDisplayName string `hcl:"circonus_check_display_name"`
227	// CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion
228	// of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID
229	// is provided, an attempt will be made to search for an existing check using Instance ID and
230	// Search Tag. If one is not found, a new HTTPTRAP check will be created.
231	// Default: use Select Tag if provided, otherwise, a random Enterprise Broker associated
232	// with the specified API token or the default Circonus Broker.
233	// Default: none
234	CirconusBrokerID string `hcl:"circonus_broker_id"`
235	// CirconusBrokerSelectTag is a special tag which will be used to select a broker when
236	// a Broker ID is not provided. The best use of this is to as a hint for which broker
237	// should be used based on *where* this particular instance is running.
238	// (e.g. a specific geo location or datacenter, dc:sfo)
239	// Default: none
240	CirconusBrokerSelectTag string `hcl:"circonus_broker_select_tag"`
241
242	// Dogstats:
243	// DogStatsdAddr is the address of a dogstatsd instance. If provided,
244	// metrics will be sent to that instance
245	DogStatsDAddr string `hcl:"dogstatsd_addr"`
246
247	// DogStatsdTags are the global tags that should be sent with each packet to dogstatsd
248	// It is a list of strings, where each string looks like "my_tag_name:my_tag_value"
249	DogStatsDTags []string `hcl:"dogstatsd_tags"`
250
251	// Prometheus:
252	// PrometheusRetentionTime is the retention time for prometheus metrics if greater than 0.
253	// Default: 24h
254	PrometheusRetentionTime    time.Duration `hcl:"-"`
255	PrometheusRetentionTimeRaw interface{}   `hcl:"prometheus_retention_time"`
256
257	// Stackdriver:
258	// StackdriverProjectID is the project to publish stackdriver metrics to.
259	StackdriverProjectID string `hcl:"stackdriver_project_id"`
260	// StackdriverLocation is the GCP or AWS region of the monitored resource.
261	StackdriverLocation string `hcl:"stackdriver_location"`
262	// StackdriverNamespace is the namespace identifier, such as a cluster name.
263	StackdriverNamespace string `hcl:"stackdriver_namespace"`
264}
265
266func (s *Telemetry) GoString() string {
267	return fmt.Sprintf("*%#v", *s)
268}
269
270// Merge merges two configurations.
271func (c *Config) Merge(c2 *Config) *Config {
272	if c2 == nil {
273		return c
274	}
275
276	result := new(Config)
277	for _, l := range c.Listeners {
278		result.Listeners = append(result.Listeners, l)
279	}
280	for _, l := range c2.Listeners {
281		result.Listeners = append(result.Listeners, l)
282	}
283
284	result.Storage = c.Storage
285	if c2.Storage != nil {
286		result.Storage = c2.Storage
287	}
288
289	result.HAStorage = c.HAStorage
290	if c2.HAStorage != nil {
291		result.HAStorage = c2.HAStorage
292	}
293
294	result.Entropy = c.Entropy
295	if c2.Entropy != nil {
296		result.Entropy = c2.Entropy
297	}
298
299	for _, s := range c.Seals {
300		result.Seals = append(result.Seals, s)
301	}
302	for _, s := range c2.Seals {
303		result.Seals = append(result.Seals, s)
304	}
305
306	result.Telemetry = c.Telemetry
307	if c2.Telemetry != nil {
308		result.Telemetry = c2.Telemetry
309	}
310
311	result.CacheSize = c.CacheSize
312	if c2.CacheSize != 0 {
313		result.CacheSize = c2.CacheSize
314	}
315
316	// merging these booleans via an OR operation
317	result.DisableCache = c.DisableCache
318	if c2.DisableCache {
319		result.DisableCache = c2.DisableCache
320	}
321
322	result.DisableMlock = c.DisableMlock
323	if c2.DisableMlock {
324		result.DisableMlock = c2.DisableMlock
325	}
326
327	result.DisablePrintableCheck = c.DisablePrintableCheck
328	if c2.DisablePrintableCheckRaw != nil {
329		result.DisablePrintableCheck = c2.DisablePrintableCheck
330	}
331
332	// merge these integers via a MAX operation
333	result.MaxLeaseTTL = c.MaxLeaseTTL
334	if c2.MaxLeaseTTL > result.MaxLeaseTTL {
335		result.MaxLeaseTTL = c2.MaxLeaseTTL
336	}
337
338	result.DefaultLeaseTTL = c.DefaultLeaseTTL
339	if c2.DefaultLeaseTTL > result.DefaultLeaseTTL {
340		result.DefaultLeaseTTL = c2.DefaultLeaseTTL
341	}
342
343	result.DefaultMaxRequestDuration = c.DefaultMaxRequestDuration
344	if c2.DefaultMaxRequestDuration > result.DefaultMaxRequestDuration {
345		result.DefaultMaxRequestDuration = c2.DefaultMaxRequestDuration
346	}
347
348	result.LogLevel = c.LogLevel
349	if c2.LogLevel != "" {
350		result.LogLevel = c2.LogLevel
351	}
352
353	result.LogFormat = c.LogFormat
354	if c2.LogFormat != "" {
355		result.LogFormat = c2.LogFormat
356	}
357
358	result.ClusterName = c.ClusterName
359	if c2.ClusterName != "" {
360		result.ClusterName = c2.ClusterName
361	}
362
363	result.ClusterCipherSuites = c.ClusterCipherSuites
364	if c2.ClusterCipherSuites != "" {
365		result.ClusterCipherSuites = c2.ClusterCipherSuites
366	}
367
368	result.EnableUI = c.EnableUI
369	if c2.EnableUI {
370		result.EnableUI = c2.EnableUI
371	}
372
373	result.EnableRawEndpoint = c.EnableRawEndpoint
374	if c2.EnableRawEndpoint {
375		result.EnableRawEndpoint = c2.EnableRawEndpoint
376	}
377
378	result.APIAddr = c.APIAddr
379	if c2.APIAddr != "" {
380		result.APIAddr = c2.APIAddr
381	}
382
383	result.ClusterAddr = c.ClusterAddr
384	if c2.ClusterAddr != "" {
385		result.ClusterAddr = c2.ClusterAddr
386	}
387
388	// Retain raw value so that it can be assigned to storage objects
389	result.DisableClustering = c.DisableClustering
390	result.DisableClusteringRaw = c.DisableClusteringRaw
391	if c2.DisableClusteringRaw != nil {
392		result.DisableClustering = c2.DisableClustering
393		result.DisableClusteringRaw = c2.DisableClusteringRaw
394	}
395
396	result.PluginDirectory = c.PluginDirectory
397	if c2.PluginDirectory != "" {
398		result.PluginDirectory = c2.PluginDirectory
399	}
400
401	result.PidFile = c.PidFile
402	if c2.PidFile != "" {
403		result.PidFile = c2.PidFile
404	}
405
406	result.DisablePerformanceStandby = c.DisablePerformanceStandby
407	if c2.DisablePerformanceStandby {
408		result.DisablePerformanceStandby = c2.DisablePerformanceStandby
409	}
410
411	result.DisableSealWrap = c.DisableSealWrap
412	if c2.DisableSealWrap {
413		result.DisableSealWrap = c2.DisableSealWrap
414	}
415
416	result.DisableIndexing = c.DisableIndexing
417	if c2.DisableIndexing {
418		result.DisableIndexing = c2.DisableIndexing
419	}
420
421	// Use values from top-level configuration for storage if set
422	if storage := result.Storage; storage != nil {
423		if result.APIAddr != "" {
424			storage.RedirectAddr = result.APIAddr
425		}
426		if result.ClusterAddr != "" {
427			storage.ClusterAddr = result.ClusterAddr
428		}
429		if result.DisableClusteringRaw != nil {
430			storage.DisableClustering = result.DisableClustering
431		}
432	}
433
434	if haStorage := result.HAStorage; haStorage != nil {
435		if result.APIAddr != "" {
436			haStorage.RedirectAddr = result.APIAddr
437		}
438		if result.ClusterAddr != "" {
439			haStorage.ClusterAddr = result.ClusterAddr
440		}
441		if result.DisableClusteringRaw != nil {
442			haStorage.DisableClustering = result.DisableClustering
443		}
444	}
445
446	return result
447}
448
449// LoadConfig loads the configuration at the given path, regardless if
450// its a file or directory.
451func LoadConfig(path string) (*Config, error) {
452	fi, err := os.Stat(path)
453	if err != nil {
454		return nil, err
455	}
456
457	if fi.IsDir() {
458		return LoadConfigDir(path)
459	}
460	return LoadConfigFile(path)
461}
462
463// LoadConfigFile loads the configuration from the given file.
464func LoadConfigFile(path string) (*Config, error) {
465	// Read the file
466	d, err := ioutil.ReadFile(path)
467	if err != nil {
468		return nil, err
469	}
470	return ParseConfig(string(d))
471}
472
473func ParseConfig(d string) (*Config, error) {
474	// Parse!
475	obj, err := hcl.Parse(d)
476	if err != nil {
477		return nil, err
478	}
479
480	// Start building the result
481	var result Config
482	if err := hcl.DecodeObject(&result, obj); err != nil {
483		return nil, err
484	}
485
486	if result.MaxLeaseTTLRaw != nil {
487		if result.MaxLeaseTTL, err = parseutil.ParseDurationSecond(result.MaxLeaseTTLRaw); err != nil {
488			return nil, err
489		}
490	}
491	if result.DefaultLeaseTTLRaw != nil {
492		if result.DefaultLeaseTTL, err = parseutil.ParseDurationSecond(result.DefaultLeaseTTLRaw); err != nil {
493			return nil, err
494		}
495	}
496
497	if result.DefaultMaxRequestDurationRaw != nil {
498		if result.DefaultMaxRequestDuration, err = parseutil.ParseDurationSecond(result.DefaultMaxRequestDurationRaw); err != nil {
499			return nil, err
500		}
501	}
502
503	if result.EnableUIRaw != nil {
504		if result.EnableUI, err = parseutil.ParseBool(result.EnableUIRaw); err != nil {
505			return nil, err
506		}
507	}
508
509	if result.DisableCacheRaw != nil {
510		if result.DisableCache, err = parseutil.ParseBool(result.DisableCacheRaw); err != nil {
511			return nil, err
512		}
513	}
514
515	if result.DisableMlockRaw != nil {
516		if result.DisableMlock, err = parseutil.ParseBool(result.DisableMlockRaw); err != nil {
517			return nil, err
518		}
519	}
520
521	if result.DisablePrintableCheckRaw != nil {
522		if result.DisablePrintableCheck, err = parseutil.ParseBool(result.DisablePrintableCheckRaw); err != nil {
523			return nil, err
524		}
525	}
526
527	if result.EnableRawEndpointRaw != nil {
528		if result.EnableRawEndpoint, err = parseutil.ParseBool(result.EnableRawEndpointRaw); err != nil {
529			return nil, err
530		}
531	}
532
533	if result.DisableClusteringRaw != nil {
534		if result.DisableClustering, err = parseutil.ParseBool(result.DisableClusteringRaw); err != nil {
535			return nil, err
536		}
537	}
538
539	if result.DisablePerformanceStandbyRaw != nil {
540		if result.DisablePerformanceStandby, err = parseutil.ParseBool(result.DisablePerformanceStandbyRaw); err != nil {
541			return nil, err
542		}
543	}
544
545	if result.DisableSealWrapRaw != nil {
546		if result.DisableSealWrap, err = parseutil.ParseBool(result.DisableSealWrapRaw); err != nil {
547			return nil, err
548		}
549	}
550
551	if result.DisableIndexingRaw != nil {
552		if result.DisableIndexing, err = parseutil.ParseBool(result.DisableIndexingRaw); err != nil {
553			return nil, err
554		}
555	}
556
557	list, ok := obj.Node.(*ast.ObjectList)
558	if !ok {
559		return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
560	}
561
562	// Look for storage but still support old backend
563	if o := list.Filter("storage"); len(o.Items) > 0 {
564		if err := ParseStorage(&result, o, "storage"); err != nil {
565			return nil, errwrap.Wrapf("error parsing 'storage': {{err}}", err)
566		}
567	} else {
568		if o := list.Filter("backend"); len(o.Items) > 0 {
569			if err := ParseStorage(&result, o, "backend"); err != nil {
570				return nil, errwrap.Wrapf("error parsing 'backend': {{err}}", err)
571			}
572		}
573	}
574
575	if o := list.Filter("ha_storage"); len(o.Items) > 0 {
576		if err := parseHAStorage(&result, o, "ha_storage"); err != nil {
577			return nil, errwrap.Wrapf("error parsing 'ha_storage': {{err}}", err)
578		}
579	} else {
580		if o := list.Filter("ha_backend"); len(o.Items) > 0 {
581			if err := parseHAStorage(&result, o, "ha_backend"); err != nil {
582				return nil, errwrap.Wrapf("error parsing 'ha_backend': {{err}}", err)
583			}
584		}
585	}
586
587	if o := list.Filter("hsm"); len(o.Items) > 0 {
588		if err := parseSeals(&result, o, "hsm"); err != nil {
589			return nil, errwrap.Wrapf("error parsing 'hsm': {{err}}", err)
590		}
591	}
592
593	if o := list.Filter("seal"); len(o.Items) > 0 {
594		if err := parseSeals(&result, o, "seal"); err != nil {
595			return nil, errwrap.Wrapf("error parsing 'seal': {{err}}", err)
596		}
597	}
598
599	if o := list.Filter("entropy"); len(o.Items) > 0 {
600		if err := parseEntropy(&result, o, "entropy"); err != nil {
601			return nil, errwrap.Wrapf("error parsing 'entropy': {{err}}", err)
602		}
603	}
604
605	if o := list.Filter("listener"); len(o.Items) > 0 {
606		if err := parseListeners(&result, o); err != nil {
607			return nil, errwrap.Wrapf("error parsing 'listener': {{err}}", err)
608		}
609	}
610
611	if o := list.Filter("telemetry"); len(o.Items) > 0 {
612		if err := parseTelemetry(&result, o); err != nil {
613			return nil, errwrap.Wrapf("error parsing 'telemetry': {{err}}", err)
614		}
615	}
616
617	return &result, nil
618}
619
620// LoadConfigDir loads all the configurations in the given directory
621// in alphabetical order.
622func LoadConfigDir(dir string) (*Config, error) {
623	f, err := os.Open(dir)
624	if err != nil {
625		return nil, err
626	}
627	defer f.Close()
628
629	fi, err := f.Stat()
630	if err != nil {
631		return nil, err
632	}
633	if !fi.IsDir() {
634		return nil, fmt.Errorf("configuration path must be a directory: %q", dir)
635	}
636
637	var files []string
638	err = nil
639	for err != io.EOF {
640		var fis []os.FileInfo
641		fis, err = f.Readdir(128)
642		if err != nil && err != io.EOF {
643			return nil, err
644		}
645
646		for _, fi := range fis {
647			// Ignore directories
648			if fi.IsDir() {
649				continue
650			}
651
652			// Only care about files that are valid to load.
653			name := fi.Name()
654			skip := true
655			if strings.HasSuffix(name, ".hcl") {
656				skip = false
657			} else if strings.HasSuffix(name, ".json") {
658				skip = false
659			}
660			if skip || isTemporaryFile(name) {
661				continue
662			}
663
664			path := filepath.Join(dir, name)
665			files = append(files, path)
666		}
667	}
668
669	var result *Config
670	for _, f := range files {
671		config, err := LoadConfigFile(f)
672		if err != nil {
673			return nil, errwrap.Wrapf(fmt.Sprintf("error loading %q: {{err}}", f), err)
674		}
675
676		if result == nil {
677			result = config
678		} else {
679			result = result.Merge(config)
680		}
681	}
682
683	return result, nil
684}
685
686// isTemporaryFile returns true or false depending on whether the
687// provided file name is a temporary file for the following editors:
688// emacs or vim.
689func isTemporaryFile(name string) bool {
690	return strings.HasSuffix(name, "~") || // vim
691		strings.HasPrefix(name, ".#") || // emacs
692		(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
693}
694
695func ParseStorage(result *Config, list *ast.ObjectList, name string) error {
696	if len(list.Items) > 1 {
697		return fmt.Errorf("only one %q block is permitted", name)
698	}
699
700	// Get our item
701	item := list.Items[0]
702
703	key := name
704	if len(item.Keys) > 0 {
705		key = item.Keys[0].Token.Value().(string)
706	}
707
708	var m map[string]string
709	if err := hcl.DecodeObject(&m, item.Val); err != nil {
710		return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
711	}
712
713	// Pull out the redirect address since it's common to all backends
714	var redirectAddr string
715	if v, ok := m["redirect_addr"]; ok {
716		redirectAddr = v
717		delete(m, "redirect_addr")
718	} else if v, ok := m["advertise_addr"]; ok {
719		redirectAddr = v
720		delete(m, "advertise_addr")
721	}
722
723	// Pull out the cluster address since it's common to all backends
724	var clusterAddr string
725	if v, ok := m["cluster_addr"]; ok {
726		clusterAddr = v
727		delete(m, "cluster_addr")
728	}
729
730	var disableClustering bool
731	var err error
732	if v, ok := m["disable_clustering"]; ok {
733		disableClustering, err = strconv.ParseBool(v)
734		if err != nil {
735			return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
736		}
737		delete(m, "disable_clustering")
738	}
739
740	// Override with top-level values if they are set
741	if result.APIAddr != "" {
742		redirectAddr = result.APIAddr
743	}
744
745	if result.ClusterAddr != "" {
746		clusterAddr = result.ClusterAddr
747	}
748
749	if result.DisableClusteringRaw != nil {
750		disableClustering = result.DisableClustering
751	}
752
753	result.Storage = &Storage{
754		RedirectAddr:      redirectAddr,
755		ClusterAddr:       clusterAddr,
756		DisableClustering: disableClustering,
757		Type:              strings.ToLower(key),
758		Config:            m,
759	}
760	return nil
761}
762
763func parseHAStorage(result *Config, list *ast.ObjectList, name string) error {
764	if len(list.Items) > 1 {
765		return fmt.Errorf("only one %q block is permitted", name)
766	}
767
768	// Get our item
769	item := list.Items[0]
770
771	key := name
772	if len(item.Keys) > 0 {
773		key = item.Keys[0].Token.Value().(string)
774	}
775
776	var m map[string]string
777	if err := hcl.DecodeObject(&m, item.Val); err != nil {
778		return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
779	}
780
781	// Pull out the redirect address since it's common to all backends
782	var redirectAddr string
783	if v, ok := m["redirect_addr"]; ok {
784		redirectAddr = v
785		delete(m, "redirect_addr")
786	} else if v, ok := m["advertise_addr"]; ok {
787		redirectAddr = v
788		delete(m, "advertise_addr")
789	}
790
791	// Pull out the cluster address since it's common to all backends
792	var clusterAddr string
793	if v, ok := m["cluster_addr"]; ok {
794		clusterAddr = v
795		delete(m, "cluster_addr")
796	}
797
798	var disableClustering bool
799	var err error
800	if v, ok := m["disable_clustering"]; ok {
801		disableClustering, err = strconv.ParseBool(v)
802		if err != nil {
803			return multierror.Prefix(err, fmt.Sprintf("%s.%s:", name, key))
804		}
805		delete(m, "disable_clustering")
806	}
807
808	// Override with top-level values if they are set
809	if result.APIAddr != "" {
810		redirectAddr = result.APIAddr
811	}
812
813	if result.ClusterAddr != "" {
814		clusterAddr = result.ClusterAddr
815	}
816
817	if result.DisableClusteringRaw != nil {
818		disableClustering = result.DisableClustering
819	}
820
821	result.HAStorage = &Storage{
822		RedirectAddr:      redirectAddr,
823		ClusterAddr:       clusterAddr,
824		DisableClustering: disableClustering,
825		Type:              strings.ToLower(key),
826		Config:            m,
827	}
828	return nil
829}
830
831func parseSeals(result *Config, list *ast.ObjectList, blockName string) error {
832	if len(list.Items) > 2 {
833		return fmt.Errorf("only two or less %q blocks are permitted", blockName)
834	}
835
836	seals := make([]*Seal, 0, len(list.Items))
837	for _, item := range list.Items {
838		key := "seal"
839		if len(item.Keys) > 0 {
840			key = item.Keys[0].Token.Value().(string)
841		}
842
843		var m map[string]string
844		if err := hcl.DecodeObject(&m, item.Val); err != nil {
845			return multierror.Prefix(err, fmt.Sprintf("seal.%s:", key))
846		}
847		var disabled bool
848		var err error
849		if v, ok := m["disabled"]; ok {
850			disabled, err = strconv.ParseBool(v)
851			if err != nil {
852				return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
853			}
854			delete(m, "disabled")
855		}
856		seals = append(seals, &Seal{
857			Type:     strings.ToLower(key),
858			Disabled: disabled,
859			Config:   m,
860		})
861	}
862
863	if len(seals) == 2 &&
864		(seals[0].Disabled && seals[1].Disabled || !seals[0].Disabled && !seals[1].Disabled) {
865		return errors.New("seals: two seals provided but both are disabled or neither are disabled")
866	}
867
868	result.Seals = seals
869
870	return nil
871}
872
873func parseListeners(result *Config, list *ast.ObjectList) error {
874	listeners := make([]*Listener, 0, len(list.Items))
875	for _, item := range list.Items {
876		key := "listener"
877		if len(item.Keys) > 0 {
878			key = item.Keys[0].Token.Value().(string)
879		}
880
881		var m map[string]interface{}
882		if err := hcl.DecodeObject(&m, item.Val); err != nil {
883			return multierror.Prefix(err, fmt.Sprintf("listeners.%s:", key))
884		}
885
886		lnType := strings.ToLower(key)
887
888		listeners = append(listeners, &Listener{
889			Type:   lnType,
890			Config: m,
891		})
892	}
893
894	result.Listeners = listeners
895	return nil
896}
897
898func parseTelemetry(result *Config, list *ast.ObjectList) error {
899	if len(list.Items) > 1 {
900		return fmt.Errorf("only one 'telemetry' block is permitted")
901	}
902
903	// Get our one item
904	item := list.Items[0]
905
906	var t Telemetry
907	if err := hcl.DecodeObject(&t, item.Val); err != nil {
908		return multierror.Prefix(err, "telemetry:")
909	}
910
911	if result.Telemetry == nil {
912		result.Telemetry = &Telemetry{}
913	}
914
915	if err := hcl.DecodeObject(&result.Telemetry, item.Val); err != nil {
916		return multierror.Prefix(err, "telemetry:")
917	}
918
919	if result.Telemetry.PrometheusRetentionTimeRaw != nil {
920		var err error
921		if result.Telemetry.PrometheusRetentionTime, err = parseutil.ParseDurationSecond(result.Telemetry.PrometheusRetentionTimeRaw); err != nil {
922			return err
923		}
924	} else {
925		result.Telemetry.PrometheusRetentionTime = prometheusDefaultRetentionTime
926	}
927
928	return nil
929}
930
931// Sanitized returns a copy of the config with all values that are considered
932// sensitive stripped. It also strips all `*Raw` values that are mainly
933// used for parsing.
934//
935// Specifically, the fields that this method strips are:
936// - Storage.Config
937// - HAStorage.Config
938// - Seals.Config
939// - Telemetry.CirconusAPIToken
940func (c *Config) Sanitized() map[string]interface{} {
941	result := map[string]interface{}{
942		"cache_size":              c.CacheSize,
943		"disable_cache":           c.DisableCache,
944		"disable_mlock":           c.DisableMlock,
945		"disable_printable_check": c.DisablePrintableCheck,
946
947		"enable_ui": c.EnableUI,
948
949		"max_lease_ttl":     c.MaxLeaseTTL,
950		"default_lease_ttl": c.DefaultLeaseTTL,
951
952		"default_max_request_duration": c.DefaultMaxRequestDuration,
953
954		"cluster_name":          c.ClusterName,
955		"cluster_cipher_suites": c.ClusterCipherSuites,
956
957		"plugin_directory": c.PluginDirectory,
958
959		"log_level":  c.LogLevel,
960		"log_format": c.LogFormat,
961
962		"pid_file":             c.PidFile,
963		"raw_storage_endpoint": c.EnableRawEndpoint,
964
965		"api_addr":           c.APIAddr,
966		"cluster_addr":       c.ClusterAddr,
967		"disable_clustering": c.DisableClustering,
968
969		"disable_performance_standby": c.DisablePerformanceStandby,
970
971		"disable_sealwrap": c.DisableSealWrap,
972
973		"disable_indexing": c.DisableIndexing,
974	}
975
976	// Sanitize listeners
977	if len(c.Listeners) != 0 {
978		var sanitizedListeners []interface{}
979		for _, ln := range c.Listeners {
980			cleanLn := map[string]interface{}{
981				"type":   ln.Type,
982				"config": ln.Config,
983			}
984			sanitizedListeners = append(sanitizedListeners, cleanLn)
985		}
986		result["listeners"] = sanitizedListeners
987	}
988
989	// Sanitize storage stanza
990	if c.Storage != nil {
991		sanitizedStorage := map[string]interface{}{
992			"type":               c.Storage.Type,
993			"redirect_addr":      c.Storage.RedirectAddr,
994			"cluster_addr":       c.Storage.ClusterAddr,
995			"disable_clustering": c.Storage.DisableClustering,
996		}
997		result["storage"] = sanitizedStorage
998	}
999
1000	// Sanitize HA storage stanza
1001	if c.HAStorage != nil {
1002		sanitizedHAStorage := map[string]interface{}{
1003			"type":               c.HAStorage.Type,
1004			"redirect_addr":      c.HAStorage.RedirectAddr,
1005			"cluster_addr":       c.HAStorage.ClusterAddr,
1006			"disable_clustering": c.HAStorage.DisableClustering,
1007		}
1008		result["ha_storage"] = sanitizedHAStorage
1009	}
1010
1011	// Sanitize seals stanza
1012	if len(c.Seals) != 0 {
1013		var sanitizedSeals []interface{}
1014		for _, s := range c.Seals {
1015			cleanSeal := map[string]interface{}{
1016				"type":     s.Type,
1017				"disabled": s.Disabled,
1018			}
1019			sanitizedSeals = append(sanitizedSeals, cleanSeal)
1020		}
1021		result["seals"] = sanitizedSeals
1022	}
1023
1024	// Sanitize telemetry stanza
1025	if c.Telemetry != nil {
1026		sanitizedTelemetry := map[string]interface{}{
1027			"statsite_address":                       c.Telemetry.StatsiteAddr,
1028			"statsd_address":                         c.Telemetry.StatsdAddr,
1029			"disable_hostname":                       c.Telemetry.DisableHostname,
1030			"circonus_api_token":                     "",
1031			"circonus_api_app":                       c.Telemetry.CirconusAPIApp,
1032			"circonus_api_url":                       c.Telemetry.CirconusAPIURL,
1033			"circonus_submission_interval":           c.Telemetry.CirconusSubmissionInterval,
1034			"circonus_submission_url":                c.Telemetry.CirconusCheckSubmissionURL,
1035			"circonus_check_id":                      c.Telemetry.CirconusCheckID,
1036			"circonus_check_force_metric_activation": c.Telemetry.CirconusCheckForceMetricActivation,
1037			"circonus_check_instance_id":             c.Telemetry.CirconusCheckInstanceID,
1038			"circonus_check_search_tag":              c.Telemetry.CirconusCheckSearchTag,
1039			"circonus_check_tags":                    c.Telemetry.CirconusCheckTags,
1040			"circonus_check_display_name":            c.Telemetry.CirconusCheckDisplayName,
1041			"circonus_broker_id":                     c.Telemetry.CirconusBrokerID,
1042			"circonus_broker_select_tag":             c.Telemetry.CirconusBrokerSelectTag,
1043			"dogstatsd_addr":                         c.Telemetry.DogStatsDAddr,
1044			"dogstatsd_tags":                         c.Telemetry.DogStatsDTags,
1045			"prometheus_retention_time":              c.Telemetry.PrometheusRetentionTime,
1046			"stackdriver_project_id":                 c.Telemetry.StackdriverProjectID,
1047			"stackdriver_location":                   c.Telemetry.StackdriverLocation,
1048			"stackdriver_namespace":                  c.Telemetry.StackdriverNamespace,
1049		}
1050		result["telemetry"] = sanitizedTelemetry
1051	}
1052
1053	return result
1054}
1055