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