package config

import (
	"fmt"
	"net"
	"reflect"
	"strings"
	"time"

	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/lib"
	"github.com/hashicorp/consul/tlsutil"
	"github.com/hashicorp/consul/types"
	"golang.org/x/time/rate"
)

type RuntimeSOAConfig struct {
	Refresh uint32 // 3600 by default
	Retry   uint32 // 600
	Expire  uint32 // 86400
	Minttl  uint32 // 0,
}

// RuntimeConfig specifies the configuration the consul agent actually
// uses. Is is derived from one or more Config structures which can come
// from files, flags and/or environment variables.
type RuntimeConfig struct {
	// non-user configurable values
	AEInterval time.Duration

	CheckDeregisterIntervalMin time.Duration
	CheckReapInterval          time.Duration
	SegmentLimit               int
	SegmentNameLimit           int
	SyncCoordinateRateTarget   float64
	SyncCoordinateIntervalMin  time.Duration
	Revision                   string
	Version                    string
	VersionPrerelease          string

	// consul config
	ConsulCoordinateUpdateMaxBatches int
	ConsulCoordinateUpdateBatchSize  int
	ConsulCoordinateUpdatePeriod     time.Duration
	ConsulRaftElectionTimeout        time.Duration
	ConsulRaftHeartbeatTimeout       time.Duration
	ConsulRaftLeaderLeaseTimeout     time.Duration
	ConsulServerHealthInterval       time.Duration

	// ACLDisabledTTL is used by agents to determine how long they will
	// wait to check again with the servers if they discover ACLs are not
	// enabled. (not user configurable)
	//
	// hcl: acl.disabled_ttl = "duration"
	ACLDisabledTTL time.Duration

	// ACLsEnabled is used to determine whether ACLs should be enabled
	//
	// hcl: acl.enabled = boolean
	ACLsEnabled bool

	// ACLAgentMasterToken is a special token that has full read and write
	// privileges for this agent, and can be used to call agent endpoints
	// when no servers are available.
	//
	// hcl: acl.tokens.agent_master = string
	ACLAgentMasterToken string

	// ACLAgentToken is the default token used to make requests for the agent
	// itself, such as for registering itself with the catalog. If not
	// configured, the 'acl_token' will be used.
	//
	// hcl: acl.tokens.agent = string
	ACLAgentToken string

	// ACLDatacenter is the central datacenter that holds authoritative
	// ACL records. This must be the same for the entire cluster.
	// If this is not set, ACLs are not enabled. Off by default.
	//
	// hcl: acl_datacenter = string
	ACLDatacenter string

	// ACLDefaultPolicy is used to control the ACL interaction when
	// there is no defined policy. This can be "allow" which means
	// ACLs are used to black-list, or "deny" which means ACLs are
	// white-lists.
	//
	// hcl: acl.default_policy = ("allow"|"deny")
	ACLDefaultPolicy string

	// ACLDownPolicy is used to control the ACL interaction when we cannot
	// reach the ACLDatacenter and the token is not in the cache.
	// There are the following modes:
	//   * allow - Allow all requests
	//   * deny - Deny all requests
	//   * extend-cache - Ignore the cache expiration, and allow cached
	//                    ACL's to be used to service requests. This
	//                    is the default. If the ACL is not in the cache,
	//                    this acts like deny.
	//   * async-cache - Same behavior as extend-cache, but perform ACL
	//                   Lookups asynchronously when cache TTL is expired.
	//
	// hcl: acl.down_policy = ("allow"|"deny"|"extend-cache"|"async-cache")
	ACLDownPolicy string

	// DEPRECATED (ACL-Legacy-Compat)
	// ACLEnforceVersion8 is used to gate a set of ACL policy features that
	// are opt-in prior to Consul 0.8 and opt-out in Consul 0.8 and later.
	//
	// hcl: acl_enforce_version_8 = (true|false)
	ACLEnforceVersion8 bool

	// ACLEnableKeyListPolicy is used to opt-in to the "list" policy added to
	// KV ACLs in Consul 1.0.
	//
	// See https://www.consul.io/docs/guides/acl.html#list-policy-for-keys for
	// more details.
	//
	// hcl: acl.enable_key_list_policy = (true|false)
	ACLEnableKeyListPolicy bool

	// ACLMasterToken is used to bootstrap the ACL system. It should be specified
	// on the servers in the ACLDatacenter. When the leader comes online, it ensures
	// that the Master token is available. This provides the initial token.
	//
	// hcl: acl.tokens.master = string
	ACLMasterToken string

	// ACLReplicationToken is used to replicate data locally from the
	// PrimaryDatacenter. Replication is only available on servers in
	// datacenters other than the PrimaryDatacenter
	//
	// DEPRECATED (ACL-Legacy-Compat): Setting this to a non-empty value
	// also enables legacy ACL replication if ACLs are enabled and in legacy mode.
	//
	// hcl: acl.tokens.replication = string
	ACLReplicationToken string

	// ACLtokenReplication is used to indicate that both tokens and policies
	// should be replicated instead of just policies
	//
	// hcl: acl.token_replication = boolean
	ACLTokenReplication bool

	// ACLTokenTTL is used to control the time-to-live of cached ACL tokens. This has
	// a major impact on performance. By default, it is set to 30 seconds.
	//
	// hcl: acl.policy_ttl = "duration"
	ACLTokenTTL time.Duration

	// ACLPolicyTTL is used to control the time-to-live of cached ACL policies. This has
	// a major impact on performance. By default, it is set to 30 seconds.
	//
	// hcl: acl.token_ttl = "duration"
	ACLPolicyTTL time.Duration

	// ACLRoleTTL is used to control the time-to-live of cached ACL roles. This has
	// a major impact on performance. By default, it is set to 30 seconds.
	//
	// hcl: acl.role_ttl = "duration"
	ACLRoleTTL time.Duration

	// ACLToken is the default token used to make requests if a per-request
	// token is not provided. If not configured the 'anonymous' token is used.
	//
	// hcl: acl.tokens.default = string
	ACLToken string

	// ACLEnableTokenPersistence determines whether or not tokens set via the agent HTTP API
	// should be persisted to disk and reloaded when an agent restarts.
	ACLEnableTokenPersistence bool

	// AutopilotCleanupDeadServers enables the automatic cleanup of dead servers when new ones
	// are added to the peer list. Defaults to true.
	//
	// hcl: autopilot { cleanup_dead_servers = (true|false) }
	AutopilotCleanupDeadServers bool

	// AutopilotDisableUpgradeMigration will disable Autopilot's upgrade migration
	// strategy of waiting until enough newer-versioned servers have been added to the
	// cluster before promoting them to voters. (Enterprise-only)
	//
	// hcl: autopilot { disable_upgrade_migration = (true|false)
	AutopilotDisableUpgradeMigration bool

	// AutopilotLastContactThreshold is the limit on the amount of time a server can go
	// without leader contact before being considered unhealthy.
	//
	// hcl: autopilot { last_contact_threshold = "duration" }
	AutopilotLastContactThreshold time.Duration

	// AutopilotMaxTrailingLogs is the amount of entries in the Raft Log that a server can
	// be behind before being considered unhealthy. The value must be positive.
	//
	// hcl: autopilot { max_trailing_logs = int }
	AutopilotMaxTrailingLogs int

	// AutopilotMinQuorum sets the minimum number of servers required in a cluster
	// before autopilot can prune dead servers.
	//
	//hcl: autopilot { min_quorum = int }
	AutopilotMinQuorum uint

	// AutopilotRedundancyZoneTag is the Meta tag to use for separating servers
	// into zones for redundancy. If left blank, this feature will be disabled.
	// (Enterprise-only)
	//
	// hcl: autopilot { redundancy_zone_tag = string }
	AutopilotRedundancyZoneTag string

	// AutopilotServerStabilizationTime is the minimum amount of time a server must be
	// in a stable, healthy state before it can be added to the cluster. Only
	// applicable with Raft protocol version 3 or higher.
	//
	// hcl: autopilot { server_stabilization_time = "duration" }
	AutopilotServerStabilizationTime time.Duration

	// AutopilotUpgradeVersionTag is the node tag to use for version info when
	// performing upgrade migrations. If left blank, the Consul version will be used.
	//
	// (Enterprise-only)
	//
	// hcl: autopilot { upgrade_version_tag = string }
	AutopilotUpgradeVersionTag string

	// DNSAllowStale is used to enable lookups with stale
	// data. This gives horizontal read scalability since
	// any Consul server can service the query instead of
	// only the leader.
	//
	// hcl: dns_config { allow_stale = (true|false) }
	DNSAllowStale bool

	// DNSARecordLimit is used to limit the maximum number of DNS Resource
	// Records returned in the ANSWER section of a DNS response for A or AAAA
	// records for both UDP and TCP queries.
	//
	// This is not normally useful and will be limited based on the querying
	// protocol, however systems that implemented §6 Rule 9 in RFC3484
	// may want to set this to `1` in order to subvert §6 Rule 9 and
	// re-obtain the effect of randomized resource records (i.e. each
	// answer contains only one IP, but the IP changes every request).
	// RFC3484 sorts answers in a deterministic order, which defeats the
	// purpose of randomized DNS responses.  This RFC has been obsoleted
	// by RFC6724 and restores the desired behavior of randomized
	// responses, however a large number of Linux hosts using glibc(3)
	// implemented §6 Rule 9 and may need this option (e.g. CentOS 5-6,
	// Debian Squeeze, etc).
	//
	// hcl: dns_config { a_record_limit = int }
	DNSARecordLimit int

	// DNSDisableCompression is used to control whether DNS responses are
	// compressed. In Consul 0.7 this was turned on by default and this
	// config was added as an opt-out.
	//
	// hcl: dns_config { disable_compression = (true|false) }
	DNSDisableCompression bool

	// DNSDomain is the DNS domain for the records. Should end with a dot.
	// Defaults to "consul."
	//
	// hcl: domain = string
	// flag: -domain string
	DNSDomain string

	// DNSAltDomain can be set to support resolution on an additional
	// consul domain. Should end with a dot.
	// If left blank, only the primary domain will be used.
	//
	// hcl: alt_domain = string
	// flag: -alt-domain string
	DNSAltDomain string

	// DNSEnableTruncate is used to enable setting the truncate
	// flag for UDP DNS queries.  This allows unmodified
	// clients to re-query the consul server using TCP
	// when the total number of records exceeds the number
	// returned by default for UDP.
	//
	// hcl: dns_config { enable_truncate = (true|false) }
	DNSEnableTruncate bool

	// DNSMaxStale is used to bound how stale of a result is
	// accepted for a DNS lookup. This can be used with
	// AllowStale to limit how old of a value is served up.
	// If the stale result exceeds this, another non-stale
	// stale read is performed.
	//
	// hcl: dns_config { max_stale = "duration" }
	DNSMaxStale time.Duration

	// DNSNodeTTL provides the TTL value for a node query.
	//
	// hcl: dns_config { node_ttl = "duration" }
	DNSNodeTTL time.Duration

	// DNSOnlyPassing is used to determine whether to filter nodes
	// whose health checks are in any non-passing state. By
	// default, only nodes in a critical state are excluded.
	//
	// hcl: dns_config { only_passing = "duration" }
	DNSOnlyPassing bool

	// DNSRecursorTimeout specifies the timeout in seconds
	// for Consul's internal dns client used for recursion.
	// This value is used for the connection, read and write timeout.
	//
	// hcl: dns_config { recursor_timeout = "duration" }
	DNSRecursorTimeout time.Duration

	// DNSServiceTTL provides the TTL value for a service
	// query for given service. The "*" wildcard can be used
	// to set a default for all services.
	//
	// hcl: dns_config { service_ttl = map[string]"duration" }
	DNSServiceTTL map[string]time.Duration

	// DNSUDPAnswerLimit is used to limit the maximum number of DNS Resource
	// Records returned in the ANSWER section of a DNS response for UDP
	// responses without EDNS support (limited to 512 bytes).
	// This parameter is deprecated, if you want to limit the number of
	// records returned by A or AAAA questions, please use DNSARecordLimit
	// instead.
	//
	// hcl: dns_config { udp_answer_limit = int }
	DNSUDPAnswerLimit int

	// DNSNodeMetaTXT controls whether DNS queries will synthesize
	// TXT records for the node metadata and add them when not specifically
	// request (query type = TXT). If unset this will default to true
	DNSNodeMetaTXT bool

	// DNSRecursors can be set to allow the DNS servers to recursively
	// resolve non-consul domains.
	//
	// hcl: recursors = []string
	// flag: -recursor string [-recursor string]
	DNSRecursors []string

	// DNSUseCache whether or not to use cache for dns queries
	//
	// hcl: dns_config { use_cache = (true|false) }
	DNSUseCache bool

	// DNSUseCache whether or not to use cache for dns queries
	//
	// hcl: dns_config { cache_max_age = "duration" }
	DNSCacheMaxAge time.Duration

	// HTTPUseCache whether or not to use cache for http queries. Defaults
	// to true.
	//
	// hcl: http_config { use_cache = (true|false) }
	HTTPUseCache bool

	// HTTPBlockEndpoints is a list of endpoint prefixes to block in the
	// HTTP API. Any requests to these will get a 403 response.
	//
	// hcl: http_config { block_endpoints = []string }
	HTTPBlockEndpoints []string

	// AllowWriteHTTPFrom restricts the agent write endpoints to the given
	// networks. Any request to a protected endpoint that is not mactched
	// by one of these networks will get a 403 response.
	// An empty slice means no restriction.
	//
	// hcl: http_config { allow_write_http_from = []string }
	AllowWriteHTTPFrom []*net.IPNet

	// HTTPResponseHeaders are used to add HTTP header response fields to the HTTP API responses.
	//
	// hcl: http_config { response_headers = map[string]string }
	HTTPResponseHeaders map[string]string

	// Embed Telemetry Config
	Telemetry lib.TelemetryConfig

	// Datacenter is the datacenter this node is in. Defaults to "dc1".
	//
	// Datacenter is exposed via /v1/agent/self from here and
	// used in lots of places like CLI commands. Treat this as an interface
	// that must be stable.
	//
	// hcl: datacenter = string
	// flag: -datacenter string
	Datacenter string

	// Defines the maximum stale value for discovery path. Defaults to "0s".
	// Discovery paths are /v1/heath/ paths
	//
	// If not set to 0, it will try to perform stale read and perform only a
	// consistent read whenever the value is too old.
	// hcl: discovery_max_stale = "duration"
	DiscoveryMaxStale time.Duration

	// Node name is the name we use to advertise. Defaults to hostname.
	//
	// NodeName is exposed via /v1/agent/self from here and
	// used in lots of places like CLI commands. Treat this as an interface
	// that must be stable.
	//
	// hcl: node_name = string
	// flag: -node string
	NodeName string

	// AdvertiseAddrLAN is the address we use for advertising our Serf, and
	// Consul RPC IP. The address can be specified as an ip address or as a
	// go-sockaddr template which resolves to a single ip address. If not
	// specified, the bind address is used.
	//
	// hcl: advertise_addr = string
	AdvertiseAddrLAN *net.IPAddr

	// AdvertiseAddrWAN is the address we use for advertising our Serf, and
	// Consul RPC IP. The address can be specified as an ip address or as a
	// go-sockaddr template which resolves to a single ip address. If not
	// specified, the bind address is used.
	//
	// hcl: advertise_addr_wan = string
	AdvertiseAddrWAN *net.IPAddr

	// BindAddr is used to control the address we bind to.
	// If not specified, the first private IP we find is used.
	// This controls the address we use for cluster facing
	// services (Gossip, Server RPC)
	//
	// The value can be either an ip address or a go-sockaddr
	// template which resolves to a single ip address.
	//
	// hcl: bind_addr = string
	// flag: -bind string
	BindAddr *net.IPAddr

	// Bootstrap is used to bring up the first Consul server, and
	// permits that node to elect itself leader
	//
	// hcl: bootstrap = (true|false)
	// flag: -bootstrap
	Bootstrap bool

	// BootstrapExpect tries to automatically bootstrap the Consul cluster, by
	// having servers wait to bootstrap until enough servers join, and then
	// performing the bootstrap process automatically. They will disable their
	// automatic bootstrap process if they detect any servers that are part of
	// an existing cluster, so it's safe to leave this set to a non-zero value.
	//
	// hcl: bootstrap_expect = int
	// flag: -bootstrap-expect=int
	BootstrapExpect int

	// CAFile is a path to a certificate authority file. This is used with
	// VerifyIncoming or VerifyOutgoing to verify the TLS connection.
	//
	// hcl: ca_file = string
	CAFile string

	// CAPath is a path to a directory of certificate authority files. This is
	// used with VerifyIncoming or VerifyOutgoing to verify the TLS connection.
	//
	// hcl: ca_path = string
	CAPath string

	// CertFile is used to provide a TLS certificate that is used for serving
	// TLS connections. Must be provided to serve TLS connections.
	//
	// hcl: cert_file = string
	CertFile string

	// CheckUpdateInterval controls the interval on which the output of a health check
	// is updated if there is no change to the state. For example, a check in a steady
	// state may run every 5 second generating a unique output (timestamp, etc), forcing
	// constant writes. This allows Consul to defer the write for some period of time,
	// reducing the write pressure when the state is steady.
	//
	// See also: DiscardCheckOutput
	//
	// hcl: check_update_interval = "duration"
	CheckUpdateInterval time.Duration

	// Maximum size for the output of a healtcheck
	// hcl check_output_max_size int
	// flag: -check_output_max_size int
	CheckOutputMaxSize int

	// Checks contains the provided check definitions.
	//
	// hcl: checks = [
	//   {
	//     id = string
	//     name = string
	//     notes = string
	//     service_id = string
	//     token = string
	//     status = string
	//     script = string
	//     args = string
	//     http = string
	//     header = map[string][]string
	//     method = string
	//     tcp = string
	//     interval = string
	//     docker_container_id = string
	//     shell = string
	//     tls_skip_verify = (true|false)
	//     timeout = "duration"
	//     ttl = "duration"
	//     deregister_critical_service_after = "duration"
	//   },
	//   ...
	// ]
	Checks []*structs.CheckDefinition

	// ClientAddrs contains the list of ip addresses the DNS, HTTP and HTTPS
	// endpoints will bind to if the endpoints are enabled (ports > 0) and the
	// addresses are not overwritten.
	//
	// The ip addresses must be provided as a space separated list of ip
	// addresses and go-sockaddr templates.
	//
	// Client addresses cannot contain UNIX socket addresses since a socket
	// cannot be shared across multiple endpoints (no ports). To use UNIX
	// sockets configure it in 'addresses'.
	//
	// hcl: client_addr = string
	// flag: -client string
	ClientAddrs []*net.IPAddr

	// ConfigEntryBootstrap contains a list of ConfigEntries to ensure are created
	// If entries of the same Kind/Name exist already these will not update them.
	ConfigEntryBootstrap []structs.ConfigEntry

	// AutoEncryptTLS requires the client to acquire TLS certificates from
	// servers.
	AutoEncryptTLS bool

	// Additional DNS SAN entries that clients request during auto_encrypt
	// flow for their certificates.
	AutoEncryptDNSSAN []string

	// Additional IP SAN entries that clients request during auto_encrypt
	// flow for their certificates.
	AutoEncryptIPSAN []net.IP

	// AutoEncryptAllowTLS enables the server to respond to
	// AutoEncrypt.Sign requests.
	AutoEncryptAllowTLS bool

	// ConnectEnabled opts the agent into connect. It should be set on all clients
	// and servers in a cluster for correct connect operation.
	ConnectEnabled bool

	// ConnectSidecarMinPort is the inclusive start of the range of ports
	// allocated to the agent for asigning to sidecar services where no port is
	// specified.
	ConnectSidecarMinPort int

	// ConnectSidecarMaxPort is the inclusive end of the range of ports
	// allocated to the agent for asigning to sidecar services where no port is
	// specified
	ConnectSidecarMaxPort int

	// ExposeMinPort is the inclusive start of the range of ports
	// allocated to the agent for exposing checks through a proxy
	ExposeMinPort int

	// ExposeMinPort is the inclusive start of the range of ports
	// allocated to the agent for exposing checks through a proxy
	ExposeMaxPort int

	// ConnectCAProvider is the type of CA provider to use with Connect.
	ConnectCAProvider string

	// ConnectCAConfig is the config to use for the CA provider.
	ConnectCAConfig map[string]interface{}

	// ConnectTestCALeafRootChangeSpread is used to control how long the CA leaf
	// cache with spread CSRs over when a root change occurs. For now we don't
	// expose this in public config intentionally but could later with a rename.
	// We only set this from during tests to effectively make CA rotation tests
	// deterministic again.
	ConnectTestCALeafRootChangeSpread time.Duration

	// DNSAddrs contains the list of TCP and UDP addresses the DNS server will
	// bind to. If the DNS endpoint is disabled (ports.dns <= 0) the list is
	// empty.
	//
	// The ip addresses are taken from 'addresses.dns' which should contain a
	// space separated list of ip addresses and/or go-sockaddr templates.
	//
	// If 'addresses.dns' was not provided the 'client_addr' addresses are
	// used.
	//
	// The DNS server cannot be bound to UNIX sockets.
	//
	// hcl: client_addr = string addresses { dns = string } ports { dns = int }
	DNSAddrs []net.Addr

	// DNSPort is the port the DNS server listens on. The default is 8600.
	// Setting this to a value <= 0 disables the endpoint.
	//
	// hcl: ports { dns = int }
	// flags: -dns-port int
	DNSPort int

	// DNSSOA is the settings applied for DNS SOA
	// hcl: soa {}
	DNSSOA RuntimeSOAConfig

	// DataDir is the path to the directory where the local state is stored.
	//
	// hcl: data_dir = string
	// flag: -data-dir string
	DataDir string

	// DefaultQueryTime is the amount of time a blocking query will wait before
	// Consul will force a response. This value can be overridden by the 'wait'
	// query parameter.
	//
	// hcl: default_query_time = "duration"
	// flag: -default-query-time string
	DefaultQueryTime time.Duration

	// DevMode enables a fast-path mode of operation to bring up an in-memory
	// server with minimal configuration. Useful for developing Consul.
	//
	// flag: -dev
	DevMode bool

	// DisableAnonymousSignature is used to turn off the anonymous signature
	// send with the update check. This is used to deduplicate messages.
	//
	// hcl: disable_anonymous_signature = (true|false)
	DisableAnonymousSignature bool

	// DisableCoordinates controls features related to network coordinates.
	//
	// hcl: disable_coordinates = (true|false)
	DisableCoordinates bool

	// DisableHostNodeID will prevent Consul from using information from the
	// host to generate a node ID, and will cause Consul to generate a
	// random ID instead.
	//
	// hcl: disable_host_node_id = (true|false)
	// flag: -disable-host-node-id
	DisableHostNodeID bool

	// DisableHTTPUnprintableCharFilter will bypass the filter preventing HTTP
	// URLs from containing unprintable chars. This filter was added in 1.0.3 as a
	// response to a vulnerability report. Disabling this is never recommended in
	// general however some users who have keys written in older versions of
	// Consul may use this to temporarily disable the filter such that they can
	// delete those keys again! We do not recommend leaving it disabled long term.
	//
	// hcl: disable_http_unprintable_char_filter
	DisableHTTPUnprintableCharFilter bool

	// DisableKeyringFile disables writing the keyring to a file.
	//
	// hcl: disable_keyring_file = (true|false)
	// flag: -disable-keyring-file
	DisableKeyringFile bool

	// DisableRemoteExec is used to turn off the remote execution
	// feature. This is for security to prevent unknown scripts from running.
	//
	// hcl: disable_remote_exec = (true|false)
	DisableRemoteExec bool

	// DisableUpdateCheck is used to turn off the automatic update and
	// security bulletin checking.
	//
	// hcl: disable_update_check = (true|false)
	DisableUpdateCheck bool

	// DiscardCheckOutput is used to turn off storing and comparing the
	// output of health checks. This reduces the write rate on the server
	// for checks with highly volatile output. (reloadable)
	//
	// See also: CheckUpdateInterval
	//
	// hcl: discard_check_output = (true|false)
	DiscardCheckOutput bool

	// EnableAgentTLSForChecks is used to apply the agent's TLS settings in
	// order to configure the HTTP client used for health checks. Enabling
	// this allows HTTP checks to present a client certificate and verify
	// the server using the same TLS configuration as the agent (CA, cert,
	// and key).
	EnableAgentTLSForChecks bool

	// EnableCentralServiceConfig controls whether the agent should incorporate
	// centralized config such as service-defaults into local service registrations.
	//
	// hcl: enable_central_service_config = (true|false)
	EnableCentralServiceConfig bool

	// EnableDebug is used to enable various debugging features.
	//
	// hcl: enable_debug = (true|false)
	EnableDebug bool

	// EnableLocalScriptChecks controls whether health checks declared from the local
	// config file which execute scripts are enabled. This includes regular script
	// checks and Docker checks.
	//
	// hcl: (enable_script_checks|enable_local_script_checks) = (true|false)
	// flag: -enable-script-checks, -enable-local-script-checks
	EnableLocalScriptChecks bool

	// EnableRemoeScriptChecks controls whether health checks declared from the http API
	// which execute scripts are enabled. This includes regular script checks and Docker
	// checks.
	//
	// hcl: enable_script_checks = (true|false)
	// flag: -enable-script-checks
	EnableRemoteScriptChecks bool

	// EnableSyslog is used to also tee all the logs over to syslog. Only supported
	// on linux and OSX. Other platforms will generate an error.
	//
	// hcl: enable_syslog = (true|false)
	// flag: -syslog
	EnableSyslog bool

	// EnableUI enables the statically-compiled assets for the Consul web UI and
	// serves them at the default /ui/ endpoint automatically.
	//
	// hcl: enable_ui = (true|false)
	// flag: -ui
	EnableUI bool

	// EncryptKey contains the encryption key to use for the Serf communication.
	//
	// hcl: encrypt = string
	// flag: -encrypt string
	EncryptKey string

	// EncryptVerifyIncoming enforces incoming gossip encryption and can be
	// used to upshift to encrypted gossip on a running cluster.
	//
	// hcl: encrypt_verify_incoming = (true|false)
	EncryptVerifyIncoming bool

	// EncryptVerifyOutgoing enforces outgoing gossip encryption and can be
	// used to upshift to encrypted gossip on a running cluster.
	//
	// hcl: encrypt_verify_outgoing = (true|false)
	EncryptVerifyOutgoing bool

	// GRPCPort is the port the gRPC server listens on. Currently this only
	// exposes the xDS and ext_authz APIs for Envoy and it is disabled by default.
	//
	// hcl: ports { grpc = int }
	// flags: -grpc-port int
	GRPCPort int

	// GRPCAddrs contains the list of TCP addresses and UNIX sockets the gRPC
	// server will bind to. If the gRPC endpoint is disabled (ports.grpc <= 0)
	// the list is empty.
	//
	// The addresses are taken from 'addresses.grpc' which should contain a
	// space separated list of ip addresses, UNIX socket paths and/or
	// go-sockaddr templates. UNIX socket paths must be written as
	// 'unix://<full path>', e.g. 'unix:///var/run/consul-grpc.sock'.
	//
	// If 'addresses.grpc' was not provided the 'client_addr' addresses are
	// used.
	//
	// hcl: client_addr = string addresses { grpc = string } ports { grpc = int }
	GRPCAddrs []net.Addr

	// HTTPAddrs contains the list of TCP addresses and UNIX sockets the HTTP
	// server will bind to. If the HTTP endpoint is disabled (ports.http <= 0)
	// the list is empty.
	//
	// The addresses are taken from 'addresses.http' which should contain a
	// space separated list of ip addresses, UNIX socket paths and/or
	// go-sockaddr templates. UNIX socket paths must be written as
	// 'unix://<full path>', e.g. 'unix:///var/run/consul-http.sock'.
	//
	// If 'addresses.http' was not provided the 'client_addr' addresses are
	// used.
	//
	// hcl: client_addr = string addresses { http = string } ports { http = int }
	HTTPAddrs []net.Addr

	// HTTPPort is the port the HTTP server listens on. The default is 8500.
	// Setting this to a value <= 0 disables the endpoint.
	//
	// hcl: ports { http = int }
	// flags: -http-port int
	HTTPPort int

	// HTTPSAddrs contains the list of TCP addresses and UNIX sockets the HTTPS
	// server will bind to. If the HTTPS endpoint is disabled (ports.https <=
	// 0) the list is empty.
	//
	// The addresses are taken from 'addresses.https' which should contain a
	// space separated list of ip addresses, UNIX socket paths and/or
	// go-sockaddr templates. UNIX socket paths must be written as
	// 'unix://<full path>', e.g. 'unix:///var/run/consul-https.sock'.
	//
	// If 'addresses.https' was not provided the 'client_addr' addresses are
	// used.
	//
	// hcl: client_addr = string addresses { https = string } ports { https = int }
	HTTPSAddrs []net.Addr

	// HTTPMaxConnsPerClient limits the number of concurrent TCP connections the
	// HTTP(S) server will accept from any single source IP address.
	//
	// hcl: limits{ http_max_conns_per_client = 200 }
	HTTPMaxConnsPerClient int

	// HTTPSHandshakeTimeout is the time allowed for HTTPS client to complete the
	// TLS handshake and send first bytes of the request.
	//
	// hcl: limits{ https_handshake_timeout = "5s" }
	HTTPSHandshakeTimeout time.Duration

	// HTTPSPort is the port the HTTP server listens on. The default is -1.
	// Setting this to a value <= 0 disables the endpoint.
	//
	// hcl: ports { https = int }
	// flags: -https-port int
	HTTPSPort int

	// KeyFile is used to provide a TLS key that is used for serving TLS
	// connections. Must be provided to serve TLS connections.
	//
	// hcl: key_file = string
	KeyFile string

	// KVMaxValueSize controls the max allowed value size. If not set defaults
	// to raft's suggested max value size.
	//
	// hcl: limits { kv_max_value_size = uint64 }
	KVMaxValueSize uint64

	// LeaveDrainTime is used to wait after a server has left the LAN Serf
	// pool for RPCs to drain and new requests to be sent to other servers.
	//
	// hcl: performance { leave_drain_time = "duration" }
	LeaveDrainTime time.Duration

	// LeaveOnTerm controls if Serf does a graceful leave when receiving
	// the TERM signal. Defaults true on clients, false on servers. (reloadable)
	//
	// hcl: leave_on_terminate = (true|false)
	LeaveOnTerm bool

	// LogLevel is the level of the logs to write. Defaults to "INFO".
	//
	// hcl: log_level = string
	LogLevel string

	// LogJSON controls whether to output logs as structured JSON. Defaults to false.
	//
	// hcl: log_json = (true|false)
	// flag: -log-json
	LogJSON bool

	// LogFile is the path to the file where the logs get written to. Defaults to empty string.
	//
	// hcl: log_file = string
	// flags: -log-file string
	LogFile string

	// LogRotateDuration is the time configured to rotate logs based on time
	//
	// hcl: log_rotate_duration = string
	// flags: -log-rotate-duration string
	LogRotateDuration time.Duration

	// LogRotateBytes is the time configured to rotate logs based on bytes written
	//
	// hcl: log_rotate_bytes = int
	// flags: -log-rotate-bytes int
	LogRotateBytes int

	// LogRotateMaxFiles is the maximum number of log file archives to keep
	//
	// hcl: log_rotate_max_files = int
	// flags: -log-rotate-max-files int
	LogRotateMaxFiles int

	// MaxQueryTime is the maximum amount of time a blocking query can wait
	// before Consul will force a response. Consul applies jitter to the wait
	// time. The jittered time will be capped to MaxQueryTime.
	//
	// hcl: max_query_time = "duration"
	// flags: -max-query-time string
	MaxQueryTime time.Duration

	// Node ID is a unique ID for this node across space and time. Defaults
	// to a randomly-generated ID that persists in the data-dir.
	//
	// todo(fs): don't we have a requirement for this to be a UUID in a specific format?
	//
	// hcl: node_id = string
	// flag: -node-id string
	NodeID types.NodeID

	// NodeMeta contains metadata key/value pairs. These are excluded from JSON output
	// because they can be reloaded and might be stale when shown from the
	// config instead of the local state.
	// todo(fs): should the sanitizer omit them from output as well since they could be stale?
	//
	// hcl: node_meta = map[string]string
	// flag: -node-meta "key:value" -node-meta "key:value" ...
	NodeMeta map[string]string

	// NonVotingServer is whether this server will act as a non-voting member
	// of the cluster to help provide read scalability. (Enterprise-only)
	//
	// hcl: non_voting_server = (true|false)
	// flag: -non-voting-server
	NonVotingServer bool

	// PidFile is the file to store our PID in.
	//
	// hcl: pid_file = string
	PidFile string

	// PrimaryDatacenter is the central datacenter that holds authoritative
	// ACL records, replicates intentions and holds the root CA for Connect.
	// This must be the same for the entire cluster. Off by default.
	//
	// hcl: primary_datacenter = string
	PrimaryDatacenter string

	// RPCAdvertiseAddr is the TCP address Consul advertises for its RPC endpoint.
	// By default this is the bind address on the default RPC Server port. If the
	// advertise address is specified then it is used.
	//
	// hcl: bind_addr = string advertise_addr = string ports { server = int }
	RPCAdvertiseAddr *net.TCPAddr

	// RPCBindAddr is the TCP address Consul will bind to for its RPC endpoint.
	// By default this is the bind address on the default RPC Server port.
	//
	// hcl: bind_addr = string ports { server = int }
	RPCBindAddr *net.TCPAddr

	// RPCHandshakeTimeout is the timeout for reading the initial magic byte on a
	// new RPC connection. If this is set high it may allow unauthenticated users
	// to hold connections open arbitrarily long, even when mutual TLS is being
	// enforced. It may be set to 0 explicitly to disable the timeout but this
	// should never be used in production. Default is 5 seconds.
	//
	// hcl: limits { rpc_handshake_timeout = "duration" }
	RPCHandshakeTimeout time.Duration

	// RPCHoldTimeout is how long an RPC can be "held" before it is errored.
	// This is used to paper over a loss of leadership by instead holding RPCs,
	// so that the caller experiences a slow response rather than an error.
	// This period is meant to be long enough for a leader election to take
	// place, and a small jitter is applied to avoid a thundering herd.
	//
	// hcl: performance { rpc_hold_timeout = "duration" }
	RPCHoldTimeout time.Duration

	// RPCRateLimit and RPCMaxBurst control how frequently RPC calls are allowed
	// to happen. In any large enough time interval, rate limiter limits the
	// rate to RPCRate tokens per second, with a maximum burst size of
	// RPCMaxBurst events. As a special case, if RPCRate == Inf (the infinite
	// rate), RPCMaxBurst is ignored.
	//
	// See https://en.wikipedia.org/wiki/Token_bucket for more about token
	// buckets.
	//
	// hcl: limit { rpc_rate = (float64|MaxFloat64) rpc_max_burst = int }
	RPCRateLimit rate.Limit
	RPCMaxBurst  int

	// RPCMaxConnsPerClient limits the number of concurrent TCP connections the
	// RPC server will accept from any single source IP address.
	//
	// hcl: limits{ rpc_max_conns_per_client = 100 }
	RPCMaxConnsPerClient int

	// RPCProtocol is the Consul protocol version to use.
	//
	// hcl: protocol = int
	RPCProtocol int

	// RaftProtocol sets the Raft protocol version to use on this server.
	// Defaults to 3.
	//
	// hcl: raft_protocol = int
	RaftProtocol int

	// RaftSnapshotThreshold sets the minimum threshold of raft commits after which
	// a snapshot is created. Defaults to 8192
	//
	// hcl: raft_snapshot_threshold = int
	RaftSnapshotThreshold int

	// RaftSnapshotInterval sets the interval to use when checking whether to create
	// a new snapshot. Defaults to 5 seconds.
	// hcl: raft_snapshot_threshold = int
	RaftSnapshotInterval time.Duration

	// RaftTrailingLogs sets the number of log entries that will be left in the
	// log store after a snapshot. This must be large enough that a follower can
	// transfer and restore an entire snapshot of the state before this many new
	// entries have been appended. In vast majority of cases the default is plenty
	// but if there is a sustained high write throughput coupled with a huge
	// multi-gigabyte snapshot setting this higher may be necessary to allow
	// followers time to reload from snapshot without becoming unhealthy. If it's
	// too low then followers are unable to ever recover from a restart and will
	// enter a loop of constantly downloading full snapshots and never catching
	// up. If you need to change this you should reconsider your usage of Consul
	// as it is not designed to store multiple-gigabyte data sets with high write
	// throughput. Defaults to 10000.
	//
	// hcl: raft_trailing_logs = int
	RaftTrailingLogs int

	// ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with
	// another agent before deciding it's permanently gone. This can be used to
	// control the time it takes to reap failed nodes from the cluster.
	//
	// hcl: reconnect_timeout = "duration"
	ReconnectTimeoutLAN time.Duration

	// ReconnectTimeoutWAN specifies the amount of time to wait to reconnect with
	// another agent before deciding it's permanently gone. This can be used to
	// control the time it takes to reap failed nodes from the cluster.
	//
	// hcl: reconnect_timeout = "duration"
	ReconnectTimeoutWAN time.Duration

	// RejoinAfterLeave controls our interaction with the cluster after leave.
	// When set to false (default), a leave causes Consul to not rejoin
	// the cluster until an explicit join is received. If this is set to
	// true, we ignore the leave, and rejoin the cluster on start.
	//
	// hcl: rejoin_after_leave = (true|false)
	// flag: -rejoin
	RejoinAfterLeave bool

	// RetryJoinIntervalLAN specifies the amount of time to wait in between join
	// attempts on agent start. The minimum allowed value is 1 second and
	// the default is 30s.
	//
	// hcl: retry_join = "duration"
	RetryJoinIntervalLAN time.Duration

	// RetryJoinIntervalWAN specifies the amount of time to wait in between join
	// attempts on agent start. The minimum allowed value is 1 second and
	// the default is 30s.
	//
	// hcl: retry_join_wan = "duration"
	RetryJoinIntervalWAN time.Duration

	// RetryJoinLAN is a list of addresses and/or go-discover expressions to
	// join with retry enabled. See
	// https://www.consul.io/docs/agent/options.html#cloud-auto-joining for
	// details.
	//
	// hcl: retry_join = []string
	// flag: -retry-join string -retry-join string
	RetryJoinLAN []string

	// RetryJoinMaxAttemptsLAN specifies the maximum number of times to retry
	// joining a host on startup. This is useful for cases where we know the
	// node will be online eventually.
	//
	// hcl: retry_max = int
	// flag: -retry-max int
	RetryJoinMaxAttemptsLAN int

	// RetryJoinMaxAttemptsWAN specifies the maximum number of times to retry
	// joining a host on startup. This is useful for cases where we know the
	// node will be online eventually.
	//
	// hcl: retry_max_wan = int
	// flag: -retry-max-wan int
	RetryJoinMaxAttemptsWAN int

	// RetryJoinWAN is a list of addresses and/or go-discover expressions to
	// join -wan with retry enabled. See
	// https://www.consul.io/docs/agent/options.html#cloud-auto-joining for
	// details.
	//
	// hcl: retry_join_wan = []string
	// flag: -retry-join-wan string -retry-join-wan string
	RetryJoinWAN []string

	// SegmentName is the network segment for this client to join.
	// (Enterprise-only)
	//
	// hcl: segment = string
	SegmentName string

	// Segments is the list of network segments for this server to
	// initialize.
	//
	// hcl: segment = [
	//   {
	//     # name is the name of the segment
	//     name = string
	//
	//     # bind is the bind ip address for this segment.
	//     bind = string
	//
	//     # port is the bind port for this segment.
	//     port = int
	//
	//     # advertise is the advertise ip address for this segment.
	//     # Defaults to the bind address if not set.
	//     advertise = string
	//
	//     # rpc_listener controls whether or not to bind a separate
	//     # RPC listener to the bind address.
	//     rpc_listener = (true|false)
	//   },
	//   ...
	// ]
	Segments []structs.NetworkSegment

	// SerfAdvertiseAddrLAN is the TCP address which is used for advertising
	// the LAN Gossip pool for both client and server. The address is the
	// combination of AdvertiseAddrLAN and the SerfPortLAN. If the advertise
	// address is not given the bind address is used.
	//
	// hcl: bind_addr = string advertise_addr = string ports { serf_lan = int }
	SerfAdvertiseAddrLAN *net.TCPAddr

	// SerfAdvertiseAddrWAN is the TCP address which is used for advertising
	// the WAN Gossip pool on the server only. The address is the combination
	// of AdvertiseAddrWAN and the SerfPortWAN. If the advertise address is not
	// given the bind address is used.
	//
	// hcl: bind_addr = string advertise_addr_wan = string ports { serf_wan = int }
	SerfAdvertiseAddrWAN *net.TCPAddr

	// SerfBindAddrLAN is the address to bind the Serf LAN TCP and UDP
	// listeners to. The ip address is either the default bind address or the
	// 'serf_lan' address which can be either an ip address or a go-sockaddr
	// template which resolves to a single ip address.
	//
	// hcl: bind_addr = string serf_lan = string ports { serf_lan = int }
	// flag: -serf-lan string
	SerfBindAddrLAN *net.TCPAddr

	// SerfBindAddrWAN is the address to bind the Serf WAN TCP and UDP
	// listeners to. The ip address is either the default bind address or the
	// 'serf_wan' address which can be either an ip address or a go-sockaddr
	// template which resolves to a single ip address.
	//
	// hcl: bind_addr = string serf_wan = string ports { serf_wan = int }
	// flag: -serf-wan string
	SerfBindAddrWAN *net.TCPAddr

	// SerfPortLAN is the port used for the LAN Gossip pool for both client and server.
	// The default is 8301.
	//
	// hcl: ports { serf_lan = int }
	SerfPortLAN int

	// SerfPortWAN is the port used for the WAN Gossip pool for the server only.
	// The default is 8302.
	//
	// hcl: ports { serf_wan = int }
	SerfPortWAN int

	// GossipLANGossipInterval is the interval between sending messages that need
	// to be gossiped that haven't been able to piggyback on probing messages.
	// If this is set to zero, non-piggyback gossip is disabled. By lowering
	// this value (more frequent) gossip messages are propagated across
	// the cluster more quickly at the expense of increased bandwidth. This
	// configuration only applies to LAN gossip communications
	//
	// The default is: 200ms
	//
	// hcl: gossip_lan { gossip_interval = duration}
	GossipLANGossipInterval time.Duration

	// GossipLANGossipNodes is the number of random nodes to send gossip messages to
	// per GossipInterval. Increasing this number causes the gossip messages to
	// propagate across the cluster more quickly at the expense of increased
	// bandwidth. This configuration only applies to LAN gossip communications
	//
	// The default is: 3
	//
	// hcl: gossip_lan { gossip_nodes = int }
	GossipLANGossipNodes int

	// GossipLANProbeInterval is the interval between random node probes. Setting
	// this lower (more frequent) will cause the memberlist cluster to detect
	// failed nodes more quickly at the expense of increased bandwidth usage.
	// This configuration only applies to LAN gossip communications
	//
	// The default is: 1s
	//
	// hcl: gossip_lan { probe_interval = duration }
	GossipLANProbeInterval time.Duration

	// GossipLANProbeTimeout is the timeout to wait for an ack from a probed node
	// before assuming it is unhealthy. This should be set to 99-percentile
	// of RTT (round-trip time) on your network. This configuration
	// only applies to the LAN gossip communications
	//
	// The default is: 500ms
	//
	// hcl: gossip_lan { probe_timeout = duration }
	GossipLANProbeTimeout time.Duration

	// GossipLANSuspicionMult is the multiplier for determining the time an
	// inaccessible node is considered suspect before declaring it dead. This
	// configuration only applies to LAN gossip communications
	//
	// The actual timeout is calculated using the formula:
	//
	//   SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval
	//
	// This allows the timeout to scale properly with expected propagation
	// delay with a larger cluster size. The higher the multiplier, the longer
	// an inaccessible node is considered part of the cluster before declaring
	// it dead, giving that suspect node more time to refute if it is indeed
	// still alive.
	//
	// The default is: 4
	//
	// hcl: gossip_lan { suspicion_mult = int }
	GossipLANSuspicionMult int

	// GossipLANRetransmitMult is the multiplier for the number of retransmissions
	// that are attempted for messages broadcasted over gossip. This
	// configuration only applies to LAN gossip communications. The actual
	// count of retransmissions is calculated using the formula:
	//
	//   Retransmits = RetransmitMult * log(N+1)
	//
	// This allows the retransmits to scale properly with cluster size. The
	// higher the multiplier, the more likely a failed broadcast is to converge
	// at the expense of increased bandwidth.
	//
	// The default is: 4
	//
	// hcl: gossip_lan { retransmit_mult = int }
	GossipLANRetransmitMult int

	// GossipWANGossipInterval  is the interval between sending messages that need
	// to be gossiped that haven't been able to piggyback on probing messages.
	// If this is set to zero, non-piggyback gossip is disabled. By lowering
	// this value (more frequent) gossip messages are propagated across
	// the cluster more quickly at the expense of increased bandwidth. This
	// configuration only applies to WAN gossip communications
	//
	// The default is: 200ms
	//
	// hcl: gossip_wan { gossip_interval = duration}
	GossipWANGossipInterval time.Duration

	// GossipWANGossipNodes is the number of random nodes to send gossip messages to
	// per GossipInterval. Increasing this number causes the gossip messages to
	// propagate across the cluster more quickly at the expense of increased
	// bandwidth. This configuration only applies to WAN gossip communications
	//
	// The default is: 3
	//
	// hcl: gossip_wan { gossip_nodes = int }
	GossipWANGossipNodes int

	// GossipWANProbeInterval is the interval between random node probes. Setting
	// this lower (more frequent) will cause the memberlist cluster to detect
	// failed nodes more quickly at the expense of increased bandwidth usage.
	// This configuration only applies to WAN gossip communications
	//
	// The default is: 1s
	//
	// hcl: gossip_wan { probe_interval = duration }
	GossipWANProbeInterval time.Duration

	// GossipWANProbeTimeout is the timeout to wait for an ack from a probed node
	// before assuming it is unhealthy. This should be set to 99-percentile
	// of RTT (round-trip time) on your network. This configuration
	// only applies to the WAN gossip communications
	//
	// The default is: 500ms
	//
	// hcl: gossip_wan { probe_timeout = duration }
	GossipWANProbeTimeout time.Duration

	// GossipWANSuspicionMult is the multiplier for determining the time an
	// inaccessible node is considered suspect before declaring it dead. This
	// configuration only applies to WAN gossip communications
	//
	// The actual timeout is calculated using the formula:
	//
	//   SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval
	//
	// This allows the timeout to scale properly with expected propagation
	// delay with a larger cluster size. The higher the multiplier, the longer
	// an inaccessible node is considered part of the cluster before declaring
	// it dead, giving that suspect node more time to refute if it is indeed
	// still alive.
	//
	// The default is: 4
	//
	// hcl: gossip_wan { suspicion_mult = int }
	GossipWANSuspicionMult int

	// GossipWANRetransmitMult is the multiplier for the number of retransmissions
	// that are attempted for messages broadcasted over gossip. This
	// configuration only applies to WAN gossip communications. The actual
	// count of retransmissions is calculated using the formula:
	//
	//   Retransmits = RetransmitMult * log(N+1)
	//
	// This allows the retransmits to scale properly with cluster size. The
	// higher the multiplier, the more likely a failed broadcast is to converge
	// at the expense of increased bandwidth.
	//
	// The default is: 4
	//
	// hcl: gossip_wan { retransmit_mult = int }
	GossipWANRetransmitMult int

	// ServerMode controls if this agent acts like a Consul server,
	// or merely as a client. Servers have more state, take part
	// in leader election, etc.
	//
	// hcl: server = (true|false)
	// flag: -server
	ServerMode bool

	// ServerName is used with the TLS certificates to ensure the name we
	// provide matches the certificate.
	//
	// hcl: server_name = string
	ServerName string

	// ServerPort is the port the RPC server will bind to.
	// The default is 8300.
	//
	// hcl: ports { server = int }
	ServerPort int

	// Services contains the provided service definitions:
	//
	// hcl: services = [
	//   {
	//     id = string
	//     name = string
	//     tags = []string
	//     address = string
	//     check = { check definition }
	//     checks = [ { check definition}, ... ]
	//     token = string
	//     enable_tag_override = (true|false)
	//   },
	//   ...
	// ]
	Services []*structs.ServiceDefinition

	// Minimum Session TTL.
	//
	// hcl: session_ttl_min = "duration"
	SessionTTLMin time.Duration

	// SkipLeaveOnInt controls if Serf skips a graceful leave when
	// receiving the INT signal. Defaults false on clients, true on
	// servers. (reloadable)
	//
	// hcl: skip_leave_on_interrupt = (true|false)
	SkipLeaveOnInt bool

	// StartJoinAddrsLAN is a list of addresses to attempt to join -lan when the
	// agent starts. If Serf is unable to communicate with any of these
	// addresses, then the agent will error and exit.
	//
	// hcl: start_join = []string
	// flag: -join string -join string
	StartJoinAddrsLAN []string

	// StartJoinWAN is a list of addresses to attempt to join -wan when the
	// agent starts. If Serf is unable to communicate with any of these
	// addresses, then the agent will error and exit.
	//
	// hcl: start_join_wan = []string
	// flag: -join-wan string -join-wan string
	StartJoinAddrsWAN []string

	// SyslogFacility is used to control where the syslog messages go
	// By default, goes to LOCAL0
	//
	// hcl: syslog_facility = string
	SyslogFacility string

	// TLSCipherSuites is used to specify the list of supported ciphersuites.
	//
	// The values should be a list of the following values:
	//
	//   TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
	//   TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
	//   TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
	//   TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
	//   TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
	//   TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
	//   TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
	//   TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
	//   TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
	//   TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
	//   TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
	//   TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
	//   TLS_RSA_WITH_AES_128_GCM_SHA256
	//   TLS_RSA_WITH_AES_256_GCM_SHA384
	//   TLS_RSA_WITH_AES_128_CBC_SHA256
	//   TLS_RSA_WITH_AES_128_CBC_SHA
	//   TLS_RSA_WITH_AES_256_CBC_SHA
	//   TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
	//   TLS_RSA_WITH_3DES_EDE_CBC_SHA
	//   TLS_RSA_WITH_RC4_128_SHA
	//   TLS_ECDHE_RSA_WITH_RC4_128_SHA
	//   TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
	//
	// todo(fs): IMHO, we should also support the raw 0xNNNN values from
	// todo(fs): https://golang.org/pkg/crypto/tls/#pkg-constants
	// todo(fs): since they are standardized by IANA.
	//
	// hcl: tls_cipher_suites = []string
	TLSCipherSuites []uint16

	// TLSMinVersion is used to set the minimum TLS version used for TLS
	// connections. Should be either "tls10", "tls11", "tls12" or "tls13".
	// Defaults to tls12.
	//
	// hcl: tls_min_version = string
	TLSMinVersion string

	// TLSPreferServerCipherSuites specifies whether to prefer the server's
	// cipher suite over the client cipher suites.
	//
	// hcl: tls_prefer_server_cipher_suites = (true|false)
	TLSPreferServerCipherSuites bool

	// TaggedAddresses are used to publish a set of addresses for
	// for a node, which can be used by the remote agent. We currently
	// populate only the "wan" tag based on the SerfWan advertise address,
	// but this structure is here for possible future features with other
	// user-defined tags. The "wan" tag will be used by remote agents if
	// they are configured with TranslateWANAddrs set to true.
	//
	// hcl: tagged_addresses = map[string]string
	TaggedAddresses map[string]string

	// TranslateWANAddrs controls whether or not Consul should prefer
	// the "wan" tagged address when doing lookups in remote datacenters.
	// See TaggedAddresses below for more details.
	//
	// hcl: translate_wan_addrs = (true|false)
	TranslateWANAddrs bool

	// TxnMaxReqLen configures the upper limit for the size (in bytes) of the
	// incoming request bodies for transactions to the /txn endpoint.
	//
	// hcl: limits { txn_max_req_len = uint64 }
	TxnMaxReqLen uint64

	// UIDir is the directory containing the Web UI resources.
	// If provided, the UI endpoints will be enabled.
	//
	// hcl: ui_dir = string
	// flag: -ui-dir string
	UIDir string

	//UIContentPath is a string that sets the external
	// path to a string. Default: /ui/
	UIContentPath string

	// UnixSocketGroup contains the group of the file permissions when
	// Consul binds to UNIX sockets.
	//
	// hcl: unix_sockets { group = string }
	UnixSocketGroup string

	// UnixSocketMode contains the mode of the file permissions when
	// Consul binds to UNIX sockets.
	//
	// hcl: unix_sockets { mode = string }
	UnixSocketMode string

	// UnixSocketUser contains the user of the file permissions when
	// Consul binds to UNIX sockets.
	//
	// hcl: unix_sockets { user = string }
	UnixSocketUser string

	// VerifyIncoming is used to verify the authenticity of incoming
	// connections. This means that TCP requests are forbidden, only allowing
	// for TLS. TLS connections must match a provided certificate authority.
	// This can be used to force client auth.
	//
	// hcl: verify_incoming = (true|false)
	VerifyIncoming bool

	// VerifyIncomingHTTPS is used to verify the authenticity of incoming HTTPS
	// connections. This means that TCP requests are forbidden, only allowing
	// for TLS. TLS connections must match a provided certificate authority.
	// This can be used to force client auth.
	//
	// hcl: verify_incoming_https = (true|false)
	VerifyIncomingHTTPS bool

	// VerifyIncomingRPC is used to verify the authenticity of incoming RPC
	// connections. This means that TCP requests are forbidden, only allowing
	// for TLS. TLS connections must match a provided certificate authority.
	// This can be used to force client auth.
	//
	// hcl: verify_incoming_rpc = (true|false)
	VerifyIncomingRPC bool

	// VerifyOutgoing is used to verify the authenticity of outgoing
	// connections. This means that TLS requests are used. TLS connections must
	// match a provided certificate authority. This is used to verify
	// authenticity of server nodes.
	//
	// hcl: verify_outgoing = (true|false)
	VerifyOutgoing bool

	// VerifyServerHostname is used to enable hostname verification of servers.
	// This ensures that the certificate presented is valid for
	// server.<datacenter>.<domain>. This prevents a compromised client from
	// being restarted as a server, and then intercepting request traffic as
	// well as being added as a raft peer. This should be enabled by default
	// with VerifyOutgoing, but for legacy reasons we cannot break existing
	// clients.
	//
	// hcl: verify_server_hostname = (true|false)
	VerifyServerHostname bool

	// Watches are used to monitor various endpoints and to invoke a
	// handler to act appropriately. These are managed entirely in the
	// agent layer using the standard APIs.
	//
	// See https://www.consul.io/docs/agent/watches.html for details.
	//
	// hcl: watches = [
	//   { type=string ... },
	//   { type=string ... },
	//   ...
	// ]
	//
	Watches []map[string]interface{}

	EnterpriseRuntimeConfig
}

func (c *RuntimeConfig) apiAddresses(maxPerType int) (unixAddrs, httpAddrs, httpsAddrs []string) {
	if len(c.HTTPSAddrs) > 0 {
		for i, addr := range c.HTTPSAddrs {
			if maxPerType < 1 || i < maxPerType {
				httpsAddrs = append(httpsAddrs, addr.String())
			} else {
				break
			}
		}
	}
	if len(c.HTTPAddrs) > 0 {
		unix_count := 0
		http_count := 0
		for _, addr := range c.HTTPAddrs {
			switch addr.(type) {
			case *net.UnixAddr:
				if maxPerType < 1 || unix_count < maxPerType {
					unixAddrs = append(unixAddrs, addr.String())
					unix_count += 1
				}
			default:
				if maxPerType < 1 || http_count < maxPerType {
					httpAddrs = append(httpAddrs, addr.String())
					http_count += 1
				}
			}
		}
	}

	return
}

func (c *RuntimeConfig) ClientAddress() (unixAddr, httpAddr, httpsAddr string) {
	unixAddrs, httpAddrs, httpsAddrs := c.apiAddresses(0)

	if len(unixAddrs) > 0 {
		unixAddr = "unix://" + unixAddrs[0]
	}

	http_any := ""
	if len(httpAddrs) > 0 {
		for _, addr := range httpAddrs {
			host, port, err := net.SplitHostPort(addr)
			if err != nil {
				continue
			}

			if host == "0.0.0.0" || host == "::" {
				if http_any == "" {
					if host == "0.0.0.0" {
						http_any = net.JoinHostPort("127.0.0.1", port)
					} else {
						http_any = net.JoinHostPort("::1", port)
					}
				}
				continue
			}

			httpAddr = addr
			break
		}

		if httpAddr == "" && http_any != "" {
			httpAddr = http_any
		}
	}

	https_any := ""
	if len(httpsAddrs) > 0 {
		for _, addr := range httpsAddrs {
			host, port, err := net.SplitHostPort(addr)
			if err != nil {
				continue
			}

			if host == "0.0.0.0" || host == "::" {
				if https_any == "" {
					if host == "0.0.0.0" {
						https_any = net.JoinHostPort("127.0.0.1", port)
					} else {
						https_any = net.JoinHostPort("::1", port)
					}
				}
				continue
			}

			httpsAddr = addr
			break
		}

		if httpsAddr == "" && https_any != "" {
			httpsAddr = https_any
		}
	}

	return
}

func (c *RuntimeConfig) APIConfig(includeClientCerts bool) (*api.Config, error) {
	cfg := &api.Config{
		Datacenter: c.Datacenter,
		TLSConfig:  api.TLSConfig{InsecureSkipVerify: !c.VerifyOutgoing},
	}

	unixAddr, httpAddr, httpsAddr := c.ClientAddress()

	if httpsAddr != "" {
		cfg.Address = httpsAddr
		cfg.Scheme = "https"
		cfg.TLSConfig.CAFile = c.CAFile
		cfg.TLSConfig.CAPath = c.CAPath
		if includeClientCerts {
			cfg.TLSConfig.CertFile = c.CertFile
			cfg.TLSConfig.KeyFile = c.KeyFile
		}
	} else if httpAddr != "" {
		cfg.Address = httpAddr
		cfg.Scheme = "http"
	} else if unixAddr != "" {
		cfg.Address = unixAddr
		// this should be ignored - however we are still talking http over a unix socket
		// so it makes sense to set it like this
		cfg.Scheme = "http"
	} else {
		return nil, fmt.Errorf("No suitable client address can be found")
	}

	return cfg, nil
}

// Sanitized returns a JSON/HCL compatible representation of the runtime
// configuration where all fields with potential secrets had their
// values replaced by 'hidden'. In addition, network addresses and
// time.Duration values are formatted to improve readability.
func (c *RuntimeConfig) Sanitized() map[string]interface{} {
	return sanitize("rt", reflect.ValueOf(c)).Interface().(map[string]interface{})
}

func (c *RuntimeConfig) ToTLSUtilConfig() tlsutil.Config {
	return tlsutil.Config{
		VerifyIncoming:           c.VerifyIncoming,
		VerifyIncomingRPC:        c.VerifyIncomingRPC,
		VerifyIncomingHTTPS:      c.VerifyIncomingHTTPS,
		VerifyOutgoing:           c.VerifyOutgoing,
		VerifyServerHostname:     c.VerifyServerHostname,
		CAFile:                   c.CAFile,
		CAPath:                   c.CAPath,
		CertFile:                 c.CertFile,
		KeyFile:                  c.KeyFile,
		NodeName:                 c.NodeName,
		Domain:                   c.DNSDomain,
		ServerName:               c.ServerName,
		TLSMinVersion:            c.TLSMinVersion,
		CipherSuites:             c.TLSCipherSuites,
		PreferServerCipherSuites: c.TLSPreferServerCipherSuites,
		EnableAgentTLSForChecks:  c.EnableAgentTLSForChecks,
		AutoEncryptTLS:           c.AutoEncryptTLS,
	}
}

// isSecret determines whether a field name represents a field which
// may contain a secret.
func isSecret(name string) bool {
	name = strings.ToLower(name)
	return strings.Contains(name, "key") || strings.Contains(name, "token") || strings.Contains(name, "secret")
}

// cleanRetryJoin sanitizes the go-discover config strings key=val key=val...
// by scrubbing the individual key=val combinations.
func cleanRetryJoin(a string) string {
	var fields []string
	for _, f := range strings.Fields(a) {
		if isSecret(f) {
			kv := strings.SplitN(f, "=", 2)
			fields = append(fields, kv[0]+"=hidden")
		} else {
			fields = append(fields, f)
		}
	}
	return strings.Join(fields, " ")
}

func sanitize(name string, v reflect.Value) reflect.Value {
	typ := v.Type()
	switch {
	// check before isStruct and isPtr
	case isNetAddr(typ):
		if v.IsNil() {
			return reflect.ValueOf("")
		}
		switch x := v.Interface().(type) {
		case *net.TCPAddr:
			return reflect.ValueOf("tcp://" + x.String())
		case *net.UDPAddr:
			return reflect.ValueOf("udp://" + x.String())
		case *net.UnixAddr:
			return reflect.ValueOf("unix://" + x.String())
		case *net.IPAddr:
			return reflect.ValueOf(x.IP.String())
		case *net.IPNet:
			return reflect.ValueOf(x.String())
		default:
			return v
		}

	// check before isNumber
	case isDuration(typ):
		x := v.Interface().(time.Duration)
		return reflect.ValueOf(x.String())

	case isString(typ):
		if strings.HasPrefix(name, "RetryJoinLAN[") || strings.HasPrefix(name, "RetryJoinWAN[") {
			x := v.Interface().(string)
			return reflect.ValueOf(cleanRetryJoin(x))
		}
		if isSecret(name) {
			return reflect.ValueOf("hidden")
		}
		return v

	case isNumber(typ) || isBool(typ):
		return v

	case isPtr(typ):
		if v.IsNil() {
			return v
		}
		return sanitize(name, v.Elem())

	case isStruct(typ):
		m := map[string]interface{}{}
		for i := 0; i < typ.NumField(); i++ {
			key := typ.Field(i).Name
			m[key] = sanitize(key, v.Field(i)).Interface()
		}
		return reflect.ValueOf(m)

	case isArray(typ) || isSlice(typ):
		ma := make([]interface{}, 0)
		for i := 0; i < v.Len(); i++ {
			ma = append(ma, sanitize(fmt.Sprintf("%s[%d]", name, i), v.Index(i)).Interface())
		}
		return reflect.ValueOf(ma)

	case isMap(typ):
		m := map[string]interface{}{}
		for _, k := range v.MapKeys() {
			key := k.String()
			m[key] = sanitize(key, v.MapIndex(k)).Interface()
		}
		return reflect.ValueOf(m)

	default:
		return v
	}
}

func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) }
func isMap(t reflect.Type) bool      { return t.Kind() == reflect.Map }
func isNetAddr(t reflect.Type) bool  { return t.Implements(reflect.TypeOf((*net.Addr)(nil)).Elem()) }
func isPtr(t reflect.Type) bool      { return t.Kind() == reflect.Ptr }
func isArray(t reflect.Type) bool    { return t.Kind() == reflect.Array }
func isSlice(t reflect.Type) bool    { return t.Kind() == reflect.Slice }
func isString(t reflect.Type) bool   { return t.Kind() == reflect.String }
func isStruct(t reflect.Type) bool   { return t.Kind() == reflect.Struct }
func isBool(t reflect.Type) bool     { return t.Kind() == reflect.Bool }
func isNumber(t reflect.Type) bool   { return isInt(t) || isUint(t) || isFloat(t) || isComplex(t) }
func isInt(t reflect.Type) bool {
	return t.Kind() == reflect.Int ||
		t.Kind() == reflect.Int8 ||
		t.Kind() == reflect.Int16 ||
		t.Kind() == reflect.Int32 ||
		t.Kind() == reflect.Int64
}
func isUint(t reflect.Type) bool {
	return t.Kind() == reflect.Uint ||
		t.Kind() == reflect.Uint8 ||
		t.Kind() == reflect.Uint16 ||
		t.Kind() == reflect.Uint32 ||
		t.Kind() == reflect.Uint64
}
func isFloat(t reflect.Type) bool { return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 }
func isComplex(t reflect.Type) bool {
	return t.Kind() == reflect.Complex64 || t.Kind() == reflect.Complex128
}
