| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package configutil |
| |
| import ( |
| "fmt" |
| "time" |
| |
| "github.com/hashicorp/go-secure-stdlib/parseutil" |
| "github.com/hashicorp/hcl" |
| "github.com/hashicorp/hcl/hcl/ast" |
| "github.com/hashicorp/hcl/hcl/token" |
| ) |
| |
| // SharedConfig contains some shared values |
| type SharedConfig struct { |
| FoundKeys []string `hcl:",decodedFields"` |
| UnusedKeys UnusedKeyMap `hcl:",unusedKeyPositions"` |
| Sections map[string][]token.Pos |
| |
| EntSharedConfig |
| |
| Listeners []*Listener `hcl:"-"` |
| |
| UserLockouts []*UserLockout `hcl:"-"` |
| |
| Seals []*KMS `hcl:"-"` |
| Entropy *Entropy `hcl:"-"` |
| |
| DisableMlock bool `hcl:"-"` |
| DisableMlockRaw interface{} `hcl:"disable_mlock"` |
| |
| Telemetry *Telemetry `hcl:"telemetry"` |
| |
| HCPLinkConf *HCPLinkConfig `hcl:"cloud"` |
| |
| DefaultMaxRequestDuration time.Duration `hcl:"-"` |
| DefaultMaxRequestDurationRaw interface{} `hcl:"default_max_request_duration"` |
| |
| // LogFormat specifies the log format. Valid values are "standard" and |
| // "json". The values are case-insenstive. If no log format is specified, |
| // then standard format will be used. |
| LogFile string `hcl:"log_file"` |
| LogFormat string `hcl:"log_format"` |
| LogLevel string `hcl:"log_level"` |
| LogRotateBytes int `hcl:"log_rotate_bytes"` |
| LogRotateBytesRaw interface{} `hcl:"log_rotate_bytes"` |
| LogRotateDuration string `hcl:"log_rotate_duration"` |
| LogRotateMaxFiles int `hcl:"log_rotate_max_files"` |
| LogRotateMaxFilesRaw interface{} `hcl:"log_rotate_max_files"` |
| |
| PidFile string `hcl:"pid_file"` |
| |
| ClusterName string `hcl:"cluster_name"` |
| |
| AdministrativeNamespacePath string `hcl:"administrative_namespace_path"` |
| } |
| |
| func ParseConfig(d string) (*SharedConfig, error) { |
| // Parse! |
| obj, err := hcl.Parse(d) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Start building the result |
| var result SharedConfig |
| |
| if err := hcl.DecodeObject(&result, obj); err != nil { |
| return nil, err |
| } |
| |
| if result.DefaultMaxRequestDurationRaw != nil { |
| if result.DefaultMaxRequestDuration, err = parseutil.ParseDurationSecond(result.DefaultMaxRequestDurationRaw); err != nil { |
| return nil, err |
| } |
| result.FoundKeys = append(result.FoundKeys, "DefaultMaxRequestDuration") |
| result.DefaultMaxRequestDurationRaw = nil |
| } |
| |
| if result.DisableMlockRaw != nil { |
| if result.DisableMlock, err = parseutil.ParseBool(result.DisableMlockRaw); err != nil { |
| return nil, err |
| } |
| result.FoundKeys = append(result.FoundKeys, "DisableMlock") |
| result.DisableMlockRaw = nil |
| } |
| |
| list, ok := obj.Node.(*ast.ObjectList) |
| if !ok { |
| return nil, fmt.Errorf("error parsing: file doesn't contain a root object") |
| } |
| |
| if o := list.Filter("hsm"); len(o.Items) > 0 { |
| result.found("hsm", "hsm") |
| if err := parseKMS(&result.Seals, o, "hsm", 2); err != nil { |
| return nil, fmt.Errorf("error parsing 'hsm': %w", err) |
| } |
| } |
| |
| if o := list.Filter("seal"); len(o.Items) > 0 { |
| result.found("seal", "Seal") |
| if err := parseKMS(&result.Seals, o, "seal", 3); err != nil { |
| return nil, fmt.Errorf("error parsing 'seal': %w", err) |
| } |
| } |
| |
| if o := list.Filter("kms"); len(o.Items) > 0 { |
| result.found("kms", "Seal") |
| if err := parseKMS(&result.Seals, o, "kms", 3); err != nil { |
| return nil, fmt.Errorf("error parsing 'kms': %w", err) |
| } |
| } |
| |
| if o := list.Filter("entropy"); len(o.Items) > 0 { |
| result.found("entropy", "Entropy") |
| if err := ParseEntropy(&result, o, "entropy"); err != nil { |
| return nil, fmt.Errorf("error parsing 'entropy': %w", err) |
| } |
| } |
| |
| if o := list.Filter("listener"); len(o.Items) > 0 { |
| result.found("listener", "Listener") |
| if err := ParseListeners(&result, o); err != nil { |
| return nil, fmt.Errorf("error parsing 'listener': %w", err) |
| } |
| } |
| |
| if o := list.Filter("user_lockout"); len(o.Items) > 0 { |
| result.found("user_lockout", "UserLockout") |
| if err := ParseUserLockouts(&result, o); err != nil { |
| return nil, fmt.Errorf("error parsing 'user_lockout': %w", err) |
| } |
| } |
| |
| if o := list.Filter("telemetry"); len(o.Items) > 0 { |
| result.found("telemetry", "Telemetry") |
| if err := parseTelemetry(&result, o); err != nil { |
| return nil, fmt.Errorf("error parsing 'telemetry': %w", err) |
| } |
| } |
| |
| if o := list.Filter("cloud"); len(o.Items) > 0 { |
| result.found("cloud", "Cloud") |
| if err := parseCloud(&result, o); err != nil { |
| return nil, fmt.Errorf("error parsing 'cloud': %w", err) |
| } |
| } |
| |
| entConfig := &(result.EntSharedConfig) |
| if err := entConfig.ParseConfig(list); err != nil { |
| return nil, fmt.Errorf("error parsing enterprise config: %w", err) |
| } |
| |
| return &result, nil |
| } |
| |
| // Sanitized returns a copy of the config with all values that are considered |
| // sensitive stripped. It also strips all `*Raw` values that are mainly |
| // used for parsing. |
| // |
| // Specifically, the fields that this method strips are: |
| // - KMS.Config |
| // - Telemetry.CirconusAPIToken |
| func (c *SharedConfig) Sanitized() map[string]interface{} { |
| if c == nil { |
| return nil |
| } |
| |
| result := map[string]interface{}{ |
| "default_max_request_duration": c.DefaultMaxRequestDuration, |
| "disable_mlock": c.DisableMlock, |
| "log_level": c.LogLevel, |
| "log_format": c.LogFormat, |
| "pid_file": c.PidFile, |
| "cluster_name": c.ClusterName, |
| "administrative_namespace_path": c.AdministrativeNamespacePath, |
| } |
| |
| // Optional log related settings |
| if c.LogFile != "" { |
| result["log_file"] = c.LogFile |
| } |
| if c.LogRotateBytes != 0 { |
| result["log_rotate_bytes"] = c.LogRotateBytes |
| } |
| if c.LogRotateDuration != "" { |
| result["log_rotate_duration"] = c.LogRotateDuration |
| } |
| if c.LogRotateMaxFiles != 0 { |
| result["log_rotate_max_files"] = c.LogRotateMaxFiles |
| } |
| |
| // Sanitize listeners |
| if len(c.Listeners) != 0 { |
| var sanitizedListeners []interface{} |
| for _, ln := range c.Listeners { |
| cleanLn := map[string]interface{}{ |
| "type": ln.Type, |
| "config": ln.RawConfig, |
| } |
| sanitizedListeners = append(sanitizedListeners, cleanLn) |
| } |
| result["listeners"] = sanitizedListeners |
| } |
| |
| // Sanitize user lockout stanza |
| if len(c.UserLockouts) != 0 { |
| var sanitizedUserLockouts []interface{} |
| for _, userlockout := range c.UserLockouts { |
| cleanUserLockout := map[string]interface{}{ |
| "type": userlockout.Type, |
| "lockout_threshold": userlockout.LockoutThreshold, |
| "lockout_duration": userlockout.LockoutDuration, |
| "lockout_counter_reset": userlockout.LockoutCounterReset, |
| "disable_lockout": userlockout.DisableLockout, |
| } |
| sanitizedUserLockouts = append(sanitizedUserLockouts, cleanUserLockout) |
| } |
| result["user_lockout_configs"] = sanitizedUserLockouts |
| } |
| |
| // Sanitize seals stanza |
| if len(c.Seals) != 0 { |
| var sanitizedSeals []interface{} |
| for _, s := range c.Seals { |
| cleanSeal := map[string]interface{}{ |
| "type": s.Type, |
| "disabled": s.Disabled, |
| } |
| sanitizedSeals = append(sanitizedSeals, cleanSeal) |
| } |
| result["seals"] = sanitizedSeals |
| } |
| |
| // Sanitize telemetry stanza |
| if c.Telemetry != nil { |
| sanitizedTelemetry := map[string]interface{}{ |
| "statsite_address": c.Telemetry.StatsiteAddr, |
| "statsd_address": c.Telemetry.StatsdAddr, |
| "disable_hostname": c.Telemetry.DisableHostname, |
| "metrics_prefix": c.Telemetry.MetricsPrefix, |
| "usage_gauge_period": c.Telemetry.UsageGaugePeriod, |
| "maximum_gauge_cardinality": c.Telemetry.MaximumGaugeCardinality, |
| "circonus_api_token": "", |
| "circonus_api_app": c.Telemetry.CirconusAPIApp, |
| "circonus_api_url": c.Telemetry.CirconusAPIURL, |
| "circonus_submission_interval": c.Telemetry.CirconusSubmissionInterval, |
| "circonus_submission_url": c.Telemetry.CirconusCheckSubmissionURL, |
| "circonus_check_id": c.Telemetry.CirconusCheckID, |
| "circonus_check_force_metric_activation": c.Telemetry.CirconusCheckForceMetricActivation, |
| "circonus_check_instance_id": c.Telemetry.CirconusCheckInstanceID, |
| "circonus_check_search_tag": c.Telemetry.CirconusCheckSearchTag, |
| "circonus_check_tags": c.Telemetry.CirconusCheckTags, |
| "circonus_check_display_name": c.Telemetry.CirconusCheckDisplayName, |
| "circonus_broker_id": c.Telemetry.CirconusBrokerID, |
| "circonus_broker_select_tag": c.Telemetry.CirconusBrokerSelectTag, |
| "dogstatsd_addr": c.Telemetry.DogStatsDAddr, |
| "dogstatsd_tags": c.Telemetry.DogStatsDTags, |
| "prometheus_retention_time": c.Telemetry.PrometheusRetentionTime, |
| "stackdriver_project_id": c.Telemetry.StackdriverProjectID, |
| "stackdriver_location": c.Telemetry.StackdriverLocation, |
| "stackdriver_namespace": c.Telemetry.StackdriverNamespace, |
| "stackdriver_debug_logs": c.Telemetry.StackdriverDebugLogs, |
| "lease_metrics_epsilon": c.Telemetry.LeaseMetricsEpsilon, |
| "num_lease_metrics_buckets": c.Telemetry.NumLeaseMetricsTimeBuckets, |
| "add_lease_metrics_namespace_labels": c.Telemetry.LeaseMetricsNameSpaceLabels, |
| } |
| result["telemetry"] = sanitizedTelemetry |
| } |
| |
| return result |
| } |
| |
| func (c *SharedConfig) found(s, k string) { |
| delete(c.UnusedKeys, s) |
| c.FoundKeys = append(c.FoundKeys, k) |
| } |