consul/agent/config/runtime_test.go

6799 lines
185 KiB
Go

package config
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/armon/go-metrics/prometheus"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/logging"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/types"
)
// testCase used to test different config loading and flag parsing scenarios.
type testCase struct {
desc string
args []string
setup func() // TODO: accept a testing.T instead of panic
expected func(rt *RuntimeConfig)
expectedErr string
expectedWarnings []string
opts LoadOpts
json []string
hcl []string
}
func (tc testCase) source(format string) []string {
if format == "hcl" {
return tc.hcl
}
return tc.json
}
// TestConfigFlagsAndEdgecases tests the command line flags and
// edgecases for the config parsing. It provides a test structure which
// checks for warnings on deprecated fields and flags. These tests
// should check one option at a time if possible
func TestLoad_IntegrationWithFlags(t *testing.T) {
dataDir := testutil.TempDir(t, "config")
run := func(t *testing.T, tc testCase) {
t.Helper()
if len(tc.json) == 0 && len(tc.hcl) == 0 {
runCase(t, tc.desc, tc.run("", dataDir))
return
}
for _, format := range []string{"json", "hcl"} {
name := fmt.Sprintf("%v_%v", tc.desc, format)
runCase(t, name, tc.run(format, dataDir))
}
}
defaultEntMeta := structs.DefaultEnterpriseMetaInDefaultPartition()
// ------------------------------------------------------------
// cmd line flags
//
run(t, testCase{
desc: "-advertise",
args: []string{
`-advertise=1.2.3.4`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
rt.TaggedAddresses = map[string]string{
"lan": "1.2.3.4",
"lan_ipv4": "1.2.3.4",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-advertise-wan",
args: []string{
`-advertise-wan=1.2.3.4`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
rt.TaggedAddresses = map[string]string{
"lan": "10.0.0.1",
"lan_ipv4": "10.0.0.1",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-advertise and -advertise-wan",
args: []string{
`-advertise=1.2.3.4`,
`-advertise-wan=5.6.7.8`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
rt.AdvertiseAddrWAN = ipAddr("5.6.7.8")
rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("5.6.7.8:8302")
rt.TaggedAddresses = map[string]string{
"lan": "1.2.3.4",
"lan_ipv4": "1.2.3.4",
"wan": "5.6.7.8",
"wan_ipv4": "5.6.7.8",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-bind",
args: []string{
`-bind=1.2.3.4`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.BindAddr = ipAddr("1.2.3.4")
rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
rt.RPCBindAddr = tcpAddr("1.2.3.4:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301")
rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302")
rt.TaggedAddresses = map[string]string{
"lan": "1.2.3.4",
"lan_ipv4": "1.2.3.4",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-bootstrap",
args: []string{
`-bootstrap`,
`-server`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.Bootstrap = true
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.DataDir = dataDir
rt.RPCConfig.EnableStreaming = true
},
expectedWarnings: []string{"bootstrap = true: do not enable unless necessary"},
})
run(t, testCase{
desc: "-bootstrap-expect",
args: []string{
`-bootstrap-expect=3`,
`-server`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.BootstrapExpect = 3
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.DataDir = dataDir
rt.RPCConfig.EnableStreaming = true
},
expectedWarnings: []string{"bootstrap_expect > 0: expecting 3 servers"},
})
run(t, testCase{
desc: "-client",
args: []string{
`-client=1.2.3.4`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("1.2.3.4")}
rt.DNSAddrs = []net.Addr{tcpAddr("1.2.3.4:8600"), udpAddr("1.2.3.4:8600")}
rt.HTTPAddrs = []net.Addr{tcpAddr("1.2.3.4:8500")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-config-dir",
args: []string{
`-data-dir=` + dataDir,
`-config-dir`, filepath.Join(dataDir, "conf.d"),
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
setup: func() {
writeFile(filepath.Join(dataDir, "conf.d/conf.json"), []byte(`{"datacenter":"a"}`))
},
})
run(t, testCase{
desc: "-config-file json",
args: []string{
`-data-dir=` + dataDir,
`-config-file`, filepath.Join(dataDir, "conf.json"),
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
setup: func() {
writeFile(filepath.Join(dataDir, "conf.json"), []byte(`{"datacenter":"a"}`))
},
})
run(t, testCase{
desc: "-config-file hcl and json",
args: []string{
`-data-dir=` + dataDir,
`-config-file`, filepath.Join(dataDir, "conf.hcl"),
`-config-file`, filepath.Join(dataDir, "conf.json"),
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "b"
rt.PrimaryDatacenter = "b"
rt.DataDir = dataDir
},
setup: func() {
writeFile(filepath.Join(dataDir, "conf.hcl"), []byte(`datacenter = "a"`))
writeFile(filepath.Join(dataDir, "conf.json"), []byte(`{"datacenter":"b"}`))
},
})
run(t, testCase{
desc: "-data-dir empty",
args: []string{
`-data-dir=`,
},
expectedErr: "data_dir cannot be empty",
})
run(t, testCase{
desc: "-data-dir non-directory",
args: []string{
`-data-dir=runtime_test.go`,
},
expectedErr: `data_dir "runtime_test.go" is not a directory`,
})
run(t, testCase{
desc: "-datacenter",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-datacenter empty",
args: []string{
`-datacenter=`,
`-data-dir=` + dataDir,
},
expectedErr: "datacenter cannot be empty",
})
run(t, testCase{
desc: "-dev",
args: []string{
`-dev`,
},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("127.0.0.1")
rt.AdvertiseAddrWAN = ipAddr("127.0.0.1")
rt.BindAddr = ipAddr("127.0.0.1")
rt.ConnectEnabled = true
rt.DevMode = true
rt.DisableAnonymousSignature = true
rt.DisableKeyringFile = true
rt.EnableDebug = true
rt.UIConfig.Enabled = true
rt.LeaveOnTerm = false
rt.Logging.LogLevel = "DEBUG"
rt.RPCAdvertiseAddr = tcpAddr("127.0.0.1:8300")
rt.RPCBindAddr = tcpAddr("127.0.0.1:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("127.0.0.1:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("127.0.0.1:8302")
rt.SerfBindAddrLAN = tcpAddr("127.0.0.1:8301")
rt.SerfBindAddrWAN = tcpAddr("127.0.0.1:8302")
rt.ServerMode = true
rt.SkipLeaveOnInt = true
rt.TaggedAddresses = map[string]string{
"lan": "127.0.0.1",
"lan_ipv4": "127.0.0.1",
"wan": "127.0.0.1",
"wan_ipv4": "127.0.0.1",
}
rt.ConsulCoordinateUpdatePeriod = 100 * time.Millisecond
rt.ConsulRaftElectionTimeout = 52 * time.Millisecond
rt.ConsulRaftHeartbeatTimeout = 35 * time.Millisecond
rt.ConsulRaftLeaderLeaseTimeout = 20 * time.Millisecond
rt.GossipLANGossipInterval = 100 * time.Millisecond
rt.GossipLANProbeInterval = 100 * time.Millisecond
rt.GossipLANProbeTimeout = 100 * time.Millisecond
rt.GossipLANSuspicionMult = 3
rt.GossipWANGossipInterval = 100 * time.Millisecond
rt.GossipWANProbeInterval = 100 * time.Millisecond
rt.GossipWANProbeTimeout = 100 * time.Millisecond
rt.GossipWANSuspicionMult = 3
rt.ConsulServerHealthInterval = 10 * time.Millisecond
rt.GRPCPort = 8502
rt.GRPCAddrs = []net.Addr{tcpAddr("127.0.0.1:8502")}
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "-disable-host-node-id",
args: []string{
`-disable-host-node-id`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DisableHostNodeID = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-disable-keyring-file",
args: []string{
`-disable-keyring-file`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DisableKeyringFile = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-dns-port",
args: []string{
`-dns-port=123`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DNSPort = 123
rt.DNSAddrs = []net.Addr{tcpAddr("127.0.0.1:123"), udpAddr("127.0.0.1:123")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-domain",
args: []string{
`-domain=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DNSDomain = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-alt-domain",
args: []string{
`-alt-domain=alt`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DNSAltDomain = "alt"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-alt-domain can't be prefixed by DC",
args: []string{
`-datacenter=a`,
`-alt-domain=a.alt`,
`-data-dir=` + dataDir,
},
expectedErr: "alt_domain cannot start with {service,connect,node,query,addr,a}",
})
run(t, testCase{
desc: "-alt-domain can't be prefixed by service",
args: []string{
`-alt-domain=service.alt`,
`-data-dir=` + dataDir,
},
expectedErr: "alt_domain cannot start with {service,connect,node,query,addr,dc1}",
})
run(t, testCase{
desc: "-alt-domain can be prefixed by non-keywords",
args: []string{
`-alt-domain=mydomain.alt`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DNSAltDomain = "mydomain.alt"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-alt-domain can't be prefixed by DC",
args: []string{
`-alt-domain=dc1.alt`,
`-data-dir=` + dataDir,
},
expectedErr: "alt_domain cannot start with {service,connect,node,query,addr,dc1}",
})
run(t, testCase{
desc: "-enable-script-checks",
args: []string{
`-enable-script-checks`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.EnableLocalScriptChecks = true
rt.EnableRemoteScriptChecks = true
rt.DataDir = dataDir
},
expectedWarnings: []string{remoteScriptCheckSecurityWarning},
})
run(t, testCase{
desc: "-encrypt",
args: []string{
`-encrypt=pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s=`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.EncryptKey = "pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s="
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-config-format disabled, skip unknown files",
args: []string{
`-data-dir=` + dataDir,
`-config-dir`, filepath.Join(dataDir, "conf"),
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
setup: func() {
writeFile(filepath.Join(dataDir, "conf", "valid.json"), []byte(`{"datacenter":"a"}`))
writeFile(filepath.Join(dataDir, "conf", "invalid.skip"), []byte(`NOPE`))
},
expectedWarnings: []string{
"skipping file " + filepath.Join(dataDir, "conf", "invalid.skip") + ", extension must be .hcl or .json, or config format must be set",
},
})
run(t, testCase{
desc: "-config-format=json",
args: []string{
`-data-dir=` + dataDir,
`-config-format=json`,
`-config-file`, filepath.Join(dataDir, "conf"),
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
setup: func() {
writeFile(filepath.Join(dataDir, "conf"), []byte(`{"datacenter":"a"}`))
},
})
run(t, testCase{
desc: "-config-format=hcl",
args: []string{
`-data-dir=` + dataDir,
`-config-format=hcl`,
`-config-file`, filepath.Join(dataDir, "conf"),
},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
setup: func() {
writeFile(filepath.Join(dataDir, "conf"), []byte(`datacenter = "a"`))
},
})
run(t, testCase{
desc: "-http-port",
args: []string{
`-http-port=123`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.HTTPPort = 123
rt.HTTPAddrs = []net.Addr{tcpAddr("127.0.0.1:123")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-join",
args: []string{
`-join=a`,
`-join=b`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.StartJoinAddrsLAN = []string{"a", "b"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-join-wan",
args: []string{
`-join-wan=a`,
`-join-wan=b`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.StartJoinAddrsWAN = []string{"a", "b"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-log-level",
args: []string{
`-log-level=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.Logging.LogLevel = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-log-json",
args: []string{
`-log-json`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.Logging.LogJSON = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-log-rotate-max-files",
args: []string{
`-log-rotate-max-files=2`,
`-data-dir=` + dataDir,
},
json: []string{`{ "log_rotate_max_files": 2 }`},
hcl: []string{`log_rotate_max_files = 2`},
expected: func(rt *RuntimeConfig) {
rt.Logging.LogRotateMaxFiles = 2
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-node",
args: []string{
`-node=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.NodeName = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-node-id",
args: []string{
`-node-id=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.NodeID = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-node-meta",
args: []string{
`-node-meta=a:b`,
`-node-meta=c:d`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.NodeMeta = map[string]string{"a": "b", "c": "d"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-non-voting-server",
args: []string{
`-non-voting-server`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.ReadReplica = true
rt.DataDir = dataDir
},
expectedWarnings: enterpriseReadReplicaWarnings,
})
run(t, testCase{
desc: "-pid-file",
args: []string{
`-pid-file=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.PidFile = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-primary-gateway",
args: []string{
`-server`,
`-datacenter=dc2`,
`-primary-gateway=a`,
`-primary-gateway=b`,
`-data-dir=` + dataDir,
},
json: []string{`{ "primary_datacenter": "dc1" }`},
hcl: []string{`primary_datacenter = "dc1"`},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "dc2"
rt.PrimaryDatacenter = "dc1"
rt.PrimaryGateways = []string{"a", "b"}
rt.DataDir = dataDir
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "-protocol",
args: []string{
`-protocol=1`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RPCProtocol = 1
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-raft-protocol",
args: []string{
`-raft-protocol=3`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RaftProtocol = 3
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-raft-protocol unsupported",
args: []string{
`-raft-protocol=2`,
`-data-dir=` + dataDir,
},
expectedErr: "raft_protocol version 2 is not supported by this version of Consul",
})
run(t, testCase{
desc: "-recursor",
args: []string{
`-recursor=1.2.3.4`,
`-recursor=5.6.7.8`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DNSRecursors = []string{"1.2.3.4", "5.6.7.8"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-rejoin",
args: []string{
`-rejoin`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RejoinAfterLeave = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-retry-interval",
args: []string{
`-retry-interval=5s`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinIntervalLAN = 5 * time.Second
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-retry-interval-wan",
args: []string{
`-retry-interval-wan=5s`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinIntervalWAN = 5 * time.Second
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-retry-join",
args: []string{
`-retry-join=a`,
`-retry-join=b`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinLAN = []string{"a", "b"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-retry-join-wan",
args: []string{
`-retry-join-wan=a`,
`-retry-join-wan=b`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinWAN = []string{"a", "b"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-retry-max",
args: []string{
`-retry-max=1`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinMaxAttemptsLAN = 1
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-retry-max-wan",
args: []string{
`-retry-max-wan=1`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinMaxAttemptsWAN = 1
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-serf-lan-bind",
args: []string{
`-serf-lan-bind=1.2.3.4`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-serf-lan-port",
args: []string{
`-serf-lan-port=123`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.SerfPortLAN = 123
rt.SerfAdvertiseAddrLAN = tcpAddr("10.0.0.1:123")
rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:123")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-serf-wan-bind",
args: []string{
`-serf-wan-bind=1.2.3.4`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-serf-wan-port",
args: []string{
`-serf-wan-port=123`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.SerfPortWAN = 123
rt.SerfAdvertiseAddrWAN = tcpAddr("10.0.0.1:123")
rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:123")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-server",
args: []string{
`-server`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.DataDir = dataDir
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "-server-port",
args: []string{
`-server-port=123`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.ServerPort = 123
rt.RPCAdvertiseAddr = tcpAddr("10.0.0.1:123")
rt.RPCBindAddr = tcpAddr("0.0.0.0:123")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-syslog",
args: []string{
`-syslog`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.Logging.EnableSyslog = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-ui",
args: []string{
`-ui`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.UIConfig.Enabled = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-ui-dir",
args: []string{
`-ui-dir=a`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.UIConfig.Dir = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "-ui-content-path",
args: []string{
`-ui-content-path=/a/b`,
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.UIConfig.ContentPath = "/a/b/"
rt.DataDir = dataDir
},
})
// ------------------------------------------------------------
// ports and addresses
//
run(t, testCase{
desc: "bind addr any v4",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr":"0.0.0.0" }`},
hcl: []string{`bind_addr = "0.0.0.0"`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("10.0.0.1")
rt.AdvertiseAddrWAN = ipAddr("10.0.0.1")
rt.BindAddr = ipAddr("0.0.0.0")
rt.RPCAdvertiseAddr = tcpAddr("10.0.0.1:8300")
rt.RPCBindAddr = tcpAddr("0.0.0.0:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("10.0.0.1:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("10.0.0.1:8302")
rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:8301")
rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:8302")
rt.TaggedAddresses = map[string]string{
"lan": "10.0.0.1",
"lan_ipv4": "10.0.0.1",
"wan": "10.0.0.1",
"wan_ipv4": "10.0.0.1",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "bind addr any v6",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr":"::" }`},
hcl: []string{`bind_addr = "::"`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("dead:beef::1")
rt.AdvertiseAddrWAN = ipAddr("dead:beef::1")
rt.BindAddr = ipAddr("::")
rt.RPCAdvertiseAddr = tcpAddr("[dead:beef::1]:8300")
rt.RPCBindAddr = tcpAddr("[::]:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("[dead:beef::1]:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("[dead:beef::1]:8302")
rt.SerfBindAddrLAN = tcpAddr("[::]:8301")
rt.SerfBindAddrWAN = tcpAddr("[::]:8302")
rt.TaggedAddresses = map[string]string{
"lan": "dead:beef::1",
"lan_ipv6": "dead:beef::1",
"wan": "dead:beef::1",
"wan_ipv6": "dead:beef::1",
}
rt.DataDir = dataDir
},
opts: LoadOpts{
getPublicIPv6: func() ([]*net.IPAddr, error) {
return []*net.IPAddr{ipAddr("dead:beef::1")}, nil
},
},
})
run(t, testCase{
desc: "bind addr any and advertise set should not detect",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr":"0.0.0.0", "advertise_addr": "1.2.3.4" }`},
hcl: []string{`bind_addr = "0.0.0.0" advertise_addr = "1.2.3.4"`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.BindAddr = ipAddr("0.0.0.0")
rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
rt.RPCBindAddr = tcpAddr("0.0.0.0:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:8301")
rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:8302")
rt.TaggedAddresses = map[string]string{
"lan": "1.2.3.4",
"lan_ipv4": "1.2.3.4",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
opts: LoadOpts{
getPrivateIPv4: func() ([]*net.IPAddr, error) {
return nil, fmt.Errorf("should not detect advertise_addr")
},
},
})
run(t, testCase{
desc: "client addr and ports == 0",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr":"0.0.0.0",
"ports":{}
}`},
hcl: []string{`
client_addr = "0.0.0.0"
ports {}
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
rt.DNSAddrs = []net.Addr{tcpAddr("0.0.0.0:8600"), udpAddr("0.0.0.0:8600")}
rt.HTTPAddrs = []net.Addr{tcpAddr("0.0.0.0:8500")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client addr and ports < 0",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr":"0.0.0.0",
"ports": { "dns":-1, "http":-2, "https":-3, "grpc":-4 }
}`},
hcl: []string{`
client_addr = "0.0.0.0"
ports { dns = -1 http = -2 https = -3 grpc = -4 }
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
rt.DNSPort = -1
rt.DNSAddrs = nil
rt.HTTPPort = -1
rt.HTTPAddrs = nil
// HTTPS and gRPC default to disabled so shouldn't be different from
// default rt.
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client addr and ports > 0",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr":"0.0.0.0",
"ports":{ "dns": 1, "http": 2, "https": 3, "grpc": 4 }
}`},
hcl: []string{`
client_addr = "0.0.0.0"
ports { dns = 1 http = 2 https = 3 grpc = 4 }
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
rt.DNSPort = 1
rt.DNSAddrs = []net.Addr{tcpAddr("0.0.0.0:1"), udpAddr("0.0.0.0:1")}
rt.HTTPPort = 2
rt.HTTPAddrs = []net.Addr{tcpAddr("0.0.0.0:2")}
rt.HTTPSPort = 3
rt.HTTPSAddrs = []net.Addr{tcpAddr("0.0.0.0:3")}
rt.GRPCPort = 4
rt.GRPCAddrs = []net.Addr{tcpAddr("0.0.0.0:4")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client addr, addresses and ports == 0",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr":"0.0.0.0",
"addresses": { "dns": "1.1.1.1", "http": "2.2.2.2", "https": "3.3.3.3", "grpc": "4.4.4.4" },
"ports":{}
}`},
hcl: []string{`
client_addr = "0.0.0.0"
addresses = { dns = "1.1.1.1" http = "2.2.2.2" https = "3.3.3.3" grpc = "4.4.4.4" }
ports {}
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
rt.DNSAddrs = []net.Addr{tcpAddr("1.1.1.1:8600"), udpAddr("1.1.1.1:8600")}
rt.HTTPAddrs = []net.Addr{tcpAddr("2.2.2.2:8500")}
// HTTPS and gRPC default to disabled so shouldn't be different from
// default rt.
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client addr, addresses and ports < 0",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr":"0.0.0.0",
"addresses": { "dns": "1.1.1.1", "http": "2.2.2.2", "https": "3.3.3.3", "grpc": "4.4.4.4" },
"ports": { "dns":-1, "http":-2, "https":-3, "grpc":-4 }
}`},
hcl: []string{`
client_addr = "0.0.0.0"
addresses = { dns = "1.1.1.1" http = "2.2.2.2" https = "3.3.3.3" grpc = "4.4.4.4" }
ports { dns = -1 http = -2 https = -3 grpc = -4 }
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
rt.DNSPort = -1
rt.DNSAddrs = nil
rt.HTTPPort = -1
rt.HTTPAddrs = nil
// HTTPS and gRPC default to disabled so shouldn't be different from
// default rt.
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client addr, addresses and ports",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr": "0.0.0.0",
"addresses": { "dns": "1.1.1.1", "http": "2.2.2.2", "https": "3.3.3.3", "grpc": "4.4.4.4" },
"ports":{ "dns":1, "http":2, "https":3, "grpc":4 }
}`},
hcl: []string{`
client_addr = "0.0.0.0"
addresses = { dns = "1.1.1.1" http = "2.2.2.2" https = "3.3.3.3" grpc = "4.4.4.4" }
ports { dns = 1 http = 2 https = 3 grpc = 4 }
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
rt.DNSPort = 1
rt.DNSAddrs = []net.Addr{tcpAddr("1.1.1.1:1"), udpAddr("1.1.1.1:1")}
rt.HTTPPort = 2
rt.HTTPAddrs = []net.Addr{tcpAddr("2.2.2.2:2")}
rt.HTTPSPort = 3
rt.HTTPSAddrs = []net.Addr{tcpAddr("3.3.3.3:3")}
rt.GRPCPort = 4
rt.GRPCAddrs = []net.Addr{tcpAddr("4.4.4.4:4")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client template and ports",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr": "{{ printf \"1.2.3.4 2001:db8::1\" }}",
"ports":{ "dns":1, "http":2, "https":3, "grpc":4 }
}`},
hcl: []string{`
client_addr = "{{ printf \"1.2.3.4 2001:db8::1\" }}"
ports { dns = 1 http = 2 https = 3 grpc = 4 }
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("1.2.3.4"), ipAddr("2001:db8::1")}
rt.DNSPort = 1
rt.DNSAddrs = []net.Addr{tcpAddr("1.2.3.4:1"), tcpAddr("[2001:db8::1]:1"), udpAddr("1.2.3.4:1"), udpAddr("[2001:db8::1]:1")}
rt.HTTPPort = 2
rt.HTTPAddrs = []net.Addr{tcpAddr("1.2.3.4:2"), tcpAddr("[2001:db8::1]:2")}
rt.HTTPSPort = 3
rt.HTTPSAddrs = []net.Addr{tcpAddr("1.2.3.4:3"), tcpAddr("[2001:db8::1]:3")}
rt.GRPCPort = 4
rt.GRPCAddrs = []net.Addr{tcpAddr("1.2.3.4:4"), tcpAddr("[2001:db8::1]:4")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client, address template and ports",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"client_addr": "{{ printf \"1.2.3.4 2001:db8::1\" }}",
"addresses": {
"dns": "{{ printf \"1.1.1.1 2001:db8::10 \" }}",
"http": "{{ printf \"2.2.2.2 unix://http 2001:db8::20 \" }}",
"https": "{{ printf \"3.3.3.3 unix://https 2001:db8::30 \" }}",
"grpc": "{{ printf \"4.4.4.4 unix://grpc 2001:db8::40 \" }}"
},
"ports":{ "dns":1, "http":2, "https":3, "grpc":4 }
}`},
hcl: []string{`
client_addr = "{{ printf \"1.2.3.4 2001:db8::1\" }}"
addresses = {
dns = "{{ printf \"1.1.1.1 2001:db8::10 \" }}"
http = "{{ printf \"2.2.2.2 unix://http 2001:db8::20 \" }}"
https = "{{ printf \"3.3.3.3 unix://https 2001:db8::30 \" }}"
grpc = "{{ printf \"4.4.4.4 unix://grpc 2001:db8::40 \" }}"
}
ports { dns = 1 http = 2 https = 3 grpc = 4 }
`},
expected: func(rt *RuntimeConfig) {
rt.ClientAddrs = []*net.IPAddr{ipAddr("1.2.3.4"), ipAddr("2001:db8::1")}
rt.DNSPort = 1
rt.DNSAddrs = []net.Addr{tcpAddr("1.1.1.1:1"), tcpAddr("[2001:db8::10]:1"), udpAddr("1.1.1.1:1"), udpAddr("[2001:db8::10]:1")}
rt.HTTPPort = 2
rt.HTTPAddrs = []net.Addr{tcpAddr("2.2.2.2:2"), unixAddr("unix://http"), tcpAddr("[2001:db8::20]:2")}
rt.HTTPSPort = 3
rt.HTTPSAddrs = []net.Addr{tcpAddr("3.3.3.3:3"), unixAddr("unix://https"), tcpAddr("[2001:db8::30]:3")}
rt.GRPCPort = 4
rt.GRPCAddrs = []net.Addr{tcpAddr("4.4.4.4:4"), unixAddr("unix://grpc"), tcpAddr("[2001:db8::40]:4")}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "advertise address lan template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "advertise_addr": "{{ printf \"1.2.3.4\" }}" }`},
hcl: []string{`advertise_addr = "{{ printf \"1.2.3.4\" }}"`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
rt.TaggedAddresses = map[string]string{
"lan": "1.2.3.4",
"lan_ipv4": "1.2.3.4",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "advertise address wan template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "advertise_addr_wan": "{{ printf \"1.2.3.4\" }}" }`},
hcl: []string{`advertise_addr_wan = "{{ printf \"1.2.3.4\" }}"`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
rt.TaggedAddresses = map[string]string{
"lan": "10.0.0.1",
"lan_ipv4": "10.0.0.1",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "advertise address lan with ports",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ports": {
"server": 1000,
"serf_lan": 2000,
"serf_wan": 3000
},
"advertise_addr": "1.2.3.4"
}`},
hcl: []string{`
ports {
server = 1000
serf_lan = 2000
serf_wan = 3000
}
advertise_addr = "1.2.3.4"
`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:1000")
rt.RPCBindAddr = tcpAddr("0.0.0.0:1000")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:2000")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:3000")
rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:2000")
rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:3000")
rt.SerfPortLAN = 2000
rt.SerfPortWAN = 3000
rt.ServerPort = 1000
rt.TaggedAddresses = map[string]string{
"lan": "1.2.3.4",
"lan_ipv4": "1.2.3.4",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "advertise address wan with ports",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ports": {
"server": 1000,
"serf_lan": 2000,
"serf_wan": 3000
},
"advertise_addr_wan": "1.2.3.4"
}`},
hcl: []string{`
ports {
server = 1000
serf_lan = 2000
serf_wan = 3000
}
advertise_addr_wan = "1.2.3.4"
`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("10.0.0.1")
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.RPCAdvertiseAddr = tcpAddr("10.0.0.1:1000")
rt.RPCBindAddr = tcpAddr("0.0.0.0:1000")
rt.SerfAdvertiseAddrLAN = tcpAddr("10.0.0.1:2000")
rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:3000")
rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:2000")
rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:3000")
rt.SerfPortLAN = 2000
rt.SerfPortWAN = 3000
rt.ServerPort = 1000
rt.TaggedAddresses = map[string]string{
"lan": "10.0.0.1",
"lan_ipv4": "10.0.0.1",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "allow disabling serf wan port",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ports": {
"serf_wan": -1
},
"advertise_addr_wan": "1.2.3.4"
}`},
hcl: []string{`
ports {
serf_wan = -1
}
advertise_addr_wan = "1.2.3.4"
`},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
rt.SerfAdvertiseAddrWAN = nil
rt.SerfBindAddrWAN = nil
rt.TaggedAddresses = map[string]string{
"lan": "10.0.0.1",
"lan_ipv4": "10.0.0.1",
"wan": "1.2.3.4",
"wan_ipv4": "1.2.3.4",
}
rt.DataDir = dataDir
rt.SerfPortWAN = -1
},
})
run(t, testCase{
desc: "serf bind address lan template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "serf_lan": "{{ printf \"1.2.3.4\" }}" }`},
hcl: []string{`serf_lan = "{{ printf \"1.2.3.4\" }}"`},
expected: func(rt *RuntimeConfig) {
rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "serf bind address wan template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "serf_wan": "{{ printf \"1.2.3.4\" }}" }`},
hcl: []string{`serf_wan = "{{ printf \"1.2.3.4\" }}"`},
expected: func(rt *RuntimeConfig) {
rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302")
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "dns recursor templates with deduplication",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "recursors": [ "{{ printf \"5.6.7.8:9999\" }}", "{{ printf \"1.2.3.4\" }}", "{{ printf \"5.6.7.8:9999\" }}" ] }`},
hcl: []string{`recursors = [ "{{ printf \"5.6.7.8:9999\" }}", "{{ printf \"1.2.3.4\" }}", "{{ printf \"5.6.7.8:9999\" }}" ] `},
expected: func(rt *RuntimeConfig) {
rt.DNSRecursors = []string{"5.6.7.8:9999", "1.2.3.4"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "start_join address template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "start_join": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
hcl: []string{`start_join = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
expected: func(rt *RuntimeConfig) {
rt.StartJoinAddrsLAN = []string{"1.2.3.4", "4.3.2.1"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "start_join_wan address template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "start_join_wan": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
hcl: []string{`start_join_wan = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
expected: func(rt *RuntimeConfig) {
rt.StartJoinAddrsWAN = []string{"1.2.3.4", "4.3.2.1"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "retry_join address template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "retry_join": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
hcl: []string{`retry_join = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinLAN = []string{"1.2.3.4", "4.3.2.1"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "retry_join_wan address template",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "retry_join_wan": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
hcl: []string{`retry_join_wan = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
expected: func(rt *RuntimeConfig) {
rt.RetryJoinWAN = []string{"1.2.3.4", "4.3.2.1"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "min/max ports for dynamic exposed listeners",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ports": {
"expose_min_port": 1234,
"expose_max_port": 5678
}
}`},
hcl: []string{`
ports {
expose_min_port = 1234
expose_max_port = 5678
}
`},
expected: func(rt *RuntimeConfig) {
rt.ExposeMinPort = 1234
rt.ExposeMaxPort = 5678
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "defaults for dynamic exposed listeners",
args: []string{`-data-dir=` + dataDir},
expected: func(rt *RuntimeConfig) {
rt.ExposeMinPort = 21500
rt.ExposeMaxPort = 21755
rt.DataDir = dataDir
},
})
// ------------------------------------------------------------
// precedence rules
//
run(t, testCase{
desc: "precedence: merge order",
args: []string{`-data-dir=` + dataDir},
json: []string{
`{
"bootstrap": true,
"bootstrap_expect": 1,
"datacenter": "a",
"start_join": ["a", "b"],
"node_meta": {"a":"b"}
}`,
`{
"bootstrap": false,
"bootstrap_expect": 0,
"datacenter":"b",
"start_join": ["c", "d"],
"node_meta": {"a":"c"}
}`,
},
hcl: []string{
`
bootstrap = true
bootstrap_expect = 1
datacenter = "a"
start_join = ["a", "b"]
node_meta = { "a" = "b" }
`,
`
bootstrap = false
bootstrap_expect = 0
datacenter = "b"
start_join = ["c", "d"]
node_meta = { "a" = "c" }
`,
},
expected: func(rt *RuntimeConfig) {
rt.Bootstrap = false
rt.BootstrapExpect = 0
rt.Datacenter = "b"
rt.PrimaryDatacenter = "b"
rt.StartJoinAddrsLAN = []string{"a", "b", "c", "d"}
rt.NodeMeta = map[string]string{"a": "c"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "precedence: flag before file",
json: []string{
`{
"advertise_addr": "1.2.3.4",
"advertise_addr_wan": "5.6.7.8",
"bootstrap":true,
"bootstrap_expect": 3,
"datacenter":"a",
"node_meta": {"a":"b"},
"recursors":["1.2.3.5", "5.6.7.9"],
"serf_lan": "a",
"serf_wan": "a",
"start_join":["a", "b"]
}`,
},
hcl: []string{`
advertise_addr = "1.2.3.4"
advertise_addr_wan = "5.6.7.8"
bootstrap = true
bootstrap_expect = 3
datacenter = "a"
node_meta = { "a" = "b" }
recursors = ["1.2.3.5", "5.6.7.9"]
serf_lan = "a"
serf_wan = "a"
start_join = ["a", "b"]
`,
},
args: []string{
`-advertise=1.1.1.1`,
`-advertise-wan=2.2.2.2`,
`-bootstrap=false`,
`-bootstrap-expect=0`,
`-datacenter=b`,
`-data-dir=` + dataDir,
`-join`, `c`, `-join=d`,
`-node-meta=a:c`,
`-recursor`, `1.2.3.6`, `-recursor=5.6.7.10`,
`-serf-lan-bind=3.3.3.3`,
`-serf-wan-bind=4.4.4.4`,
},
expected: func(rt *RuntimeConfig) {
rt.AdvertiseAddrLAN = ipAddr("1.1.1.1")
rt.AdvertiseAddrWAN = ipAddr("2.2.2.2")
rt.RPCAdvertiseAddr = tcpAddr("1.1.1.1:8300")
rt.SerfAdvertiseAddrLAN = tcpAddr("1.1.1.1:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("2.2.2.2:8302")
rt.Datacenter = "b"
rt.PrimaryDatacenter = "b"
rt.DNSRecursors = []string{"1.2.3.6", "5.6.7.10", "1.2.3.5", "5.6.7.9"}
rt.NodeMeta = map[string]string{"a": "c"}
rt.SerfBindAddrLAN = tcpAddr("3.3.3.3:8301")
rt.SerfBindAddrWAN = tcpAddr("4.4.4.4:8302")
rt.StartJoinAddrsLAN = []string{"c", "d", "a", "b"}
rt.TaggedAddresses = map[string]string{
"lan": "1.1.1.1",
"lan_ipv4": "1.1.1.1",
"wan": "2.2.2.2",
"wan_ipv4": "2.2.2.2",
}
rt.DataDir = dataDir
},
})
// ------------------------------------------------------------
// transformations
//
run(t, testCase{
desc: "raft performance scaling",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "performance": { "raft_multiplier": 9} }`},
hcl: []string{`performance = { raft_multiplier=9 }`},
expected: func(rt *RuntimeConfig) {
rt.ConsulRaftElectionTimeout = 9 * 1000 * time.Millisecond
rt.ConsulRaftHeartbeatTimeout = 9 * 1000 * time.Millisecond
rt.ConsulRaftLeaderLeaseTimeout = 9 * 500 * time.Millisecond
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "Serf Allowed CIDRS LAN, multiple values from flags",
args: []string{`-data-dir=` + dataDir, `-serf-lan-allowed-cidrs=127.0.0.0/4`, `-serf-lan-allowed-cidrs=192.168.0.0/24`},
json: []string{},
hcl: []string{},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.SerfAllowedCIDRsLAN = []net.IPNet{*(parseCIDR(t, "127.0.0.0/4")), *(parseCIDR(t, "192.168.0.0/24"))}
},
})
run(t, testCase{
desc: "Serf Allowed CIDRS LAN/WAN, multiple values from HCL/JSON",
args: []string{`-data-dir=` + dataDir},
json: []string{`{"serf_lan_allowed_cidrs": ["127.0.0.0/4", "192.168.0.0/24"]}`,
`{"serf_wan_allowed_cidrs": ["10.228.85.46/25"]}`},
hcl: []string{`serf_lan_allowed_cidrs=["127.0.0.0/4", "192.168.0.0/24"]`,
`serf_wan_allowed_cidrs=["10.228.85.46/25"]`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.SerfAllowedCIDRsLAN = []net.IPNet{*(parseCIDR(t, "127.0.0.0/4")), *(parseCIDR(t, "192.168.0.0/24"))}
rt.SerfAllowedCIDRsWAN = []net.IPNet{*(parseCIDR(t, "10.228.85.46/25"))}
},
})
run(t, testCase{
desc: "Serf Allowed CIDRS WAN, multiple values from flags",
args: []string{`-data-dir=` + dataDir, `-serf-wan-allowed-cidrs=192.168.4.0/24`, `-serf-wan-allowed-cidrs=192.168.3.0/24`},
json: []string{},
hcl: []string{},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.SerfAllowedCIDRsWAN = []net.IPNet{*(parseCIDR(t, "192.168.4.0/24")), *(parseCIDR(t, "192.168.3.0/24"))}
},
})
// ------------------------------------------------------------
// validations
//
run(t, testCase{
desc: "invalid input",
args: []string{`-data-dir=` + dataDir},
json: []string{`this is not JSON`},
hcl: []string{`*** 0123 this is not HCL`},
expectedErr: "failed to parse",
})
run(t, testCase{
desc: "datacenter is lower-cased",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "datacenter": "A" }`},
hcl: []string{`datacenter = "A"`},
expected: func(rt *RuntimeConfig) {
rt.Datacenter = "a"
rt.PrimaryDatacenter = "a"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "acl_datacenter is lower-cased",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "acl_datacenter": "A" }`},
hcl: []string{`acl_datacenter = "A"`},
expected: func(rt *RuntimeConfig) {
rt.ACLsEnabled = true
rt.DataDir = dataDir
rt.PrimaryDatacenter = "a"
},
expectedWarnings: []string{`The 'acl_datacenter' field is deprecated. Use the 'primary_datacenter' field instead.`},
})
run(t, testCase{
desc: "acl_replication_token enables acl replication",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "acl_replication_token": "a" }`},
hcl: []string{`acl_replication_token = "a"`},
expectedWarnings: []string{deprecationWarning("acl_replication_token", "acl.tokens.replication")},
expected: func(rt *RuntimeConfig) {
rt.ACLTokens.ACLReplicationToken = "a"
rt.ACLTokenReplication = true
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "acl.tokens.replace does not enable acl replication",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "acl": { "tokens": { "replication": "a" }}}`},
hcl: []string{`acl { tokens { replication = "a"}}`},
expected: func(rt *RuntimeConfig) {
rt.ACLTokens.ACLReplicationToken = "a"
rt.ACLTokenReplication = false
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "acl_enforce_version_8 is deprecated",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "acl_enforce_version_8": true }`},
hcl: []string{`acl_enforce_version_8 = true`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
},
expectedWarnings: []string{`config key "acl_enforce_version_8" is deprecated and should be removed`},
})
run(t, testCase{
desc: "advertise address detect fails v4",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "0.0.0.0"}`},
hcl: []string{`bind_addr = "0.0.0.0"`},
opts: LoadOpts{
getPrivateIPv4: func() ([]*net.IPAddr, error) {
return nil, errors.New("some error")
},
},
expectedErr: "Error detecting private IPv4 address: some error",
})
run(t, testCase{
desc: "advertise address detect none v4",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "0.0.0.0"}`},
hcl: []string{`bind_addr = "0.0.0.0"`},
opts: LoadOpts{
getPrivateIPv4: func() ([]*net.IPAddr, error) {
return nil, nil
},
},
expectedErr: "No private IPv4 address found",
})
run(t, testCase{
desc: "advertise address detect multiple v4",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "0.0.0.0"}`},
hcl: []string{`bind_addr = "0.0.0.0"`},
opts: LoadOpts{
getPrivateIPv4: func() ([]*net.IPAddr, error) {
return []*net.IPAddr{ipAddr("1.1.1.1"), ipAddr("2.2.2.2")}, nil
},
},
expectedErr: "Multiple private IPv4 addresses found. Please configure one",
})
run(t, testCase{
desc: "advertise address detect fails v6",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "::"}`},
hcl: []string{`bind_addr = "::"`},
opts: LoadOpts{
getPublicIPv6: func() ([]*net.IPAddr, error) {
return nil, errors.New("some error")
},
},
expectedErr: "Error detecting public IPv6 address: some error",
})
run(t, testCase{
desc: "advertise address detect none v6",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "::"}`},
hcl: []string{`bind_addr = "::"`},
opts: LoadOpts{
getPublicIPv6: func() ([]*net.IPAddr, error) {
return nil, nil
},
},
expectedErr: "No public IPv6 address found",
})
run(t, testCase{
desc: "advertise address detect multiple v6",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "::"}`},
hcl: []string{`bind_addr = "::"`},
opts: LoadOpts{
getPublicIPv6: func() ([]*net.IPAddr, error) {
return []*net.IPAddr{ipAddr("dead:beef::1"), ipAddr("dead:beef::2")}, nil
},
},
expectedErr: "Multiple public IPv6 addresses found. Please configure one",
})
run(t, testCase{
desc: "ae_interval is overridden by NonUserSource",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "ae_interval": "-1s" }`},
hcl: []string{`ae_interval = "-1s"`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.AEInterval = time.Minute
},
})
run(t, testCase{
desc: "primary_datacenter invalid",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "primary_datacenter": "%" }`},
hcl: []string{`primary_datacenter = "%"`},
expectedErr: `primary_datacenter can only contain lowercase alphanumeric, - or _ characters.`,
})
run(t, testCase{
desc: "acl_datacenter deprecated",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "acl_datacenter": "ab" }`},
hcl: []string{`acl_datacenter = "ab"`},
expectedWarnings: []string{`The 'acl_datacenter' field is deprecated. Use the 'primary_datacenter' field instead.`},
expected: func(rt *RuntimeConfig) {
rt.ACLsEnabled = true
rt.PrimaryDatacenter = "ab"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "autopilot.max_trailing_logs invalid",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "autopilot": { "max_trailing_logs": -1 } }`},
hcl: []string{`autopilot = { max_trailing_logs = -1 }`},
expectedErr: "autopilot.max_trailing_logs cannot be -1. Must be greater than or equal to zero",
})
run(t, testCase{
desc: "bind_addr cannot be empty",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "" }`},
hcl: []string{`bind_addr = ""`},
expectedErr: "bind_addr cannot be empty",
})
run(t, testCase{
desc: "bind_addr does not allow multiple addresses",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "1.1.1.1 2.2.2.2" }`},
hcl: []string{`bind_addr = "1.1.1.1 2.2.2.2"`},
expectedErr: "bind_addr cannot contain multiple addresses",
})
run(t, testCase{
desc: "bind_addr cannot be a unix socket",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "bind_addr": "unix:///foo" }`},
hcl: []string{`bind_addr = "unix:///foo"`},
expectedErr: "bind_addr cannot be a unix socket",
})
run(t, testCase{
desc: "bootstrap without server",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap": true }`},
hcl: []string{`bootstrap = true`},
expectedErr: "'bootstrap = true' requires 'server = true'",
})
run(t, testCase{
desc: "bootstrap-expect without server",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap_expect": 3 }`},
hcl: []string{`bootstrap_expect = 3`},
expectedErr: "'bootstrap_expect > 0' requires 'server = true'",
})
run(t, testCase{
desc: "bootstrap-expect invalid",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap_expect": -1 }`},
hcl: []string{`bootstrap_expect = -1`},
expectedErr: "bootstrap_expect cannot be -1. Must be greater than or equal to zero",
})
run(t, testCase{
desc: "bootstrap-expect and dev mode",
args: []string{
`-dev`,
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap_expect": 3, "server": true }`},
hcl: []string{`bootstrap_expect = 3 server = true`},
expectedErr: "'bootstrap_expect > 0' not allowed in dev mode",
})
run(t, testCase{
desc: "bootstrap-expect and bootstrap",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap": true, "bootstrap_expect": 3, "server": true }`},
hcl: []string{`bootstrap = true bootstrap_expect = 3 server = true`},
expectedErr: "'bootstrap_expect > 0' and 'bootstrap = true' are mutually exclusive",
})
run(t, testCase{
desc: "bootstrap-expect=1 equals bootstrap",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap_expect": 1, "server": true }`},
hcl: []string{`bootstrap_expect = 1 server = true`},
expected: func(rt *RuntimeConfig) {
rt.Bootstrap = true
rt.BootstrapExpect = 0
rt.LeaveOnTerm = false
rt.ServerMode = true
rt.SkipLeaveOnInt = true
rt.DataDir = dataDir
rt.RPCConfig.EnableStreaming = true
},
expectedWarnings: []string{"BootstrapExpect is set to 1; this is the same as Bootstrap mode.", "bootstrap = true: do not enable unless necessary"},
})
run(t, testCase{
desc: "bootstrap-expect=2 warning",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap_expect": 2, "server": true }`},
hcl: []string{`bootstrap_expect = 2 server = true`},
expected: func(rt *RuntimeConfig) {
rt.BootstrapExpect = 2
rt.LeaveOnTerm = false
rt.ServerMode = true
rt.SkipLeaveOnInt = true
rt.DataDir = dataDir
rt.RPCConfig.EnableStreaming = true
},
expectedWarnings: []string{
`bootstrap_expect = 2: A cluster with 2 servers will provide no failure tolerance. See https://www.consul.io/docs/internals/consensus.html#deployment-table`,
`bootstrap_expect > 0: expecting 2 servers`,
},
})
run(t, testCase{
desc: "bootstrap-expect > 2 but even warning",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "bootstrap_expect": 4, "server": true }`},
hcl: []string{`bootstrap_expect = 4 server = true`},
expected: func(rt *RuntimeConfig) {
rt.BootstrapExpect = 4
rt.LeaveOnTerm = false
rt.ServerMode = true
rt.SkipLeaveOnInt = true
rt.DataDir = dataDir
rt.RPCConfig.EnableStreaming = true
},
expectedWarnings: []string{
`bootstrap_expect is even number: A cluster with an even number of servers does not achieve optimum fault tolerance. See https://www.consul.io/docs/internals/consensus.html#deployment-table`,
`bootstrap_expect > 0: expecting 4 servers`,
},
})
run(t, testCase{
desc: "client mode sets LeaveOnTerm and SkipLeaveOnInt correctly",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "server": false }`},
hcl: []string{` server = false`},
expected: func(rt *RuntimeConfig) {
rt.LeaveOnTerm = true
rt.ServerMode = false
rt.SkipLeaveOnInt = false
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "client does not allow socket",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "client_addr": "unix:///foo" }`},
hcl: []string{`client_addr = "unix:///foo"`},
expectedErr: "client_addr cannot be a unix socket",
})
run(t, testCase{
desc: "datacenter invalid",
args: []string{`-data-dir=` + dataDir},
json: []string{`{ "datacenter": "%" }`},
hcl: []string{`datacenter = "%"`},
expectedErr: `datacenter can only contain lowercase alphanumeric, - or _ characters.`,
})
run(t, testCase{
desc: "dns does not allow socket",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "addresses": {"dns": "unix:///foo" } }`},
hcl: []string{`addresses = { dns = "unix:///foo" }`},
expectedErr: "DNS address cannot be a unix socket",
})
run(t, testCase{
desc: "ui enabled and dir specified",
args: []string{
`-datacenter=a`,
`-data-dir=` + dataDir,
},
json: []string{`{ "ui_config": { "enabled": true, "dir": "a" } }`},
hcl: []string{`ui_config { enabled = true dir = "a"}`},
expectedErr: "Both the ui_config.enabled and ui_config.dir (or -ui and -ui-dir) were specified, please provide only one.\n" +
"If trying to use your own web UI resources, use ui_config.dir or the -ui-dir flag.\n" +
"The web UI is included in the binary so use ui_config.enabled or the -ui flag to enable it",
})
// test ANY address failures
// to avoid combinatory explosion for tests use 0.0.0.0, :: or [::] but not all of them
run(t, testCase{
desc: "advertise_addr any",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "advertise_addr": "0.0.0.0" }`},
hcl: []string{`advertise_addr = "0.0.0.0"`},
expectedErr: "Advertise address cannot be 0.0.0.0, :: or [::]",
})
run(t, testCase{
desc: "advertise_addr_wan any",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "advertise_addr_wan": "::" }`},
hcl: []string{`advertise_addr_wan = "::"`},
expectedErr: "Advertise WAN address cannot be 0.0.0.0, :: or [::]",
})
run(t, testCase{
desc: "recursors any",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "recursors": ["::"] }`},
hcl: []string{`recursors = ["::"]`},
expectedErr: "DNS recursor address cannot be 0.0.0.0, :: or [::]",
})
run(t, testCase{
desc: "dns_config.udp_answer_limit invalid",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "dns_config": { "udp_answer_limit": -1 } }`},
hcl: []string{`dns_config = { udp_answer_limit = -1 }`},
expectedErr: "dns_config.udp_answer_limit cannot be -1. Must be greater than or equal to zero",
})
run(t, testCase{
desc: "dns_config.a_record_limit invalid",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "dns_config": { "a_record_limit": -1 } }`},
hcl: []string{`dns_config = { a_record_limit = -1 }`},
expectedErr: "dns_config.a_record_limit cannot be -1. Must be greater than or equal to zero",
})
run(t, testCase{
desc: "performance.raft_multiplier < 0",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "performance": { "raft_multiplier": -1 } }`},
hcl: []string{`performance = { raft_multiplier = -1 }`},
expectedErr: `performance.raft_multiplier cannot be -1. Must be between 1 and 10`,
})
run(t, testCase{
desc: "performance.raft_multiplier == 0",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "performance": { "raft_multiplier": 0 } }`},
hcl: []string{`performance = { raft_multiplier = 0 }`},
expectedErr: `performance.raft_multiplier cannot be 0. Must be between 1 and 10`,
})
run(t, testCase{
desc: "performance.raft_multiplier > 10",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "performance": { "raft_multiplier": 20 } }`},
hcl: []string{`performance = { raft_multiplier = 20 }`},
expectedErr: `performance.raft_multiplier cannot be 20. Must be between 1 and 10`,
})
run(t, testCase{
desc: "node_name invalid",
args: []string{
`-data-dir=` + dataDir,
`-node=`,
},
opts: LoadOpts{
hostname: func() (string, error) { return "", nil },
},
expectedErr: "node_name cannot be empty",
})
run(t, testCase{
desc: "node_meta key too long",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "dns_config": { "udp_answer_limit": 1 } }`,
`{ "node_meta": { "` + randomString(130) + `": "a" } }`,
},
hcl: []string{
`dns_config = { udp_answer_limit = 1 }`,
`node_meta = { "` + randomString(130) + `" = "a" }`,
},
expectedErr: "Key is too long (limit: 128 characters)",
})
run(t, testCase{
desc: "node_meta value too long",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "dns_config": { "udp_answer_limit": 1 } }`,
`{ "node_meta": { "a": "` + randomString(520) + `" } }`,
},
hcl: []string{
`dns_config = { udp_answer_limit = 1 }`,
`node_meta = { "a" = "` + randomString(520) + `" }`,
},
expectedErr: "Value is too long (limit: 512 characters)",
})
run(t, testCase{
desc: "node_meta too many keys",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "dns_config": { "udp_answer_limit": 1 } }`,
`{ "node_meta": {` + metaPairs(70, "json") + `} }`,
},
hcl: []string{
`dns_config = { udp_answer_limit = 1 }`,
`node_meta = {` + metaPairs(70, "hcl") + ` }`,
},
expectedErr: "Node metadata cannot contain more than 64 key/value pairs",
})
run(t, testCase{
desc: "unique listeners dns vs http",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"client_addr": "1.2.3.4",
"ports": { "dns": 1000, "http": 1000 }
}`},
hcl: []string{`
client_addr = "1.2.3.4"
ports = { dns = 1000 http = 1000 }
`},
expectedErr: "HTTP address 1.2.3.4:1000 already configured for DNS",
})
run(t, testCase{
desc: "unique listeners dns vs https",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"client_addr": "1.2.3.4",
"ports": { "dns": 1000, "https": 1000 }
}`},
hcl: []string{`
client_addr = "1.2.3.4"
ports = { dns = 1000 https = 1000 }
`},
expectedErr: "HTTPS address 1.2.3.4:1000 already configured for DNS",
})
run(t, testCase{
desc: "unique listeners http vs https",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"client_addr": "1.2.3.4",
"ports": { "http": 1000, "https": 1000 }
}`},
hcl: []string{`
client_addr = "1.2.3.4"
ports = { http = 1000 https = 1000 }
`},
expectedErr: "HTTPS address 1.2.3.4:1000 already configured for HTTP",
})
run(t, testCase{
desc: "unique advertise addresses HTTP vs RPC",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"addresses": { "http": "10.0.0.1" },
"ports": { "http": 1000, "server": 1000 }
}`},
hcl: []string{`
addresses = { http = "10.0.0.1" }
ports = { http = 1000 server = 1000 }
`},
expectedErr: "RPC Advertise address 10.0.0.1:1000 already configured for HTTP",
})
run(t, testCase{
desc: "unique advertise addresses RPC vs Serf LAN",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"ports": { "server": 1000, "serf_lan": 1000 }
}`},
hcl: []string{`
ports = { server = 1000 serf_lan = 1000 }
`},
expectedErr: "Serf Advertise LAN address 10.0.0.1:1000 already configured for RPC Advertise",
})
run(t, testCase{
desc: "unique advertise addresses RPC vs Serf WAN",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"ports": { "server": 1000, "serf_wan": 1000 }
}`},
hcl: []string{`
ports = { server = 1000 serf_wan = 1000 }
`},
expectedErr: "Serf Advertise WAN address 10.0.0.1:1000 already configured for RPC Advertise",
})
run(t, testCase{
desc: "http use_cache defaults to true",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"http_config": {}
}`},
hcl: []string{`
http_config = {}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.HTTPUseCache = true
},
})
run(t, testCase{
desc: "http use_cache is enabled when true",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"http_config": { "use_cache": true }
}`},
hcl: []string{`
http_config = { use_cache = true }
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.HTTPUseCache = true
},
})
run(t, testCase{
desc: "http use_cache is disabled when false",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"http_config": { "use_cache": false }
}`},
hcl: []string{`
http_config = { use_cache = false }
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.HTTPUseCache = false
},
})
run(t, testCase{
desc: "sidecar_service can't have ID",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"service": {
"name": "web",
"port": 1234,
"connect": {
"sidecar_service": {
"ID": "random-sidecar-id"
}
}
}
}`},
hcl: []string{`
service {
name = "web"
port = 1234
connect {
sidecar_service {
ID = "random-sidecar-id"
}
}
}
`},
expectedErr: "sidecar_service can't specify an ID",
})
run(t, testCase{
desc: "sidecar_service can't have nested sidecar",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"service": {
"name": "web",
"port": 1234,
"connect": {
"sidecar_service": {
"connect": {
"sidecar_service": {}
}
}
}
}
}`},
hcl: []string{`
service {
name = "web"
port = 1234
connect {
sidecar_service {
connect {
sidecar_service {
}
}
}
}
}
`},
expectedErr: "sidecar_service can't have a nested sidecar_service",
})
run(t, testCase{
desc: "telemetry.prefix_filter cannot be empty",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"telemetry": { "prefix_filter": [""] }
}`},
hcl: []string{`
telemetry = { prefix_filter = [""] }
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
},
expectedWarnings: []string{"Cannot have empty filter rule in prefix_filter"},
})
run(t, testCase{
desc: "telemetry.prefix_filter must start with + or -",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"telemetry": { "prefix_filter": ["+foo", "-bar", "nix"] }
}`},
hcl: []string{`
telemetry = { prefix_filter = ["+foo", "-bar", "nix"] }
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.Telemetry.AllowedPrefixes = []string{"foo"}
rt.Telemetry.BlockedPrefixes = []string{"bar"}
},
expectedWarnings: []string{`Filter rule must begin with either '+' or '-': "nix"`},
})
run(t, testCase{
desc: "encrypt has invalid key",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{ "encrypt": "this is not a valid key" }`},
hcl: []string{` encrypt = "this is not a valid key" `},
expectedErr: "encrypt has invalid key: illegal base64 data at input byte 4",
})
run(t, testCase{
desc: "multiple check files",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "check": { "name": "a", "args": ["/bin/true"], "interval": "1s" } }`,
`{ "check": { "name": "b", "args": ["/bin/false"], "interval": "1s" } }`,
},
hcl: []string{
`check = { name = "a" args = ["/bin/true"] interval = "1s"}`,
`check = { name = "b" args = ["/bin/false"] interval = "1s" }`,
},
expected: func(rt *RuntimeConfig) {
rt.Checks = []*structs.CheckDefinition{
{Name: "a", ScriptArgs: []string{"/bin/true"}, OutputMaxSize: checks.DefaultBufSize, Interval: time.Second},
{Name: "b", ScriptArgs: []string{"/bin/false"}, OutputMaxSize: checks.DefaultBufSize, Interval: time.Second},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "grpc check",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "check": { "name": "a", "grpc": "localhost:12345/foo", "grpc_use_tls": true, "interval": "1s" } }`,
},
hcl: []string{
`check = { name = "a" grpc = "localhost:12345/foo", grpc_use_tls = true interval = "1s" }`,
},
expected: func(rt *RuntimeConfig) {
rt.Checks = []*structs.CheckDefinition{
{Name: "a", GRPC: "localhost:12345/foo", GRPCUseTLS: true, OutputMaxSize: checks.DefaultBufSize, Interval: time.Second},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "h2ping check without h2ping_use_tls set",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "check": { "name": "a", "h2ping": "localhost:55555", "interval": "5s" } }`,
},
hcl: []string{
`check = { name = "a" h2ping = "localhost:55555" interval = "5s" }`,
},
expected: func(rt *RuntimeConfig) {
rt.Checks = []*structs.CheckDefinition{
{Name: "a", H2PING: "localhost:55555", H2PingUseTLS: true, OutputMaxSize: checks.DefaultBufSize, Interval: 5 * time.Second},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "h2ping check with h2ping_use_tls set to false",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "check": { "name": "a", "h2ping": "localhost:55555", "h2ping_use_tls": false, "interval": "5s" } }`,
},
hcl: []string{
`check = { name = "a" h2ping = "localhost:55555" h2ping_use_tls = false interval = "5s" }`,
},
expected: func(rt *RuntimeConfig) {
rt.Checks = []*structs.CheckDefinition{
{Name: "a", H2PING: "localhost:55555", H2PingUseTLS: false, OutputMaxSize: checks.DefaultBufSize, Interval: 5 * time.Second},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "alias check with no node",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "check": { "name": "a", "alias_service": "foo" } }`,
},
hcl: []string{
`check = { name = "a", alias_service = "foo" }`,
},
expected: func(rt *RuntimeConfig) {
rt.Checks = []*structs.CheckDefinition{
{Name: "a", AliasService: "foo", OutputMaxSize: checks.DefaultBufSize},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "multiple service files",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80 } }`,
`{ "service": { "name": "b", "port": 90, "meta": {"my": "value"}, "weights": {"passing": 13} } }`,
},
hcl: []string{
`service = { name = "a" port = 80 }`,
`service = { name = "b" port = 90 meta={my="value"}, weights={passing=13}}`,
},
expected: func(rt *RuntimeConfig) {
rt.Services = []*structs.ServiceDefinition{
{
Name: "a",
Port: 80,
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
{
Name: "b",
Port: 90,
Meta: map[string]string{"my": "value"},
Weights: &structs.Weights{
Passing: 13,
Warning: 1,
},
},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "service with wrong meta: too long key",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80, "meta": { "` + randomString(520) + `": "metaValue" } } }`,
},
hcl: []string{
`service = { name = "a" port = 80, meta={` + randomString(520) + `="metaValue"} }`,
},
expectedErr: `Key is too long`,
})
run(t, testCase{
desc: "service with wrong meta: too long value",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80, "meta": { "a": "` + randomString(520) + `" } } }`,
},
hcl: []string{
`service = { name = "a" port = 80, meta={a="` + randomString(520) + `"} }`,
},
expectedErr: `Value is too long`,
})
run(t, testCase{
desc: "service with wrong meta: too many meta",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80, "meta": { ` + metaPairs(70, "json") + `} } }`,
},
hcl: []string{
`service = { name = "a" port = 80 meta={` + metaPairs(70, "hcl") + `} }`,
},
expectedErr: `invalid meta for service a: Node metadata cannot contain more than 64 key`,
})
run(t, testCase{
desc: "translated keys",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{
"service": {
"name": "a",
"port": 80,
"tagged_addresses": {
"wan": {
"address": "198.18.3.4",
"port": 443
}
},
"enable_tag_override": true,
"check": {
"id": "x",
"name": "y",
"DockerContainerID": "z",
"DeregisterCriticalServiceAfter": "10s",
"ScriptArgs": ["a", "b"],
"Interval": "2s"
}
}
}`,
},
hcl: []string{
`service = {
name = "a"
port = 80
enable_tag_override = true
tagged_addresses = {
wan = {
address = "198.18.3.4"
port = 443
}
}
check = {
id = "x"
name = "y"
DockerContainerID = "z"
DeregisterCriticalServiceAfter = "10s"
ScriptArgs = ["a", "b"]
Interval = "2s"
}
}`,
},
expected: func(rt *RuntimeConfig) {
rt.Services = []*structs.ServiceDefinition{
{
Name: "a",
Port: 80,
TaggedAddresses: map[string]structs.ServiceAddress{
"wan": {
Address: "198.18.3.4",
Port: 443,
},
},
EnableTagOverride: true,
Checks: []*structs.CheckType{
{
CheckID: "x",
Name: "y",
DockerContainerID: "z",
DeregisterCriticalServiceAfter: 10 * time.Second,
ScriptArgs: []string{"a", "b"},
OutputMaxSize: checks.DefaultBufSize,
Interval: 2 * time.Second,
},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "ignore snapshot_agent sub-object",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "snapshot_agent": { "dont": "care" } }`,
},
hcl: []string{
`snapshot_agent = { dont = "care" }`,
},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
},
})
run(t, testCase{
// Test that slices in structured config are preserved by
// decode.HookWeakDecodeFromSlice.
desc: "service.connectsidecar_service with checks and upstreams",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"service": {
"name": "web",
"port": 1234,
"connect": {
"sidecar_service": {
"port": 2345,
"checks": [
{
"TCP": "127.0.0.1:2345",
"Interval": "10s"
}
],
"proxy": {
"expose": {
"checks": true,
"paths": [
{
"path": "/health",
"local_path_port": 8080,
"listener_port": 21500,
"protocol": "http"
}
]
},
"mode": "transparent",
"transparent_proxy": {
"outbound_listener_port": 10101,
"dialed_directly": true
},
"upstreams": [
{
"destination_name": "db",
"local_bind_port": 7000
},
{
"destination_name": "db2",
"local_bind_socket_path": "/tmp/socketpath",
"local_bind_socket_mode": "0644"
}
]
}
}
}
}
}`},
hcl: []string{`
service {
name = "web"
port = 1234
connect {
sidecar_service {
port = 2345
checks = [
{
tcp = "127.0.0.1:2345"
interval = "10s"
}
]
proxy {
expose {
checks = true
paths = [
{
path = "/health"
local_path_port = 8080
listener_port = 21500
protocol = "http"
}
]
}
mode = "transparent"
transparent_proxy = {
outbound_listener_port = 10101
dialed_directly = true
}
upstreams = [
{
destination_name = "db"
local_bind_port = 7000
},
{
destination_name = "db2",
local_bind_socket_path = "/tmp/socketpath",
local_bind_socket_mode = "0644"
}
]
}
}
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.Services = []*structs.ServiceDefinition{
{
Name: "web",
Port: 1234,
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Port: 2345,
Checks: structs.CheckTypes{
{
TCP: "127.0.0.1:2345",
Interval: 10 * time.Second,
OutputMaxSize: checks.DefaultBufSize,
},
},
Proxy: &structs.ConnectProxyConfig{
Expose: structs.ExposeConfig{
Checks: true,
Paths: []structs.ExposePath{
{
Path: "/health",
LocalPathPort: 8080,
ListenerPort: 21500,
Protocol: "http",
},
},
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
Upstreams: structs.Upstreams{
structs.Upstream{
DestinationType: "service",
DestinationName: "db",
LocalBindPort: 7000,
},
structs.Upstream{
DestinationType: "service",
DestinationName: "db2",
LocalBindSocketPath: "/tmp/socketpath",
LocalBindSocketMode: "0644",
},
},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
}
},
})
run(t, testCase{
// Test that slices in structured config are preserved by
// decode.HookWeakDecodeFromSlice.
desc: "services.connect.sidecar_service with checks and upstreams",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"services": [{
"name": "web",
"port": 1234,
"connect": {
"sidecar_service": {
"port": 2345,
"checks": [
{
"TCP": "127.0.0.1:2345",
"Interval": "10s"
}
],
"proxy": {
"expose": {
"checks": true,
"paths": [
{
"path": "/health",
"local_path_port": 8080,
"listener_port": 21500,
"protocol": "http"
}
]
},
"mode": "transparent",
"transparent_proxy": {
"outbound_listener_port": 10101,
"dialed_directly": true
},
"upstreams": [
{
"destination_name": "db",
"local_bind_port": 7000
}
]
}
}
}
}]
}`},
hcl: []string{`
services = [{
name = "web"
port = 1234
connect {
sidecar_service {
port = 2345
checks = [
{
tcp = "127.0.0.1:2345"
interval = "10s"
}
]
proxy {
expose {
checks = true
paths = [
{
path = "/health"
local_path_port = 8080
listener_port = 21500
protocol = "http"
}
]
}
mode = "transparent"
transparent_proxy = {
outbound_listener_port = 10101
dialed_directly = true
}
upstreams = [
{
destination_name = "db"
local_bind_port = 7000
},
]
}
}
}
}]
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.Services = []*structs.ServiceDefinition{
{
Name: "web",
Port: 1234,
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Port: 2345,
Checks: structs.CheckTypes{
{
TCP: "127.0.0.1:2345",
Interval: 10 * time.Second,
OutputMaxSize: checks.DefaultBufSize,
},
},
Proxy: &structs.ConnectProxyConfig{
Expose: structs.ExposeConfig{
Checks: true,
Paths: []structs.ExposePath{
{
Path: "/health",
LocalPathPort: 8080,
ListenerPort: 21500,
Protocol: "http",
},
},
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
Upstreams: structs.Upstreams{
structs.Upstream{
DestinationType: "service",
DestinationName: "db",
LocalBindPort: 7000,
},
},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
}
},
})
run(t, testCase{
// This tests checks that VerifyServerHostname implies VerifyOutgoing
desc: "verify_server_hostname implies verify_outgoing",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"verify_server_hostname": true
}`},
hcl: []string{`
verify_server_hostname = true
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.VerifyServerHostname = true
rt.VerifyOutgoing = true
},
})
run(t, testCase{
desc: "auto_encrypt.allow_tls works implies connect",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"verify_incoming": true,
"auto_encrypt": { "allow_tls": true },
"server": true
}`},
hcl: []string{`
verify_incoming = true
auto_encrypt { allow_tls = true }
server = true
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.VerifyIncoming = true
rt.AutoEncryptAllowTLS = true
rt.ConnectEnabled = true
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "auto_encrypt.allow_tls works with verify_incoming",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"verify_incoming": true,
"auto_encrypt": { "allow_tls": true },
"server": true
}`},
hcl: []string{`
verify_incoming = true
auto_encrypt { allow_tls = true }
server = true
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.VerifyIncoming = true
rt.AutoEncryptAllowTLS = true
rt.ConnectEnabled = true
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "auto_encrypt.allow_tls works with verify_incoming_rpc",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"verify_incoming_rpc": true,
"auto_encrypt": { "allow_tls": true },
"server": true
}`},
hcl: []string{`
verify_incoming_rpc = true
auto_encrypt { allow_tls = true }
server = true
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.VerifyIncomingRPC = true
rt.AutoEncryptAllowTLS = true
rt.ConnectEnabled = true
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "auto_encrypt.allow_tls warns without verify_incoming or verify_incoming_rpc",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"auto_encrypt": { "allow_tls": true },
"server": true
}`},
hcl: []string{`
auto_encrypt { allow_tls = true }
server = true
`},
expectedWarnings: []string{"if auto_encrypt.allow_tls is turned on, either verify_incoming or verify_incoming_rpc should be enabled. It is necessary to turn it off during a migration to TLS, but it should definitely be turned on afterwards."},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.AutoEncryptAllowTLS = true
rt.ConnectEnabled = true
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.RPCConfig.EnableStreaming = true
},
})
run(t, testCase{
desc: "rpc.enable_streaming = true has no effect when not running in server mode",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"rpc": { "enable_streaming": true }
}`},
hcl: []string{`
rpc { enable_streaming = true }
`},
expectedWarnings: []string{"rpc.enable_streaming = true has no effect when not running in server mode"},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
// rpc.enable_streaming make no sense in not-server mode
rt.RPCConfig.EnableStreaming = true
rt.ServerMode = false
},
})
run(t, testCase{
desc: "use_streaming_backend = true requires rpc.enable_streaming on servers to work properly",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"use_streaming_backend": true,
"rpc": {"enable_streaming": false},
"server": true
}`},
hcl: []string{`
use_streaming_backend = true
rpc { enable_streaming = false }
server = true
`},
expectedWarnings: []string{"use_streaming_backend = true requires rpc.enable_streaming on servers to work properly"},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.UseStreamingBackend = true
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
},
})
run(t, testCase{
desc: "auto_encrypt.allow_tls errors in client mode",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"auto_encrypt": { "allow_tls": true },
"server": false
}`},
hcl: []string{`
auto_encrypt { allow_tls = true }
server = false
`},
expectedErr: "auto_encrypt.allow_tls can only be used on a server.",
})
run(t, testCase{
desc: "auto_encrypt.tls errors in server mode",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"auto_encrypt": { "tls": true },
"server": true
}`},
hcl: []string{`
auto_encrypt { tls = true }
server = true
`},
expectedErr: "auto_encrypt.tls can only be used on a client.",
})
run(t, testCase{
desc: "test connect vault provider configuration",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"connect": {
"enabled": true,
"ca_provider": "vault",
"ca_config": {
"ca_file": "/capath/ca.pem",
"ca_path": "/capath/",
"cert_file": "/certpath/cert.pem",
"key_file": "/certpath/key.pem",
"tls_server_name": "server.name",
"tls_skip_verify": true,
"token": "abc",
"root_pki_path": "consul-vault",
"intermediate_pki_path": "connect-intermediate"
}
}
}`},
hcl: []string{`
connect {
enabled = true
ca_provider = "vault"
ca_config {
ca_file = "/capath/ca.pem"
ca_path = "/capath/"
cert_file = "/certpath/cert.pem"
key_file = "/certpath/key.pem"
tls_server_name = "server.name"
tls_skip_verify = true
token = "abc"
root_pki_path = "consul-vault"
intermediate_pki_path = "connect-intermediate"
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConnectEnabled = true
rt.ConnectCAProvider = "vault"
rt.ConnectCAConfig = map[string]interface{}{
"CAFile": "/capath/ca.pem",
"CAPath": "/capath/",
"CertFile": "/certpath/cert.pem",
"KeyFile": "/certpath/key.pem",
"TLSServerName": "server.name",
"TLSSkipVerify": true,
"Token": "abc",
"RootPKIPath": "consul-vault",
"IntermediatePKIPath": "connect-intermediate",
}
},
})
run(t, testCase{
desc: "test connect vault provider configuration with root cert ttl",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"connect": {
"enabled": true,
"ca_provider": "vault",
"ca_config": {
"ca_file": "/capath/ca.pem",
"ca_path": "/capath/",
"cert_file": "/certpath/cert.pem",
"key_file": "/certpath/key.pem",
"tls_server_name": "server.name",
"tls_skip_verify": true,
"token": "abc",
"root_pki_path": "consul-vault",
"root_cert_ttl": "96360h",
"intermediate_pki_path": "connect-intermediate"
}
}
}`},
hcl: []string{`
connect {
enabled = true
ca_provider = "vault"
ca_config {
ca_file = "/capath/ca.pem"
ca_path = "/capath/"
cert_file = "/certpath/cert.pem"
key_file = "/certpath/key.pem"
tls_server_name = "server.name"
tls_skip_verify = true
root_pki_path = "consul-vault"
token = "abc"
intermediate_pki_path = "connect-intermediate"
root_cert_ttl = "96360h"
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConnectEnabled = true
rt.ConnectCAProvider = "vault"
rt.ConnectCAConfig = map[string]interface{}{
"CAFile": "/capath/ca.pem",
"CAPath": "/capath/",
"CertFile": "/certpath/cert.pem",
"KeyFile": "/certpath/key.pem",
"TLSServerName": "server.name",
"TLSSkipVerify": true,
"Token": "abc",
"RootPKIPath": "consul-vault",
"RootCertTTL": "96360h",
"IntermediatePKIPath": "connect-intermediate",
}
},
})
run(t, testCase{
desc: "Connect AWS CA provider configuration",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"connect": {
"enabled": true,
"ca_provider": "aws-pca",
"ca_config": {
"existing_arn": "foo",
"delete_on_exit": true
}
}
}`},
hcl: []string{`
connect {
enabled = true
ca_provider = "aws-pca"
ca_config {
existing_arn = "foo"
delete_on_exit = true
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConnectEnabled = true
rt.ConnectCAProvider = "aws-pca"
rt.ConnectCAConfig = map[string]interface{}{
"ExistingARN": "foo",
"DeleteOnExit": true,
}
},
})
run(t, testCase{
desc: "Connect AWS CA provider TTL validation",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"connect": {
"enabled": true,
"ca_provider": "aws-pca",
"ca_config": {
"leaf_cert_ttl": "1h"
}
}
}`},
hcl: []string{`
connect {
enabled = true
ca_provider = "aws-pca"
ca_config {
leaf_cert_ttl = "1h"
}
}
`},
expectedErr: "AWS PCA doesn't support certificates that are valid for less than 24 hours",
})
run(t, testCase{
desc: "Connect AWS CA provider EC key length validation",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"connect": {
"enabled": true,
"ca_provider": "aws-pca",
"ca_config": {
"private_key_bits": 521
}
}
}`},
hcl: []string{`
connect {
enabled = true
ca_provider = "aws-pca"
ca_config {
private_key_bits = 521
}
}
`},
expectedErr: "AWS PCA only supports P256 EC curve",
})
run(t, testCase{
desc: "connect.enable_mesh_gateway_wan_federation requires connect.enabled",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"connect": {
"enabled": false,
"enable_mesh_gateway_wan_federation": true
}
}`},
hcl: []string{`
connect {
enabled = false
enable_mesh_gateway_wan_federation = true
}
`},
expectedErr: "'connect.enable_mesh_gateway_wan_federation=true' requires 'connect.enabled=true'",
})
run(t, testCase{
desc: "connect.enable_mesh_gateway_wan_federation cannot use -join-wan",
args: []string{
`-data-dir=` + dataDir,
`-join-wan=1.2.3.4`,
},
json: []string{`{
"server": true,
"primary_datacenter": "one",
"datacenter": "one",
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}`},
hcl: []string{`
server = true
primary_datacenter = "one"
datacenter = "one"
connect {
enabled = true
enable_mesh_gateway_wan_federation = true
}
`},
expectedErr: "'start_join_wan' is incompatible with 'connect.enable_mesh_gateway_wan_federation = true'",
})
run(t, testCase{
desc: "connect.enable_mesh_gateway_wan_federation cannot use -retry-join-wan",
args: []string{
`-data-dir=` + dataDir,
`-retry-join-wan=1.2.3.4`,
},
json: []string{`{
"server": true,
"primary_datacenter": "one",
"datacenter": "one",
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}`},
hcl: []string{`
server = true
primary_datacenter = "one"
datacenter = "one"
connect {
enabled = true
enable_mesh_gateway_wan_federation = true
}
`},
expectedErr: "'retry_join_wan' is incompatible with 'connect.enable_mesh_gateway_wan_federation = true'",
})
run(t, testCase{
desc: "connect.enable_mesh_gateway_wan_federation requires server mode",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"server": false,
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}`},
hcl: []string{`
server = false
connect {
enabled = true
enable_mesh_gateway_wan_federation = true
}
`},
expectedErr: "'connect.enable_mesh_gateway_wan_federation = true' requires 'server = true'",
})
run(t, testCase{
desc: "connect.enable_mesh_gateway_wan_federation requires no slashes in node names",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"server": true,
"node_name": "really/why",
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}`},
hcl: []string{`
server = true
node_name = "really/why"
connect {
enabled = true
enable_mesh_gateway_wan_federation = true
}
`},
expectedErr: "'connect.enable_mesh_gateway_wan_federation = true' requires that 'node_name' not contain '/' characters",
})
run(t, testCase{
desc: "primary_gateways requires server mode",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"server": false,
"primary_gateways": [ "foo.local", "bar.local" ]
}`},
hcl: []string{`
server = false
primary_gateways = [ "foo.local", "bar.local" ]
`},
expectedErr: "'primary_gateways' requires 'server = true'",
})
run(t, testCase{
desc: "primary_gateways only works in a secondary datacenter",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"server": true,
"primary_datacenter": "one",
"datacenter": "one",
"primary_gateways": [ "foo.local", "bar.local" ]
}`},
hcl: []string{`
server = true
primary_datacenter = "one"
datacenter = "one"
primary_gateways = [ "foo.local", "bar.local" ]
`},
expectedErr: "'primary_gateways' should only be configured in a secondary datacenter",
})
run(t, testCase{
desc: "connect.enable_mesh_gateway_wan_federation in secondary with primary_gateways configured",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`{
"server": true,
"primary_datacenter": "one",
"datacenter": "two",
"primary_gateways": [ "foo.local", "bar.local" ],
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}`},
hcl: []string{`
server = true
primary_datacenter = "one"
datacenter = "two"
primary_gateways = [ "foo.local", "bar.local" ]
connect {
enabled = true
enable_mesh_gateway_wan_federation = true
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.Datacenter = "two"
rt.PrimaryDatacenter = "one"
rt.PrimaryGateways = []string{"foo.local", "bar.local"}
rt.ConnectEnabled = true
rt.ConnectMeshGatewayWANFederationEnabled = true
// server things
rt.ServerMode = true
rt.LeaveOnTerm = false
rt.SkipLeaveOnInt = true
rt.RPCConfig.EnableStreaming = true
},
})
// ------------------------------------------------------------
// ConfigEntry Handling
//
run(t, testCase{
desc: "ConfigEntry bootstrap doesn't parse",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"foo": "bar"
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
foo = "bar"
}
}`},
expectedErr: "config_entries.bootstrap[0]: Payload does not contain a kind/Kind",
})
run(t, testCase{
desc: "ConfigEntry bootstrap unknown kind",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "foo",
"name": "bar",
"baz": 1
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
kind = "foo"
name = "bar"
baz = 1
}
}`},
expectedErr: "config_entries.bootstrap[0]: invalid config entry kind: foo",
})
run(t, testCase{
desc: "ConfigEntry bootstrap invalid service-defaults",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "service-defaults",
"name": "web",
"made_up_key": "blah"
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
kind = "service-defaults"
name = "web"
made_up_key = "blah"
}
}`},
expectedErr: "config_entries.bootstrap[0]: 1 error occurred:\n\t* invalid config key \"made_up_key\"\n\n",
})
run(t, testCase{
desc: "ConfigEntry bootstrap proxy-defaults (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "proxy-defaults",
"name": "global",
"config": {
"bar": "abc",
"moreconfig": {
"moar": "config"
}
},
"mesh_gateway": {
"mode": "remote"
},
"mode": "transparent",
"transparent_proxy": {
"outbound_listener_port": 10101,
"dialed_directly": true
}
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
kind = "proxy-defaults"
name = "global"
config {
"bar" = "abc"
"moreconfig" {
"moar" = "config"
}
}
mesh_gateway {
mode = "remote"
}
mode = "transparent"
transparent_proxy = {
outbound_listener_port = 10101
dialed_directly = true
}
}
}`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: *defaultEntMeta,
Config: map[string]interface{}{
"bar": "abc",
"moreconfig": map[string]interface{}{
"moar": "config",
},
},
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap proxy-defaults (camel-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"Kind": "proxy-defaults",
"Name": "global",
"Config": {
"bar": "abc",
"moreconfig": {
"moar": "config"
}
},
"MeshGateway": {
"Mode": "remote"
},
"Mode": "transparent",
"TransparentProxy": {
"OutboundListenerPort": 10101,
"DialedDirectly": true
}
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
Kind = "proxy-defaults"
Name = "global"
Config {
"bar" = "abc"
"moreconfig" {
"moar" = "config"
}
}
MeshGateway {
Mode = "remote"
}
Mode = "transparent"
TransparentProxy = {
OutboundListenerPort = 10101
DialedDirectly = true
}
}
}`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: *defaultEntMeta,
Config: map[string]interface{}{
"bar": "abc",
"moreconfig": map[string]interface{}{
"moar": "config",
},
},
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap service-defaults (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "service-defaults",
"name": "web",
"meta" : {
"foo": "bar",
"gir": "zim"
},
"protocol": "http",
"external_sni": "abc-123",
"mesh_gateway": {
"mode": "remote"
},
"mode": "transparent",
"transparent_proxy": {
"outbound_listener_port": 10101,
"dialed_directly": true
}
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
kind = "service-defaults"
name = "web"
meta {
"foo" = "bar"
"gir" = "zim"
}
protocol = "http"
external_sni = "abc-123"
mesh_gateway {
mode = "remote"
}
mode = "transparent"
transparent_proxy = {
outbound_listener_port = 10101
dialed_directly = true
}
}
}`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "web",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
EnterpriseMeta: *defaultEntMeta,
Protocol: "http",
ExternalSNI: "abc-123",
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap service-defaults (camel-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"Kind": "service-defaults",
"Name": "web",
"Meta" : {
"foo": "bar",
"gir": "zim"
},
"Protocol": "http",
"ExternalSNI": "abc-123",
"MeshGateway": {
"Mode": "remote"
},
"Mode": "transparent",
"TransparentProxy": {
"OutboundListenerPort": 10101,
"DialedDirectly": true
}
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
Kind = "service-defaults"
Name = "web"
Meta {
"foo" = "bar"
"gir" = "zim"
}
Protocol = "http"
ExternalSNI = "abc-123"
MeshGateway {
Mode = "remote"
}
Mode = "transparent"
TransparentProxy = {
OutboundListenerPort = 10101
DialedDirectly = true
}
}
}`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "web",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
EnterpriseMeta: *defaultEntMeta,
Protocol: "http",
ExternalSNI: "abc-123",
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap service-router (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "service-router",
"name": "main",
"meta" : {
"foo": "bar",
"gir": "zim"
},
"routes": [
{
"match": {
"http": {
"path_exact": "/foo",
"header": [
{
"name": "debug1",
"present": true
},
{
"name": "debug2",
"present": true,
"invert": true
},
{
"name": "debug3",
"exact": "1"
},
{
"name": "debug4",
"prefix": "aaa"
},
{
"name": "debug5",
"suffix": "bbb"
},
{
"name": "debug6",
"regex": "a.*z"
}
]
}
},
"destination": {
"service" : "carrot",
"service_subset" : "kale",
"namespace" : "leek",
"prefix_rewrite" : "/alternate",
"request_timeout" : "99s",
"num_retries" : 12345,
"retry_on_connect_failure": true,
"retry_on_status_codes" : [401, 209]
}
},
{
"match": {
"http": {
"path_prefix": "/foo",
"methods": [ "GET", "DELETE" ],
"query_param": [
{
"name": "hack1",
"present": true
},
{
"name": "hack2",
"exact": "1"
},
{
"name": "hack3",
"regex": "a.*z"
}
]
}
}
},
{
"match": {
"http": {
"path_regex": "/foo"
}
}
}
]
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
kind = "service-router"
name = "main"
meta {
"foo" = "bar"
"gir" = "zim"
}
routes = [
{
match {
http {
path_exact = "/foo"
header = [
{
name = "debug1"
present = true
},
{
name = "debug2"
present = true
invert = true
},
{
name = "debug3"
exact = "1"
},
{
name = "debug4"
prefix = "aaa"
},
{
name = "debug5"
suffix = "bbb"
},
{
name = "debug6"
regex = "a.*z"
},
]
}
}
destination {
service = "carrot"
service_subset = "kale"
namespace = "leek"
prefix_rewrite = "/alternate"
request_timeout = "99s"
num_retries = 12345
retry_on_connect_failure = true
retry_on_status_codes = [401, 209]
}
},
{
match {
http {
path_prefix = "/foo"
methods = [ "GET", "DELETE" ]
query_param = [
{
name = "hack1"
present = true
},
{
name = "hack2"
exact = "1"
},
{
name = "hack3"
regex = "a.*z"
},
]
}
}
},
{
match {
http {
path_regex = "/foo"
}
}
},
]
}
}`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "main",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
EnterpriseMeta: *defaultEntMeta,
Routes: []structs.ServiceRoute{
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathExact: "/foo",
Header: []structs.ServiceRouteHTTPMatchHeader{
{
Name: "debug1",
Present: true,
},
{
Name: "debug2",
Present: true,
Invert: true,
},
{
Name: "debug3",
Exact: "1",
},
{
Name: "debug4",
Prefix: "aaa",
},
{
Name: "debug5",
Suffix: "bbb",
},
{
Name: "debug6",
Regex: "a.*z",
},
},
},
},
Destination: &structs.ServiceRouteDestination{
Service: "carrot",
ServiceSubset: "kale",
Namespace: "leek",
Partition: acl.DefaultPartitionName,
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
NumRetries: 12345,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{401, 209},
},
},
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathPrefix: "/foo",
Methods: []string{"GET", "DELETE"},
QueryParam: []structs.ServiceRouteHTTPMatchQueryParam{
{
Name: "hack1",
Present: true,
},
{
Name: "hack2",
Exact: "1",
},
{
Name: "hack3",
Regex: "a.*z",
},
},
},
},
},
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathRegex: "/foo",
},
},
},
},
},
}
},
})
// TODO(rb): add in missing tests for ingress-gateway (snake + camel)
// TODO(rb): add in missing tests for terminating-gateway (snake + camel)
run(t, testCase{
desc: "ConfigEntry bootstrap service-intentions (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "service-intentions",
"name": "web",
"meta" : {
"foo": "bar",
"gir": "zim"
},
"sources": [
{
"name": "foo",
"action": "deny",
"type": "consul",
"description": "foo desc"
},
{
"name": "bar",
"action": "allow",
"description": "bar desc"
},
{
"name": "*",
"action": "deny",
"description": "wild desc"
}
]
}
]
}
}`,
},
hcl: []string{`
config_entries {
bootstrap {
kind = "service-intentions"
name = "web"
meta {
"foo" = "bar"
"gir" = "zim"
}
sources = [
{
name = "foo"
action = "deny"
type = "consul"
description = "foo desc"
},
{
name = "bar"
action = "allow"
description = "bar desc"
}
]
sources {
name = "*"
action = "deny"
description = "wild desc"
}
}
}
`,
},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: "service-intentions",
Name: "web",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "foo",
Action: "deny",
Type: "consul",
Description: "foo desc",
Precedence: 9,
EnterpriseMeta: *defaultEntMeta,
},
{
Name: "bar",
Action: "allow",
Type: "consul",
Description: "bar desc",
Precedence: 9,
EnterpriseMeta: *defaultEntMeta,
},
{
Name: "*",
Action: "deny",
Type: "consul",
Description: "wild desc",
Precedence: 8,
EnterpriseMeta: *defaultEntMeta,
},
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap service-intentions wildcard destination (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "service-intentions",
"name": "*",
"sources": [
{
"name": "foo",
"action": "deny",
"precedence": 6
}
]
}
]
}
}`,
},
hcl: []string{`
config_entries {
bootstrap {
kind = "service-intentions"
name = "*"
sources {
name = "foo"
action = "deny"
# should be parsed, but we'll ignore it later
precedence = 6
}
}
}
`,
},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: "service-intentions",
Name: "*",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "foo",
Action: "deny",
Type: "consul",
Precedence: 6,
EnterpriseMeta: *defaultEntMeta,
},
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap cluster (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "mesh",
"meta" : {
"foo": "bar",
"gir": "zim"
},
"transparent_proxy": {
"mesh_destinations_only": true
}
}
]
}
}`,
},
hcl: []string{`
config_entries {
bootstrap {
kind = "mesh"
meta {
"foo" = "bar"
"gir" = "zim"
}
transparent_proxy {
mesh_destinations_only = true
}
}
}
`,
},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.MeshConfigEntry{
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
EnterpriseMeta: *defaultEntMeta,
TransparentProxy: structs.TransparentProxyMeshConfig{
MeshDestinationsOnly: true,
},
},
}
},
})
run(t, testCase{
desc: "ConfigEntry bootstrap cluster (camel-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"Kind": "mesh",
"Meta" : {
"foo": "bar",
"gir": "zim"
},
"TransparentProxy": {
"MeshDestinationsOnly": true
}
}
]
}
}`,
},
hcl: []string{`
config_entries {
bootstrap {
Kind = "mesh"
Meta {
"foo" = "bar"
"gir" = "zim"
}
TransparentProxy {
MeshDestinationsOnly = true
}
}
}
`,
},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.MeshConfigEntry{
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
EnterpriseMeta: *defaultEntMeta,
TransparentProxy: structs.TransparentProxyMeshConfig{
MeshDestinationsOnly: true,
},
},
}
},
})
///////////////////////////////////
// Defaults sanity checks
run(t, testCase{
desc: "default limits",
args: []string{
`-data-dir=` + dataDir,
},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
// Note that in the happy case this test will pass even if you comment
// out all the stuff below since rt is also initialized from the
// defaults. But it's still valuable as it will fail as soon as the
// defaults are changed from these values forcing that change to be
// intentional.
rt.RPCHandshakeTimeout = 5 * time.Second
rt.HTTPSHandshakeTimeout = 5 * time.Second
rt.HTTPMaxConnsPerClient = 200
rt.RPCMaxConnsPerClient = 100
rt.SegmentLimit = 64
},
})
///////////////////////////////////
// Auto Config related tests
run(t, testCase{
desc: "auto config and auto encrypt error",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
auto_config {
enabled = true
intro_token = "blah"
server_addresses = ["198.18.0.1"]
}
auto_encrypt {
tls = true
}
verify_outgoing = true
`},
json: []string{`{
"auto_config": {
"enabled": true,
"intro_token": "blah",
"server_addresses": ["198.18.0.1"]
},
"auto_encrypt": {
"tls": true
},
"verify_outgoing": true
}`},
expectedErr: "both auto_encrypt.tls and auto_config.enabled cannot be set to true.",
})
run(t, testCase{
desc: "auto config not allowed for servers",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
server = true
auto_config {
enabled = true
intro_token = "blah"
server_addresses = ["198.18.0.1"]
}
verify_outgoing = true
`},
json: []string{`
{
"server": true,
"auto_config": {
"enabled": true,
"intro_token": "blah",
"server_addresses": ["198.18.0.1"]
},
"verify_outgoing": true
}`},
expectedErr: "auto_config.enabled cannot be set to true for server agents",
})
run(t, testCase{
desc: "auto config tls not enabled",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
auto_config {
enabled = true
server_addresses = ["198.18.0.1"]
intro_token = "foo"
}
`},
json: []string{`
{
"auto_config": {
"enabled": true,
"server_addresses": ["198.18.0.1"],
"intro_token": "foo"
}
}`},
expectedErr: "auto_config.enabled cannot be set without configuring TLS for server communications",
})
run(t, testCase{
desc: "auto config server tls not enabled",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
server = true
auto_config {
authorization {
enabled = true
}
}
`},
json: []string{`
{
"server": true,
"auto_config": {
"authorization": {
"enabled": true
}
}
}`},
expectedErr: "auto_config.authorization.enabled cannot be set without providing a TLS certificate for the server",
})
run(t, testCase{
desc: "auto config no intro token",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
auto_config {
enabled = true
server_addresses = ["198.18.0.1"]
}
verify_outgoing = true
`},
json: []string{`
{
"auto_config": {
"enabled": true,
"server_addresses": ["198.18.0.1"]
},
"verify_outgoing": true
}`},
expectedErr: "One of auto_config.intro_token, auto_config.intro_token_file or the CONSUL_INTRO_TOKEN environment variable must be set to enable auto_config",
})
run(t, testCase{
desc: "auto config no server addresses",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
auto_config {
enabled = true
intro_token = "blah"
}
verify_outgoing = true
`},
json: []string{`
{
"auto_config": {
"enabled": true,
"intro_token": "blah"
},
"verify_outgoing": true
}`},
expectedErr: "auto_config.enabled is set without providing a list of addresses",
})
run(t, testCase{
desc: "auto config client",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
auto_config {
enabled = true
intro_token = "blah"
intro_token_file = "blah"
server_addresses = ["198.18.0.1"]
dns_sans = ["foo"]
ip_sans = ["invalid", "127.0.0.1"]
}
verify_outgoing = true
`},
json: []string{`
{
"auto_config": {
"enabled": true,
"intro_token": "blah",
"intro_token_file": "blah",
"server_addresses": ["198.18.0.1"],
"dns_sans": ["foo"],
"ip_sans": ["invalid", "127.0.0.1"]
},
"verify_outgoing": true
}`},
expectedWarnings: []string{
"Cannot parse ip \"invalid\" from auto_config.ip_sans",
"Both an intro token and intro token file are set. The intro token will be used instead of the file",
},
expected: func(rt *RuntimeConfig) {
rt.ConnectEnabled = true
rt.AutoConfig.Enabled = true
rt.AutoConfig.IntroToken = "blah"
rt.AutoConfig.IntroTokenFile = "blah"
rt.AutoConfig.ServerAddresses = []string{"198.18.0.1"}
rt.AutoConfig.DNSSANs = []string{"foo"}
rt.AutoConfig.IPSANs = []net.IP{net.IPv4(127, 0, 0, 1)}
rt.DataDir = dataDir
rt.VerifyOutgoing = true
},
})
run(t, testCase{
desc: "auto config authorizer client not allowed",
args: []string{
`-data-dir=` + dataDir,
},
hcl: []string{`
auto_config {
authorization {
enabled = true
}
}
`},
json: []string{`
{
"auto_config": {
"authorization": {
"enabled": true
}
}
}`},
expectedErr: "auto_config.authorization.enabled cannot be set to true for client agents",
})
run(t, testCase{
desc: "auto config authorizer invalid config",
args: []string{
`-data-dir=` + dataDir,
`-server`,
},
hcl: []string{`
auto_config {
authorization {
enabled = true
}
}
cert_file = "foo"
`},
json: []string{`
{
"auto_config": {
"authorization": {
"enabled": true
}
},
"cert_file": "foo"
}`},
expectedErr: `auto_config.authorization.static has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`,
})
run(t, testCase{
desc: "auto config authorizer invalid config 2",
args: []string{
`-data-dir=` + dataDir,
`-server`,
},
hcl: []string{`
auto_config {
authorization {
enabled = true
static {
jwks_url = "https://fake.uri.local"
oidc_discovery_url = "https://fake.uri.local"
}
}
}
cert_file = "foo"
`},
json: []string{`
{
"auto_config": {
"authorization": {
"enabled": true,
"static": {
"jwks_url": "https://fake.uri.local",
"oidc_discovery_url": "https://fake.uri.local"
}
}
},
"cert_file": "foo"
}`},
expectedErr: `auto_config.authorization.static has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`,
})
run(t, testCase{
desc: "auto config authorizer require token replication in secondary",
args: []string{
`-data-dir=` + dataDir,
`-server`,
},
hcl: []string{`
primary_datacenter = "otherdc"
acl {
enabled = true
}
auto_config {
authorization {
enabled = true
static {
jwks_url = "https://fake.uri.local"
oidc_discovery_url = "https://fake.uri.local"
}
}
}
cert_file = "foo"
`},
json: []string{`
{
"primary_datacenter": "otherdc",
"acl": {
"enabled": true
},
"auto_config": {
"authorization": {
"enabled": true,
"static": {
"jwks_url": "https://fake.uri.local",
"oidc_discovery_url": "https://fake.uri.local"
}
}
},
"cert_file": "foo"
}`},
expectedErr: `Enabling auto-config authorization (auto_config.authorization.enabled) in non primary datacenters with ACLs enabled (acl.enabled) requires also enabling ACL token replication (acl.enable_token_replication)`,
})
run(t, testCase{
desc: "auto config authorizer invalid claim assertion",
args: []string{
`-data-dir=` + dataDir,
`-server`,
},
hcl: []string{`
auto_config {
authorization {
enabled = true
static {
jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"]
claim_assertions = [
"values.node == ${node}"
]
}
}
}
cert_file = "foo"
`},
json: []string{`
{
"auto_config": {
"authorization": {
"enabled": true,
"static": {
"jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"],
"claim_assertions": [
"values.node == ${node}"
]
}
}
},
"cert_file": "foo"
}`},
expectedErr: `auto_config.authorization.static.claim_assertion "values.node == ${node}" is invalid: Selector "values" is not valid`,
})
run(t, testCase{
desc: "auto config authorizer ok",
args: []string{
`-data-dir=` + dataDir,
`-server`,
},
hcl: []string{`
auto_config {
authorization {
enabled = true
static {
jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"]
claim_assertions = [
"value.node == ${node}"
]
claim_mappings = {
node = "node"
}
}
}
}
cert_file = "foo"
`},
json: []string{`
{
"auto_config": {
"authorization": {
"enabled": true,
"static": {
"jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"],
"claim_assertions": [
"value.node == ${node}"
],
"claim_mappings": {
"node": "node"
}
}
}
},
"cert_file": "foo"
}`},
expected: func(rt *RuntimeConfig) {
rt.AutoConfig.Authorizer.Enabled = true
rt.AutoConfig.Authorizer.AuthMethod.Config["ClaimMappings"] = map[string]string{
"node": "node",
}
rt.AutoConfig.Authorizer.AuthMethod.Config["JWTValidationPubKeys"] = []string{"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"}
rt.AutoConfig.Authorizer.ClaimAssertions = []string{"value.node == ${node}"}
rt.DataDir = dataDir
rt.LeaveOnTerm = false
rt.ServerMode = true
rt.SkipLeaveOnInt = true
rt.CertFile = "foo"
rt.RPCConfig.EnableStreaming = true
},
})
// UI Config tests
run(t, testCase{
desc: "ui config deprecated",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui": true,
"ui_content_path": "/bar"
}`},
hcl: []string{`
ui = true
ui_content_path = "/bar"
`},
expectedWarnings: []string{
`The 'ui' field is deprecated. Use the 'ui_config.enabled' field instead.`,
`The 'ui_content_path' field is deprecated. Use the 'ui_config.content_path' field instead.`,
},
expected: func(rt *RuntimeConfig) {
// Should still work!
rt.UIConfig.Enabled = true
rt.UIConfig.ContentPath = "/bar/"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "ui-dir config deprecated",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_dir": "/bar"
}`},
hcl: []string{`
ui_dir = "/bar"
`},
expectedWarnings: []string{
`The 'ui_dir' field is deprecated. Use the 'ui_config.dir' field instead.`,
},
expected: func(rt *RuntimeConfig) {
// Should still work!
rt.UIConfig.Dir = "/bar"
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "metrics_provider constraint",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_provider": "((((lisp 4 life))))"
}
}`},
hcl: []string{`
ui_config {
metrics_provider = "((((lisp 4 life))))"
}
`},
expectedErr: `ui_config.metrics_provider can only contain lowercase alphanumeric, - or _ characters.`,
})
run(t, testCase{
desc: "metrics_provider_options_json invalid JSON",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_provider_options_json": "not valid JSON"
}
}`},
hcl: []string{`
ui_config {
metrics_provider_options_json = "not valid JSON"
}
`},
expectedErr: `ui_config.metrics_provider_options_json must be empty or a string containing a valid JSON object.`,
})
run(t, testCase{
desc: "metrics_provider_options_json not an object",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_provider_options_json": "1.0"
}
}`},
hcl: []string{`
ui_config {
metrics_provider_options_json = "1.0"
}
`},
expectedErr: `ui_config.metrics_provider_options_json must be empty or a string containing a valid JSON object.`,
})
run(t, testCase{
desc: "metrics_proxy.base_url valid",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"base_url": "___"
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
base_url = "___"
}
}
`},
expectedErr: `ui_config.metrics_proxy.base_url must be a valid http or https URL.`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist invalid (empty)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["", "/foo"]
}
}
`},
expectedErr: `ui_config.metrics_proxy.path_allowlist: path "" is not an absolute path`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist invalid (relative)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["bar/baz", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["bar/baz", "/foo"]
}
}
`},
expectedErr: `ui_config.metrics_proxy.path_allowlist: path "bar/baz" is not an absolute path`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist invalid (weird)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["://bar/baz", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["://bar/baz", "/foo"]
}
}
`},
expectedErr: `ui_config.metrics_proxy.path_allowlist: path "://bar/baz" is not an absolute path`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist invalid (fragment)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["/bar/baz#stuff", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["/bar/baz#stuff", "/foo"]
}
}
`},
expectedErr: `ui_config.metrics_proxy.path_allowlist: path "/bar/baz#stuff" is not an absolute path`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist invalid (querystring)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["/bar/baz?stu=ff", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["/bar/baz?stu=ff", "/foo"]
}
}
`},
expectedErr: `ui_config.metrics_proxy.path_allowlist: path "/bar/baz?stu=ff" is not an absolute path`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist invalid (encoded slash)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["/bar%2fbaz", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["/bar%2fbaz", "/foo"]
}
}
`},
expectedErr: `ui_config.metrics_proxy.path_allowlist: path "/bar%2fbaz" is not an absolute path`,
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist ok",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"path_allowlist": ["/bar/baz", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
path_allowlist = ["/bar/baz", "/foo"]
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.UIConfig.MetricsProxy.PathAllowlist = []string{"/bar/baz", "/foo"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist defaulted for prometheus",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_provider": "prometheus"
}
}`},
hcl: []string{`
ui_config {
metrics_provider = "prometheus"
}
`},
expected: func(rt *RuntimeConfig) {
rt.UIConfig.MetricsProvider = "prometheus"
rt.UIConfig.MetricsProxy.PathAllowlist = []string{
"/api/v1/query",
"/api/v1/query_range",
}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "metrics_proxy.path_allowlist not overridden with defaults for prometheus",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_provider": "prometheus",
"metrics_proxy": {
"path_allowlist": ["/bar/baz", "/foo"]
}
}
}`},
hcl: []string{`
ui_config {
metrics_provider = "prometheus"
metrics_proxy {
path_allowlist = ["/bar/baz", "/foo"]
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.UIConfig.MetricsProvider = "prometheus"
rt.UIConfig.MetricsProxy.PathAllowlist = []string{"/bar/baz", "/foo"}
rt.DataDir = dataDir
},
})
run(t, testCase{
desc: "metrics_proxy.base_url http(s)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"metrics_proxy": {
"base_url": "localhost:1234"
}
}
}`},
hcl: []string{`
ui_config {
metrics_proxy {
base_url = "localhost:1234"
}
}
`},
expectedErr: `ui_config.metrics_proxy.base_url must be a valid http or https URL.`,
})
run(t, testCase{
desc: "dashboard_url_templates key format",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"dashboard_url_templates": {
"(*&ASDOUISD)": "localhost:1234"
}
}
}`},
hcl: []string{`
ui_config {
dashboard_url_templates {
"(*&ASDOUISD)" = "localhost:1234"
}
}
`},
expectedErr: `ui_config.dashboard_url_templates key names can only contain lowercase alphanumeric, - or _ characters.`,
})
run(t, testCase{
desc: "dashboard_url_templates value format",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"ui_config": {
"dashboard_url_templates": {
"services": "localhost:1234"
}
}
}`},
hcl: []string{`
ui_config {
dashboard_url_templates {
services = "localhost:1234"
}
}
`},
expectedErr: `ui_config.dashboard_url_templates values must be a valid http or https URL.`,
})
// Per node reconnect timeout test
run(t, testCase{
desc: "server and advertised reconnect timeout error",
args: []string{
`-data-dir=` + dataDir,
`-server`,
},
hcl: []string{`
advertise_reconnect_timeout = "5s"
`},
json: []string{`
{
"advertise_reconnect_timeout": "5s"
}`},
expectedErr: "advertise_reconnect_timeout can only be used on a client",
})
}
func (tc testCase) run(format string, dataDir string) func(t *testing.T) {
return func(t *testing.T) {
// clean data dir before every test
os.RemoveAll(dataDir)
os.MkdirAll(dataDir, 0755)
if tc.setup != nil {
tc.setup()
}
opts := tc.opts
fs := flag.NewFlagSet("", flag.ContinueOnError)
AddFlags(fs, &opts)
require.NoError(t, fs.Parse(tc.args))
require.Len(t, fs.Args(), 0)
for i, data := range tc.source(format) {
opts.sources = append(opts.sources, FileSource{
Name: fmt.Sprintf("src-%d.%s", i, format),
Format: format,
Data: data,
})
}
patchLoadOptsShims(&opts)
result, err := Load(opts)
switch {
case err == nil && tc.expectedErr != "":
t.Fatalf("got nil want error to contain %q", tc.expectedErr)
case err != nil && tc.expectedErr == "":
t.Fatalf("got error %s want nil", err)
case err != nil && tc.expectedErr != "" && !strings.Contains(err.Error(), tc.expectedErr):
t.Fatalf("error %q does not contain %q", err.Error(), tc.expectedErr)
}
if tc.expectedErr != "" {
return
}
require.Equal(t, tc.expectedWarnings, result.Warnings, "warnings")
// build a default configuration, then patch the fields we expect to change
// and compare it with the generated configuration. Since the expected
// runtime config has been validated we do not need to validate it again.
expectedOpts := LoadOpts{}
patchLoadOptsShims(&expectedOpts)
x, err := newBuilder(expectedOpts)
require.NoError(t, err)
expected, err := x.build()
require.NoError(t, err)
if tc.expected != nil {
tc.expected(&expected)
}
actual := *result.RuntimeConfig
// both DataDir fields should always be the same, so test for the
// invariant, and than updated the expected, so that every test
// case does not need to set this field.
require.Equal(t, actual.DataDir, actual.ACLTokens.DataDir)
expected.ACLTokens.DataDir = actual.ACLTokens.DataDir
// These fields are always the same
expected.ACLResolverSettings.Datacenter = expected.Datacenter
expected.ACLResolverSettings.ACLsEnabled = expected.ACLsEnabled
expected.ACLResolverSettings.NodeName = expected.NodeName
expected.ACLResolverSettings.EnterpriseMeta = *structs.NodeEnterpriseMetaInPartition(expected.PartitionOrDefault())
assertDeepEqual(t, expected, actual, cmpopts.EquateEmpty())
}
}
func runCase(t *testing.T, name string, fn func(t *testing.T)) {
t.Helper()
t.Run(name, func(t *testing.T) {
t.Helper()
t.Log("case:", name)
fn(t)
})
}
func assertDeepEqual(t *testing.T, x, y interface{}, opts ...cmp.Option) {
t.Helper()
if diff := cmp.Diff(x, y, opts...); diff != "" {
t.Fatalf("assertion failed: values are not equal\n--- expected\n+++ actual\n%v", diff)
}
}
func TestLoad_InvalidConfigFormat(t *testing.T) {
_, err := Load(LoadOpts{ConfigFormat: "yaml"})
require.Error(t, err)
require.Contains(t, err.Error(), "-config-format must be either 'hcl' or 'json'")
}
// TestFullConfig tests the conversion from a fully populated JSON or
// HCL config file to a RuntimeConfig structure. All fields must be set
// to a unique non-zero value.
func TestLoad_FullConfig(t *testing.T) {
dataDir := testutil.TempDir(t, "consul")
cidr := func(s string) *net.IPNet {
_, n, _ := net.ParseCIDR(s)
return n
}
defaultEntMeta := structs.DefaultEnterpriseMetaInDefaultPartition()
nodeEntMeta := structs.NodeEnterpriseMetaInDefaultPartition()
expected := &RuntimeConfig{
// non-user configurable values
AEInterval: time.Minute,
CheckDeregisterIntervalMin: time.Minute,
CheckReapInterval: 30 * time.Second,
SegmentNameLimit: 64,
SyncCoordinateIntervalMin: 15 * time.Second,
SyncCoordinateRateTarget: 64,
Revision: "JNtPSav3",
Version: "R909Hblt",
VersionPrerelease: "ZT1JOQLn",
// consul configuration
ConsulCoordinateUpdateBatchSize: 128,
ConsulCoordinateUpdateMaxBatches: 5,
ConsulCoordinateUpdatePeriod: 5 * time.Second,
ConsulRaftElectionTimeout: 5 * time.Second,
ConsulRaftHeartbeatTimeout: 5 * time.Second,
ConsulRaftLeaderLeaseTimeout: 2500 * time.Millisecond,
GossipLANGossipInterval: 25252 * time.Second,
GossipLANGossipNodes: 6,
GossipLANProbeInterval: 101 * time.Millisecond,
GossipLANProbeTimeout: 102 * time.Millisecond,
GossipLANSuspicionMult: 1235,
GossipLANRetransmitMult: 1234,
GossipWANGossipInterval: 6966 * time.Second,
GossipWANGossipNodes: 2,
GossipWANProbeInterval: 103 * time.Millisecond,
GossipWANProbeTimeout: 104 * time.Millisecond,
GossipWANSuspicionMult: 16385,
GossipWANRetransmitMult: 16384,
ConsulServerHealthInterval: 2 * time.Second,
// user configurable values
ACLTokens: token.Config{
EnablePersistence: true,
DataDir: dataDir,
ACLDefaultToken: "418fdff1",
ACLAgentToken: "bed2377c",
ACLAgentRecoveryToken: "1dba6aba",
ACLReplicationToken: "5795983a",
},
ACLsEnabled: true,
PrimaryDatacenter: "ejtmd43d",
ACLResolverSettings: consul.ACLResolverSettings{
ACLsEnabled: true,
Datacenter: "rzo029wg",
NodeName: "otlLxGaI",
EnterpriseMeta: *nodeEntMeta,
ACLDefaultPolicy: "72c2e7a0",
ACLDownPolicy: "03eb2aee",
ACLTokenTTL: 3321 * time.Second,
ACLPolicyTTL: 1123 * time.Second,
ACLRoleTTL: 9876 * time.Second,
},
ACLEnableKeyListPolicy: true,
ACLInitialManagementToken: "3820e09a",
ACLTokenReplication: true,
AdvertiseAddrLAN: ipAddr("17.99.29.16"),
AdvertiseAddrWAN: ipAddr("78.63.37.19"),
AdvertiseReconnectTimeout: 0 * time.Second,
AutopilotCleanupDeadServers: true,
AutopilotDisableUpgradeMigration: true,
AutopilotLastContactThreshold: 12705 * time.Second,
AutopilotMaxTrailingLogs: 17849,
AutopilotMinQuorum: 3,
AutopilotRedundancyZoneTag: "3IsufDJf",
AutopilotServerStabilizationTime: 23057 * time.Second,
AutopilotUpgradeVersionTag: "W9pDwFAL",
BindAddr: ipAddr("16.99.34.17"),
BootstrapExpect: 53,
Cache: cache.Options{
EntryFetchMaxBurst: 42,
EntryFetchRate: 0.334,
},
CAFile: "erA7T0PM",
CAPath: "mQEN1Mfp",
CertFile: "7s4QAzDk",
CheckOutputMaxSize: checks.DefaultBufSize,
Checks: []*structs.CheckDefinition{
{
ID: "uAjE6m9Z",
Name: "QsZRGpYr",
Notes: "VJ7Sk4BY",
ServiceID: "lSulPcyz",
Token: "toO59sh8",
Status: "9RlWsXMV",
ScriptArgs: []string{"4BAJttck", "4D2NPtTQ"},
HTTP: "dohLcyQ2",
Header: map[string][]string{
"ZBfTin3L": {"1sDbEqYG", "lJGASsWK"},
"Ui0nU99X": {"LMccm3Qe", "k5H5RggQ"},
},
Method: "aldrIQ4l",
Body: "wSjTy7dg",
TCP: "RJQND605",
H2PING: "9N1cSb5B",
H2PingUseTLS: false,
Interval: 22164 * time.Second,
OutputMaxSize: checks.DefaultBufSize,
DockerContainerID: "ipgdFtjd",
Shell: "qAeOYy0M",
TLSServerName: "bdeb5f6a",
TLSSkipVerify: true,
Timeout: 1813 * time.Second,
DeregisterCriticalServiceAfter: 14232 * time.Second,
},
{
ID: "Cqq95BhP",
Name: "3qXpkS0i",
Notes: "sb5qLTex",
ServiceID: "CmUUcRna",
Token: "a3nQzHuy",
Status: "irj26nf3",
ScriptArgs: []string{"9s526ogY", "gSlOHj1w"},
HTTP: "yzhgsQ7Y",
Header: map[string][]string{
"zcqwA8dO": {"qb1zx0DL", "sXCxPFsD"},
"qxvdnSE9": {"6wBPUYdF", "YYh8wtSZ"},
},
Method: "gLrztrNw",
Body: "0jkKgGUC",
OutputMaxSize: checks.DefaultBufSize,
TCP: "4jG5casb",
H2PING: "HCHU7gEb",
H2PingUseTLS: false,
Interval: 28767 * time.Second,
DockerContainerID: "THW6u7rL",
Shell: "C1Zt3Zwh",
TLSServerName: "6adc3bfb",
TLSSkipVerify: true,
Timeout: 18506 * time.Second,
DeregisterCriticalServiceAfter: 2366 * time.Second,
},
{
ID: "fZaCAXww",
Name: "OOM2eo0f",
Notes: "zXzXI9Gt",
ServiceID: "L8G0QNmR",
Token: "oo4BCTgJ",
Status: "qLykAl5u",
ScriptArgs: []string{"f3BemRjy", "e5zgpef7"},
HTTP: "29B93haH",
Header: map[string][]string{
"hBq0zn1q": {"2a9o9ZKP", "vKwA5lR6"},
"f3r6xFtM": {"RyuIdDWv", "QbxEcIUM"},
},
Method: "Dou0nGT5",
Body: "5PBQd2OT",
OutputMaxSize: checks.DefaultBufSize,
TCP: "JY6fTTcw",
H2PING: "rQ8eyCSF",
H2PingUseTLS: false,
Interval: 18714 * time.Second,
DockerContainerID: "qF66POS9",
Shell: "sOnDy228",
TLSServerName: "7BdnzBYk",
TLSSkipVerify: true,
Timeout: 5954 * time.Second,
DeregisterCriticalServiceAfter: 13209 * time.Second,
},
},
CheckUpdateInterval: 16507 * time.Second,
ClientAddrs: []*net.IPAddr{ipAddr("93.83.18.19")},
ConfigEntryBootstrap: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: *defaultEntMeta,
Config: map[string]interface{}{
"foo": "bar",
// has to be a float due to being a map[string]interface
"bar": float64(1),
},
},
},
AutoEncryptTLS: false,
AutoEncryptDNSSAN: []string{"a.com", "b.com"},
AutoEncryptIPSAN: []net.IP{net.ParseIP("192.168.4.139"), net.ParseIP("192.168.4.140")},
AutoEncryptAllowTLS: true,
AutoConfig: AutoConfig{
Enabled: false,
IntroToken: "OpBPGRwt",
IntroTokenFile: "gFvAXwI8",
DNSSANs: []string{"6zdaWg9J"},
IPSANs: []net.IP{net.IPv4(198, 18, 99, 99)},
ServerAddresses: []string{"198.18.100.1"},
Authorizer: AutoConfigAuthorizer{
Enabled: true,
AllowReuse: true,
ClaimAssertions: []string{"value.node == \"${node}\""},
AuthMethod: structs.ACLAuthMethod{
Name: "Auto Config Authorizer",
Type: "jwt",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Config: map[string]interface{}{
"JWTValidationPubKeys": []string{"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"},
"ClaimMappings": map[string]string{
"node": "node",
},
"BoundIssuer": "consul",
"BoundAudiences": []string{"consul-cluster-1"},
"ListClaimMappings": map[string]string{
"foo": "bar",
},
"OIDCDiscoveryURL": "",
"OIDCDiscoveryCACert": "",
"JWKSURL": "",
"JWKSCACert": "",
"ExpirationLeeway": 0 * time.Second,
"NotBeforeLeeway": 0 * time.Second,
"ClockSkewLeeway": 0 * time.Second,
"JWTSupportedAlgs": []string(nil),
},
},
},
},
ConnectEnabled: true,
ConnectSidecarMinPort: 8888,
ConnectSidecarMaxPort: 9999,
ExposeMinPort: 1111,
ExposeMaxPort: 2222,
ConnectCAProvider: "consul",
ConnectCAConfig: map[string]interface{}{
"IntermediateCertTTL": "8760h",
"LeafCertTTL": "1h",
"RootCertTTL": "96360h",
"CSRMaxPerSecond": float64(100),
"CSRMaxConcurrent": float64(2),
},
ConnectMeshGatewayWANFederationEnabled: false,
ConnectServerlessPluginEnabled: true,
DNSAddrs: []net.Addr{tcpAddr("93.95.95.81:7001"), udpAddr("93.95.95.81:7001")},
DNSARecordLimit: 29907,
DNSAllowStale: true,
DNSDisableCompression: true,
DNSDomain: "7W1xXSqd",
DNSAltDomain: "1789hsd",
DNSEnableTruncate: true,
DNSMaxStale: 29685 * time.Second,
DNSNodeTTL: 7084 * time.Second,
DNSOnlyPassing: true,
DNSPort: 7001,
DNSRecursorStrategy: "sequential",
DNSRecursorTimeout: 4427 * time.Second,
DNSRecursors: []string{"63.38.39.58", "92.49.18.18"},
DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0},
DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second},
DNSUDPAnswerLimit: 29909,
DNSNodeMetaTXT: true,
DNSUseCache: true,
DNSCacheMaxAge: 5 * time.Minute,
DataDir: dataDir,
Datacenter: "rzo029wg",
DefaultQueryTime: 16743 * time.Second,
DisableAnonymousSignature: true,
DisableCoordinates: true,
DisableHostNodeID: true,
DisableHTTPUnprintableCharFilter: true,
DisableKeyringFile: true,
DisableRemoteExec: true,
DisableUpdateCheck: true,
DiscardCheckOutput: true,
DiscoveryMaxStale: 5 * time.Second,
EnableAgentTLSForChecks: true,
EnableCentralServiceConfig: false,
EnableDebug: true,
EnableRemoteScriptChecks: true,
EnableLocalScriptChecks: true,
EncryptKey: "A4wELWqH",
EncryptVerifyIncoming: true,
EncryptVerifyOutgoing: true,
GRPCPort: 4881,
GRPCAddrs: []net.Addr{tcpAddr("32.31.61.91:4881")},
HTTPAddrs: []net.Addr{tcpAddr("83.39.91.39:7999")},
HTTPBlockEndpoints: []string{"RBvAFcGD", "fWOWFznh"},
AllowWriteHTTPFrom: []*net.IPNet{cidr("127.0.0.0/8"), cidr("22.33.44.55/32"), cidr("0.0.0.0/0")},
HTTPPort: 7999,
HTTPResponseHeaders: map[string]string{"M6TKa9NP": "xjuxjOzQ", "JRCrHZed": "rl0mTx81"},
HTTPSAddrs: []net.Addr{tcpAddr("95.17.17.19:15127")},
HTTPMaxConnsPerClient: 100,
HTTPMaxHeaderBytes: 10,
HTTPSHandshakeTimeout: 2391 * time.Millisecond,
HTTPSPort: 15127,
HTTPUseCache: false,
KeyFile: "IEkkwgIA",
KVMaxValueSize: 1234567800,
LeaveDrainTime: 8265 * time.Second,
LeaveOnTerm: true,
Logging: logging.Config{
LogLevel: "k1zo9Spt",
LogJSON: true,
EnableSyslog: true,
SyslogFacility: "hHv79Uia",
},
MaxQueryTime: 18237 * time.Second,
NodeID: types.NodeID("AsUIlw99"),
NodeMeta: map[string]string{"5mgGQMBk": "mJLtVMSG", "A7ynFMJB": "0Nx6RGab"},
NodeName: "otlLxGaI",
ReadReplica: true,
PidFile: "43xN80Km",
PrimaryGateways: []string{"aej8eeZo", "roh2KahS"},
PrimaryGatewaysInterval: 18866 * time.Second,
RPCAdvertiseAddr: tcpAddr("17.99.29.16:3757"),
RPCBindAddr: tcpAddr("16.99.34.17:3757"),
RPCHandshakeTimeout: 1932 * time.Millisecond,
RPCHoldTimeout: 15707 * time.Second,
RPCProtocol: 30793,
RPCRateLimit: 12029.43,
RPCMaxBurst: 44848,
RPCMaxConnsPerClient: 2954,
RaftProtocol: 3,
RaftSnapshotThreshold: 16384,
RaftSnapshotInterval: 30 * time.Second,
RaftTrailingLogs: 83749,
ReconnectTimeoutLAN: 23739 * time.Second,
ReconnectTimeoutWAN: 26694 * time.Second,
RejoinAfterLeave: true,
RetryJoinIntervalLAN: 8067 * time.Second,
RetryJoinIntervalWAN: 28866 * time.Second,
RetryJoinLAN: []string{"pbsSFY7U", "l0qLtWij"},
RetryJoinMaxAttemptsLAN: 913,
RetryJoinMaxAttemptsWAN: 23160,
RetryJoinWAN: []string{"PFsR02Ye", "rJdQIhER"},
RPCConfig: consul.RPCConfig{EnableStreaming: true},
SegmentLimit: 123,
SerfPortLAN: 8301,
SerfPortWAN: 8302,
ServerMode: true,
ServerName: "Oerr9n1G",
ServerPort: 3757,
Services: []*structs.ServiceDefinition{
{
ID: "wI1dzxS4",
Name: "7IszXMQ1",
Tags: []string{"0Zwg8l6v", "zebELdN5"},
Address: "9RhqPSPB",
Token: "myjKJkWH",
Port: 72219,
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
EnableTagOverride: true,
Checks: []*structs.CheckType{
{
CheckID: "qmfeO5if",
Name: "atDGP7n5",
Status: "pDQKEhWL",
Notes: "Yt8EDLev",
ScriptArgs: []string{"81EDZLPa", "bPY5X8xd"},
HTTP: "qzHYvmJO",
Header: map[string][]string{
"UkpmZ3a3": {"2dfzXuxZ"},
"cVFpko4u": {"gGqdEB6k", "9LsRo22u"},
},
Method: "X5DrovFc",
Body: "WeikigLh",
OutputMaxSize: checks.DefaultBufSize,
TCP: "ICbxkpSF",
H2PING: "7s7BbMyb",
H2PingUseTLS: false,
Interval: 24392 * time.Second,
DockerContainerID: "ZKXr68Yb",
Shell: "CEfzx0Fo",
TLSServerName: "4f191d4F",
TLSSkipVerify: true,
Timeout: 38333 * time.Second,
DeregisterCriticalServiceAfter: 44214 * time.Second,
},
},
// Note that although this SidecarService is only syntax sugar for
// registering another service, that has to happen in the agent code so
// it can make intelligent decisions about automatic port assignments
// etc. So we expect config just to pass it through verbatim.
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
},
},
{
ID: "MRHVMZuD",
Name: "6L6BVfgH",
Tags: []string{"7Ale4y6o", "PMBW08hy"},
Address: "R6H6g8h0",
Token: "ZgY8gjMI",
Port: 38292,
Weights: &structs.Weights{
Passing: 1979,
Warning: 6,
},
EnableTagOverride: true,
Checks: structs.CheckTypes{
&structs.CheckType{
CheckID: "GTti9hCo",
Name: "9OOS93ne",
Notes: "CQy86DH0",
Status: "P0SWDvrk",
ScriptArgs: []string{"EXvkYIuG", "BATOyt6h"},
HTTP: "u97ByEiW",
Header: map[string][]string{
"MUlReo8L": {"AUZG7wHG", "gsN0Dc2N"},
"1UJXjVrT": {"OJgxzTfk", "xZZrFsq7"},
},
Method: "5wkAxCUE",
Body: "7CRjCJyz",
OutputMaxSize: checks.DefaultBufSize,
TCP: "MN3oA9D2",
H2PING: "OV6Q2XEg",
H2PingUseTLS: false,
Interval: 32718 * time.Second,
DockerContainerID: "cU15LMet",
Shell: "nEz9qz2l",
TLSServerName: "f43ouY7a",
TLSSkipVerify: true,
Timeout: 34738 * time.Second,
DeregisterCriticalServiceAfter: 84282 * time.Second,
},
&structs.CheckType{
CheckID: "UHsDeLxG",
Name: "PQSaPWlT",
Notes: "jKChDOdl",
Status: "5qFz6OZn",
OutputMaxSize: checks.DefaultBufSize,
Timeout: 4868 * time.Second,
TTL: 11222 * time.Second,
DeregisterCriticalServiceAfter: 68482 * time.Second,
},
},
Connect: &structs.ServiceConnect{},
},
{
ID: "Kh81CPF6",
Name: "Kh81CPF6-proxy",
Port: 31471,
Kind: "connect-proxy",
Proxy: &structs.ConnectProxyConfig{
DestinationServiceName: "6L6BVfgH",
DestinationServiceID: "6L6BVfgH-id",
LocalServiceAddress: "127.0.0.2",
LocalServicePort: 23759,
Config: map[string]interface{}{
"cedGGtZf": "pWrUNiWw",
},
Upstreams: structs.Upstreams{
{
DestinationType: "service", // Default should be explicitly filled
DestinationName: "KPtAj2cb",
DestinationPartition: defaultEntMeta.PartitionOrEmpty(),
DestinationNamespace: defaultEntMeta.NamespaceOrEmpty(),
LocalBindPort: 4051,
Config: map[string]interface{}{
"kzRnZOyd": "nUNKoL8H",
},
},
{
DestinationType: "prepared_query",
DestinationNamespace: "9nakw0td",
DestinationPartition: "part-9nakw0td",
DestinationName: "KSd8HsRl",
LocalBindPort: 11884,
LocalBindAddress: "127.24.88.0",
},
{
DestinationType: "prepared_query",
DestinationNamespace: "9nakw0td",
DestinationPartition: "part-9nakw0td",
DestinationName: "placeholder",
LocalBindSocketPath: "/foo/bar/upstream",
LocalBindSocketMode: "0600",
},
},
Expose: structs.ExposeConfig{
Checks: true,
Paths: []structs.ExposePath{
{
Path: "/health",
LocalPathPort: 8080,
ListenerPort: 21500,
Protocol: "http",
},
},
},
Mode: structs.ProxyModeTransparent,
TransparentProxy: structs.TransparentProxyConfig{
OutboundListenerPort: 10101,
DialedDirectly: true,
},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
{
ID: "kvVqbwSE",
Kind: "mesh-gateway",
Name: "gw-primary-dc",
Port: 27147,
Proxy: &structs.ConnectProxyConfig{
Config: map[string]interface{}{
"1CuJHVfw": "Kzqsa7yc",
},
Upstreams: structs.Upstreams{},
},
Weights: &structs.Weights{
Passing: 1,
Warning: 1,
},
},
{
ID: "dLOXpSCI",
Name: "o1ynPkp0",
TaggedAddresses: map[string]structs.ServiceAddress{
"lan": {
Address: "2d79888a",
Port: 2143,
},
"wan": {
Address: "d4db85e2",
Port: 6109,
},
},
Tags: []string{"nkwshvM5", "NTDWn3ek"},
Address: "cOlSOhbp",
Token: "msy7iWER",
Meta: map[string]string{"mymeta": "data"},
Port: 24237,
Weights: &structs.Weights{
Passing: 100,
Warning: 1,
},
EnableTagOverride: true,
Connect: &structs.ServiceConnect{
Native: true,
},
Checks: structs.CheckTypes{
&structs.CheckType{
CheckID: "Zv99e9Ka",
Name: "sgV4F7Pk",
Notes: "yP5nKbW0",
Status: "7oLMEyfu",
ScriptArgs: []string{"5wEZtZpv", "0Ihyk8cS"},
HTTP: "KyDjGY9H",
Header: map[string][]string{
"gv5qefTz": {"5Olo2pMG", "PvvKWQU5"},
"SHOVq1Vv": {"jntFhyym", "GYJh32pp"},
},
Method: "T66MFBfR",
Body: "OwGjTFQi",
OutputMaxSize: checks.DefaultBufSize,
TCP: "bNnNfx2A",
H2PING: "qC1pidiW",
H2PingUseTLS: false,
Interval: 22224 * time.Second,
DockerContainerID: "ipgdFtjd",
Shell: "omVZq7Sz",
TLSServerName: "axw5QPL5",
TLSSkipVerify: true,
Timeout: 18913 * time.Second,
DeregisterCriticalServiceAfter: 8482 * time.Second,
},
&structs.CheckType{
CheckID: "G79O6Mpr",
Name: "IEqrzrsd",
Notes: "SVqApqeM",
Status: "XXkVoZXt",
ScriptArgs: []string{"wD05Bvao", "rLYB7kQC"},
HTTP: "kyICZsn8",
Header: map[string][]string{
"4ebP5vL4": {"G20SrL5Q", "DwPKlMbo"},
"p2UI34Qz": {"UsG1D0Qh", "NHhRiB6s"},
},
Method: "ciYHWors",
Body: "lUVLGYU7",
OutputMaxSize: checks.DefaultBufSize,
TCP: "FfvCwlqH",
H2PING: "spI3muI3",
H2PingUseTLS: false,
Interval: 12356 * time.Second,
DockerContainerID: "HBndBU6R",
Shell: "hVI33JjA",
TLSServerName: "7uwWOnUS",
TLSSkipVerify: true,
Timeout: 38282 * time.Second,
DeregisterCriticalServiceAfter: 4992 * time.Second,
},
&structs.CheckType{
CheckID: "RMi85Dv8",
Name: "iehanzuq",
Status: "rCvn53TH",
Notes: "fti5lfF3",
ScriptArgs: []string{"16WRUmwS", "QWk7j7ae"},
HTTP: "dl3Fgme3",
Header: map[string][]string{
"rjm4DEd3": {"2m3m2Fls"},
"l4HwQ112": {"fk56MNlo", "dhLK56aZ"},
},
Method: "9afLm3Mj",
Body: "wVVL2V6f",
OutputMaxSize: checks.DefaultBufSize,
TCP: "fjiLFqVd",
H2PING: "5NbNWhan",
H2PingUseTLS: false,
Interval: 23926 * time.Second,
DockerContainerID: "dO5TtRHk",
Shell: "e6q2ttES",
TLSServerName: "ECSHk8WF",
TLSSkipVerify: true,
Timeout: 38483 * time.Second,
DeregisterCriticalServiceAfter: 68787 * time.Second,
},
},
},
},
UseStreamingBackend: true,
SerfAdvertiseAddrLAN: tcpAddr("17.99.29.16:8301"),
SerfAdvertiseAddrWAN: tcpAddr("78.63.37.19:8302"),
SerfBindAddrLAN: tcpAddr("99.43.63.15:8301"),
SerfBindAddrWAN: tcpAddr("67.88.33.19:8302"),
SerfAllowedCIDRsLAN: []net.IPNet{},
SerfAllowedCIDRsWAN: []net.IPNet{},
SessionTTLMin: 26627 * time.Second,
SkipLeaveOnInt: true,
StartJoinAddrsLAN: []string{"LR3hGDoG", "MwVpZ4Up"},
StartJoinAddrsWAN: []string{"EbFSc3nA", "kwXTh623"},
Telemetry: lib.TelemetryConfig{
CirconusAPIApp: "p4QOTe9j",
CirconusAPIToken: "E3j35V23",
CirconusAPIURL: "mEMjHpGg",
CirconusBrokerID: "BHlxUhed",
CirconusBrokerSelectTag: "13xy1gHm",
CirconusCheckDisplayName: "DRSlQR6n",
CirconusCheckForceMetricActivation: "Ua5FGVYf",
CirconusCheckID: "kGorutad",
CirconusCheckInstanceID: "rwoOL6R4",
CirconusCheckSearchTag: "ovT4hT4f",
CirconusCheckTags: "prvO4uBl",
CirconusSubmissionInterval: "DolzaflP",
CirconusSubmissionURL: "gTcbS93G",
DisableCompatOneNine: true,
DisableHostname: true,
DogstatsdAddr: "0wSndumK",
DogstatsdTags: []string{"3N81zSUB", "Xtj8AnXZ"},
FilterDefault: true,
AllowedPrefixes: []string{"oJotS8XJ"},
BlockedPrefixes: []string{"cazlEhGn"},
MetricsPrefix: "ftO6DySn",
StatsdAddr: "drce87cy",
StatsiteAddr: "HpFwKB8R",
PrometheusOpts: prometheus.PrometheusOpts{
Expiration: 15 * time.Second,
Name: "ftO6DySn", // notice this is the same as the metrics prefix
},
},
TLSCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256},
TLSMinVersion: "pAOWafkR",
TLSPreferServerCipherSuites: true,
TaggedAddresses: map[string]string{
"7MYgHrYH": "dALJAhLD",
"h6DdBy6K": "ebrr9zZ8",
"lan": "17.99.29.16",
"lan_ipv4": "17.99.29.16",
"wan": "78.63.37.19",
"wan_ipv4": "78.63.37.19",
},
TranslateWANAddrs: true,
TxnMaxReqLen: 567800000,
UIConfig: UIConfig{
Dir: "pVncV4Ey",
ContentPath: "/qp1WRhYH/", // slashes are added in parsing
MetricsProvider: "sgnaoa_lower_case",
MetricsProviderFiles: []string{"sgnaMFoa", "dicnwkTH"},
MetricsProviderOptionsJSON: "{\"DIbVQadX\": 1}",
MetricsProxy: UIMetricsProxy{
BaseURL: "http://foo.bar",
AddHeaders: []UIMetricsProxyAddHeader{
{
Name: "p3nynwc9",
Value: "TYBgnN2F",
},
},
PathAllowlist: []string{"/aSh3cu", "/eiK/2Th"},
},
DashboardURLTemplates: map[string]string{"u2eziu2n_lower_case": "http://lkjasd.otr"},
},
UnixSocketUser: "E0nB1DwA",
UnixSocketGroup: "8pFodrV8",
UnixSocketMode: "E8sAwOv4",
VerifyIncoming: true,
VerifyIncomingHTTPS: true,
VerifyIncomingRPC: true,
VerifyOutgoing: true,
VerifyServerHostname: true,
Watches: []map[string]interface{}{
{
"type": "key",
"datacenter": "GyE6jpeW",
"key": "j9lF1Tve",
"handler": "90N7S4LN",
},
{
"type": "keyprefix",
"datacenter": "fYrl3F5d",
"key": "sl3Dffu7",
"args": []interface{}{"dltjDJ2a", "flEa7C2d"},
},
},
RaftBoltDBConfig: consul.RaftBoltDBConfig{NoFreelistSync: true},
}
entFullRuntimeConfig(expected)
expectedWarns := []string{
deprecationWarning("acl_datacenter", "primary_datacenter"),
deprecationWarning("acl_agent_master_token", "acl.tokens.agent_recovery"),
deprecationWarning("acl.tokens.agent_master", "acl.tokens.agent_recovery"),
deprecationWarning("acl_agent_token", "acl.tokens.agent"),
deprecationWarning("acl_token", "acl.tokens.default"),
deprecationWarning("acl_master_token", "acl.tokens.initial_management"),
deprecationWarning("acl.tokens.master", "acl.tokens.initial_management"),
deprecationWarning("acl_replication_token", "acl.tokens.replication"),
deprecationWarning("enable_acl_replication", "acl.enable_token_replication"),
deprecationWarning("acl_default_policy", "acl.default_policy"),
deprecationWarning("acl_down_policy", "acl.down_policy"),
deprecationWarning("acl_ttl", "acl.token_ttl"),
deprecationWarning("acl_enable_key_list_policy", "acl.enable_key_list_policy"),
`bootstrap_expect > 0: expecting 53 servers`,
}
expectedWarns = append(expectedWarns, enterpriseConfigKeyWarnings...)
// FIXME: ensure that all fields are set to unique non-zero values.
// There are many fields that are not set to non-zero values, so this nonZero
// check does not actually work.
if err := nonZero("RuntimeConfig", nil, expected); err != nil {
t.Log(err)
}
for _, format := range []string{"json", "hcl"} {
t.Run(format, func(t *testing.T) {
opts := LoadOpts{
ConfigFiles: []string{"testdata/full-config." + format},
HCL: []string{fmt.Sprintf(`data_dir = "%s"`, dataDir)},
}
opts.Overrides = append(opts.Overrides, versionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn"))
r, err := Load(opts)
require.NoError(t, err)
assertDeepEqual(t, expected, r.RuntimeConfig)
require.ElementsMatch(t, expectedWarns, r.Warnings, "Warnings: %#v", r.Warnings)
})
}
}
// nonZero verifies recursively that all fields are set to unique,
// non-zero and non-nil values.
//
// struct: check all fields recursively
// slice: check len > 0 and all values recursively
// ptr: check not nil
// bool: check not zero (cannot check uniqueness)
// string, int, uint: check not zero and unique
// other: error
func nonZero(name string, uniq map[interface{}]string, v interface{}) error {
if v == nil {
return fmt.Errorf("%q is nil", name)
}
if uniq == nil {
uniq = map[interface{}]string{}
}
isUnique := func(v interface{}) error {
if other := uniq[v]; other != "" {
return fmt.Errorf("%q and %q both use value %q", name, other, v)
}
uniq[v] = name
return nil
}
val, typ := reflect.ValueOf(v), reflect.TypeOf(v)
// fmt.Printf("%s: %T\n", name, v)
switch typ.Kind() {
case reflect.Struct:
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
fieldname := fmt.Sprintf("%s.%s", name, f.Name)
err := nonZero(fieldname, uniq, val.Field(i).Interface())
if err != nil {
return err
}
}
case reflect.Slice:
if val.Len() == 0 {
return fmt.Errorf("%q is empty slice", name)
}
for i := 0; i < val.Len(); i++ {
elemname := fmt.Sprintf("%s[%d]", name, i)
err := nonZero(elemname, uniq, val.Index(i).Interface())
if err != nil {
return err
}
}
case reflect.Map:
if val.Len() == 0 {
return fmt.Errorf("%q is empty map", name)
}
for _, key := range val.MapKeys() {
keyname := fmt.Sprintf("%s[%s]", name, key.String())
if err := nonZero(keyname, uniq, key.Interface()); err != nil {
if strings.Contains(err.Error(), "is zero value") {
return fmt.Errorf("%q has zero value map key", name)
}
return err
}
if err := nonZero(keyname, uniq, val.MapIndex(key).Interface()); err != nil {
return err
}
}
case reflect.Bool:
if val.Bool() != true {
return fmt.Errorf("%q is zero value", name)
}
// do not test bool for uniqueness since there are only two values
case reflect.String:
if val.Len() == 0 {
return fmt.Errorf("%q is zero value", name)
}
return isUnique(v)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if val.Int() == 0 {
return fmt.Errorf("%q is zero value", name)
}
return isUnique(v)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if val.Uint() == 0 {
return fmt.Errorf("%q is zero value", name)
}
return isUnique(v)
case reflect.Float32, reflect.Float64:
if val.Float() == 0 {
return fmt.Errorf("%q is zero value", name)
}
return isUnique(v)
case reflect.Ptr:
if val.IsNil() {
return fmt.Errorf("%q is nil", name)
}
return nonZero("*"+name, uniq, val.Elem().Interface())
default:
return fmt.Errorf("%T is not supported", v)
}
return nil
}
func TestNonZero(t *testing.T) {
tests := []struct {
desc string
v interface{}
err error
}{
{"nil", nil, errors.New(`"x" is nil`)},
{"zero bool", false, errors.New(`"x" is zero value`)},
{"zero string", "", errors.New(`"x" is zero value`)},
{"zero int", int(0), errors.New(`"x" is zero value`)},
{"zero int8", int8(0), errors.New(`"x" is zero value`)},
{"zero int16", int16(0), errors.New(`"x" is zero value`)},
{"zero int32", int32(0), errors.New(`"x" is zero value`)},
{"zero int64", int64(0), errors.New(`"x" is zero value`)},
{"zero uint", uint(0), errors.New(`"x" is zero value`)},
{"zero uint8", uint8(0), errors.New(`"x" is zero value`)},
{"zero uint16", uint16(0), errors.New(`"x" is zero value`)},
{"zero uint32", uint32(0), errors.New(`"x" is zero value`)},
{"zero uint64", uint64(0), errors.New(`"x" is zero value`)},
{"zero float32", float32(0), errors.New(`"x" is zero value`)},
{"zero float64", float64(0), errors.New(`"x" is zero value`)},
{"ptr to zero value", pString(""), errors.New(`"*x" is zero value`)},
{"empty slice", []string{}, errors.New(`"x" is empty slice`)},
{"slice with zero value", []string{""}, errors.New(`"x[0]" is zero value`)},
{"empty map", map[string]string{}, errors.New(`"x" is empty map`)},
{"map with zero value key", map[string]string{"": "y"}, errors.New(`"x" has zero value map key`)},
{"map with zero value elem", map[string]string{"y": ""}, errors.New(`"x[y]" is zero value`)},
{"struct with nil field", struct{ Y *int }{}, errors.New(`"x.Y" is nil`)},
{"struct with zero value field", struct{ Y string }{}, errors.New(`"x.Y" is zero value`)},
{"struct with empty array", struct{ Y []string }{}, errors.New(`"x.Y" is empty slice`)},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
require.Equal(t, tt.err, nonZero("x", nil, tt.v))
})
}
}
func TestConfigDecodeBytes(t *testing.T) {
// Test with some input
src := []byte("abc")
key := base64.StdEncoding.EncodeToString(src)
result, err := decodeBytes(key)
if err != nil {
t.Fatalf("err: %s", err)
}
if !bytes.Equal(src, result) {
t.Fatalf("bad: %#v", result)
}
// Test with no input
result, err = decodeBytes("")
if err != nil {
t.Fatalf("err: %s", err)
}
if len(result) > 0 {
t.Fatalf("bad: %#v", result)
}
}
func parseCIDR(t *testing.T, cidr string) *net.IPNet {
_, x, err := net.ParseCIDR(cidr)
if err != nil {
t.Fatalf("CIDRParse: %v", err)
}
return x
}
func TestRuntimeConfig_Sanitize(t *testing.T) {
rt := RuntimeConfig{
BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")},
CheckOutputMaxSize: checks.DefaultBufSize,
SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
DNSAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
&net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
},
DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0},
AllowWriteHTTPFrom: []*net.IPNet{
parseCIDR(t, "127.0.0.0/8"),
parseCIDR(t, "::1/128"),
},
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
&net.UnixAddr{Name: "/var/run/foo"},
},
Cache: cache.Options{
EntryFetchMaxBurst: 42,
EntryFetchRate: 0.334,
},
ConsulCoordinateUpdatePeriod: 15 * time.Second,
RaftProtocol: 3,
RetryJoinLAN: []string{
"foo=bar key=baz secret=boom bang=bar",
},
RetryJoinWAN: []string{
"wan_foo=bar wan_key=baz wan_secret=boom wan_bang=bar",
},
PrimaryGateways: []string{
"pmgw_foo=bar pmgw_key=baz pmgw_secret=boom pmgw_bang=bar",
},
Services: []*structs.ServiceDefinition{
{
Name: "foo",
Token: "bar",
Check: structs.CheckType{
Name: "blurb",
OutputMaxSize: checks.DefaultBufSize,
},
Weights: &structs.Weights{
Passing: 67,
Warning: 3,
},
},
},
Checks: []*structs.CheckDefinition{
{
Name: "zoo",
Token: "zope",
OutputMaxSize: checks.DefaultBufSize,
},
},
KVMaxValueSize: 1234567800000000,
SerfAllowedCIDRsLAN: []net.IPNet{
*parseCIDR(t, "192.168.1.0/24"),
*parseCIDR(t, "127.0.0.0/8"),
},
TxnMaxReqLen: 5678000000000000,
UIConfig: UIConfig{
MetricsProxy: UIMetricsProxy{
AddHeaders: []UIMetricsProxyAddHeader{
{Name: "foo", Value: "secret"},
},
},
},
}
b, err := json.MarshalIndent(rt.Sanitized(), "", " ")
require.NoError(t, err)
actual := string(b)
require.JSONEq(t, golden(t, actual, testRuntimeConfigSanitizeExpectedFilename), actual)
}
func TestRuntime_apiAddresses(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
&net.UnixAddr{Name: "/var/run/foo"},
},
HTTPSAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678},
}}
unixAddrs, httpAddrs, httpsAddrs := rt.apiAddresses(1)
require.Len(t, unixAddrs, 1)
require.Len(t, httpAddrs, 1)
require.Len(t, httpsAddrs, 1)
require.Equal(t, "/var/run/foo", unixAddrs[0])
require.Equal(t, "198.18.0.1:5678", httpAddrs[0])
require.Equal(t, "198.18.0.2:5678", httpsAddrs[0])
}
func TestRuntime_APIConfigHTTPS(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
&net.UnixAddr{Name: "/var/run/foo"},
},
HTTPSAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678},
},
Datacenter: "dc-test",
CAFile: "/etc/consul/ca.crt",
CAPath: "/etc/consul/ca.dir",
CertFile: "/etc/consul/server.crt",
KeyFile: "/etc/consul/ssl/server.key",
VerifyOutgoing: false,
}
cfg, err := rt.APIConfig(false)
require.NoError(t, err)
require.Equal(t, "198.18.0.2:5678", cfg.Address)
require.Equal(t, "https", cfg.Scheme)
require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile)
require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath)
require.Equal(t, "", cfg.TLSConfig.CertFile)
require.Equal(t, "", cfg.TLSConfig.KeyFile)
require.Equal(t, rt.Datacenter, cfg.Datacenter)
require.Equal(t, true, cfg.TLSConfig.InsecureSkipVerify)
rt.VerifyOutgoing = true
cfg, err = rt.APIConfig(true)
require.NoError(t, err)
require.Equal(t, "198.18.0.2:5678", cfg.Address)
require.Equal(t, "https", cfg.Scheme)
require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile)
require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath)
require.Equal(t, rt.CertFile, cfg.TLSConfig.CertFile)
require.Equal(t, rt.KeyFile, cfg.TLSConfig.KeyFile)
require.Equal(t, rt.Datacenter, cfg.Datacenter)
require.Equal(t, false, cfg.TLSConfig.InsecureSkipVerify)
}
func TestRuntime_APIConfigHTTP(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.UnixAddr{Name: "/var/run/foo"},
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
},
Datacenter: "dc-test",
}
cfg, err := rt.APIConfig(false)
require.NoError(t, err)
require.Equal(t, rt.Datacenter, cfg.Datacenter)
require.Equal(t, "198.18.0.1:5678", cfg.Address)
require.Equal(t, "http", cfg.Scheme)
require.Equal(t, "", cfg.TLSConfig.CAFile)
require.Equal(t, "", cfg.TLSConfig.CAPath)
require.Equal(t, "", cfg.TLSConfig.CertFile)
require.Equal(t, "", cfg.TLSConfig.KeyFile)
}
func TestRuntime_APIConfigUNIX(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.UnixAddr{Name: "/var/run/foo"},
},
Datacenter: "dc-test",
}
cfg, err := rt.APIConfig(false)
require.NoError(t, err)
require.Equal(t, rt.Datacenter, cfg.Datacenter)
require.Equal(t, "unix:///var/run/foo", cfg.Address)
require.Equal(t, "http", cfg.Scheme)
require.Equal(t, "", cfg.TLSConfig.CAFile)
require.Equal(t, "", cfg.TLSConfig.CAPath)
require.Equal(t, "", cfg.TLSConfig.CertFile)
require.Equal(t, "", cfg.TLSConfig.KeyFile)
}
func TestRuntime_APIConfigANYAddrV4(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 5678},
},
Datacenter: "dc-test",
}
cfg, err := rt.APIConfig(false)
require.NoError(t, err)
require.Equal(t, rt.Datacenter, cfg.Datacenter)
require.Equal(t, "127.0.0.1:5678", cfg.Address)
require.Equal(t, "http", cfg.Scheme)
require.Equal(t, "", cfg.TLSConfig.CAFile)
require.Equal(t, "", cfg.TLSConfig.CAPath)
require.Equal(t, "", cfg.TLSConfig.CertFile)
require.Equal(t, "", cfg.TLSConfig.KeyFile)
}
func TestRuntime_APIConfigANYAddrV6(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("::"), Port: 5678},
},
Datacenter: "dc-test",
}
cfg, err := rt.APIConfig(false)
require.NoError(t, err)
require.Equal(t, rt.Datacenter, cfg.Datacenter)
require.Equal(t, "[::1]:5678", cfg.Address)
require.Equal(t, "http", cfg.Scheme)
require.Equal(t, "", cfg.TLSConfig.CAFile)
require.Equal(t, "", cfg.TLSConfig.CAPath)
require.Equal(t, "", cfg.TLSConfig.CertFile)
require.Equal(t, "", cfg.TLSConfig.KeyFile)
}
func TestRuntime_ClientAddress(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("::"), Port: 5678},
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5679},
&net.UnixAddr{Name: "/var/run/foo", Net: "unix"},
},
HTTPSAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("::"), Port: 5688},
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5689},
},
}
unix, http, https := rt.ClientAddress()
require.Equal(t, "unix:///var/run/foo", unix)
require.Equal(t, "198.18.0.1:5679", http)
require.Equal(t, "198.18.0.1:5689", https)
}
func TestRuntime_ClientAddressAnyV4(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 5678},
&net.UnixAddr{Name: "/var/run/foo", Net: "unix"},
},
HTTPSAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 5688},
},
}
unix, http, https := rt.ClientAddress()
require.Equal(t, "unix:///var/run/foo", unix)
require.Equal(t, "127.0.0.1:5678", http)
require.Equal(t, "127.0.0.1:5688", https)
}
func TestRuntime_ClientAddressAnyV6(t *testing.T) {
rt := RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("::"), Port: 5678},
&net.UnixAddr{Name: "/var/run/foo", Net: "unix"},
},
HTTPSAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("::"), Port: 5688},
},
}
unix, http, https := rt.ClientAddress()
require.Equal(t, "unix:///var/run/foo", unix)
require.Equal(t, "[::1]:5678", http)
require.Equal(t, "[::1]:5688", https)
}
func TestRuntime_ToTLSUtilConfig(t *testing.T) {
c := &RuntimeConfig{
VerifyIncoming: true,
VerifyIncomingRPC: true,
VerifyIncomingHTTPS: true,
VerifyOutgoing: true,
VerifyServerHostname: true,
CAFile: "a",
CAPath: "b",
CertFile: "c",
KeyFile: "d",
NodeName: "e",
ServerName: "f",
DNSDomain: "g",
TLSMinVersion: "tls12",
TLSCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
TLSPreferServerCipherSuites: true,
EnableAgentTLSForChecks: true,
AutoEncryptTLS: true,
}
r := c.ToTLSUtilConfig()
require.True(t, r.VerifyIncoming)
require.True(t, r.VerifyIncomingRPC)
require.True(t, r.VerifyIncomingHTTPS)
require.True(t, r.VerifyOutgoing)
require.True(t, r.EnableAgentTLSForChecks)
require.True(t, r.AutoTLS)
require.True(t, r.VerifyServerHostname)
require.True(t, r.PreferServerCipherSuites)
require.Equal(t, "a", r.CAFile)
require.Equal(t, "b", r.CAPath)
require.Equal(t, "c", r.CertFile)
require.Equal(t, "d", r.KeyFile)
require.Equal(t, "e", r.NodeName)
require.Equal(t, "f", r.ServerName)
require.Equal(t, "g", r.Domain)
require.Equal(t, "tls12", r.TLSMinVersion)
require.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, r.CipherSuites)
}
func TestRuntime_ToTLSUtilConfig_AutoConfig(t *testing.T) {
c := &RuntimeConfig{
VerifyIncoming: true,
VerifyIncomingRPC: true,
VerifyIncomingHTTPS: true,
VerifyOutgoing: true,
VerifyServerHostname: true,
CAFile: "a",
CAPath: "b",
CertFile: "c",
KeyFile: "d",
NodeName: "e",
ServerName: "f",
DNSDomain: "g",
TLSMinVersion: "tls12",
TLSCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
TLSPreferServerCipherSuites: true,
EnableAgentTLSForChecks: true,
AutoConfig: AutoConfig{Enabled: true},
}
r := c.ToTLSUtilConfig()
require.True(t, r.VerifyIncoming)
require.True(t, r.VerifyIncomingRPC)
require.True(t, r.VerifyIncomingHTTPS)
require.True(t, r.VerifyOutgoing)
require.True(t, r.EnableAgentTLSForChecks)
require.True(t, r.AutoTLS)
require.True(t, r.VerifyServerHostname)
require.True(t, r.PreferServerCipherSuites)
require.Equal(t, "a", r.CAFile)
require.Equal(t, "b", r.CAPath)
require.Equal(t, "c", r.CertFile)
require.Equal(t, "d", r.KeyFile)
require.Equal(t, "e", r.NodeName)
require.Equal(t, "f", r.ServerName)
require.Equal(t, "g", r.Domain)
require.Equal(t, "tls12", r.TLSMinVersion)
require.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, r.CipherSuites)
}
func Test_UIPathBuilder(t *testing.T) {
cases := []struct {
name string
path string
expected string
}{
{
"Letters only string",
"hello",
"/hello/",
},
{
"Alphanumeric",
"Hello1",
"/Hello1/",
},
{
"Hyphen and underscore",
"-_",
"/-_/",
},
{
"Many slashes",
"/hello/ui/1/",
"/hello/ui/1/",
},
}
for _, tt := range cases {
actual := UIPathBuilder(tt.path)
require.Equal(t, tt.expected, actual)
}
}
func splitIPPort(hostport string) (net.IP, int) {
h, p, err := net.SplitHostPort(hostport)
if err != nil {
panic(err)
}
port, err := strconv.Atoi(p)
if err != nil {
panic(err)
}
return net.ParseIP(h), port
}
func ipAddr(addr string) *net.IPAddr {
return &net.IPAddr{IP: net.ParseIP(addr)}
}
func tcpAddr(addr string) *net.TCPAddr {
ip, port := splitIPPort(addr)
return &net.TCPAddr{IP: ip, Port: port}
}
func udpAddr(addr string) *net.UDPAddr {
ip, port := splitIPPort(addr)
return &net.UDPAddr{IP: ip, Port: port}
}
func unixAddr(addr string) *net.UnixAddr {
if !strings.HasPrefix(addr, "unix://") {
panic("not a unix socket addr: " + addr)
}
return &net.UnixAddr{Net: "unix", Name: addr[len("unix://"):]}
}
func writeFile(path string, data []byte) {
if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
panic(err)
}
if err := ioutil.WriteFile(path, data, 0640); err != nil {
panic(err)
}
}
func randomString(n int) string {
s := ""
for ; n > 0; n-- {
s += "x"
}
return s
}
func metaPairs(n int, format string) string {
var s []string
for i := 0; i < n; i++ {
switch format {
case "json":
s = append(s, fmt.Sprintf(`"%d":"%d"`, i, i))
case "hcl":
s = append(s, fmt.Sprintf(`"%d"="%d"`, i, i))
default:
panic("invalid format: " + format)
}
}
switch format {
case "json":
return strings.Join(s, ",")
case "hcl":
return strings.Join(s, " ")
default:
panic("invalid format: " + format)
}
}
func TestConnectCAConfiguration(t *testing.T) {
type testCase struct {
config RuntimeConfig
expected *structs.CAConfiguration
err string
}
cases := map[string]testCase{
"defaults": {
config: RuntimeConfig{
ConnectEnabled: true,
},
expected: &structs.CAConfiguration{
Provider: "consul",
Config: map[string]interface{}{
"LeafCertTTL": "72h",
"IntermediateCertTTL": "8760h", // 365 * 24h
"RootCertTTL": "87600h", // 365 * 10 * 24h
},
},
},
"cluster-id-override": {
config: RuntimeConfig{
ConnectEnabled: true,
ConnectCAConfig: map[string]interface{}{
"cluster_id": "adfe7697-09b4-413a-ac0a-fa81ed3a3001",
},
},
expected: &structs.CAConfiguration{
Provider: "consul",
ClusterID: "adfe7697-09b4-413a-ac0a-fa81ed3a3001",
Config: map[string]interface{}{
"LeafCertTTL": "72h",
"IntermediateCertTTL": "8760h", // 365 * 24h
"RootCertTTL": "87600h", // 365 * 10 * 24h
"cluster_id": "adfe7697-09b4-413a-ac0a-fa81ed3a3001",
},
},
},
"cluster-id-non-uuid": {
config: RuntimeConfig{
ConnectEnabled: true,
ConnectCAConfig: map[string]interface{}{
"cluster_id": "foo",
},
},
err: "cluster_id was supplied but was not a valid UUID",
},
"provider-override": {
config: RuntimeConfig{
ConnectEnabled: true,
ConnectCAProvider: "vault",
},
expected: &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"LeafCertTTL": "72h",
"IntermediateCertTTL": "8760h", // 365 * 24h
"RootCertTTL": "87600h", // 365 * 10 * 24h
},
},
},
"other-config": {
config: RuntimeConfig{
ConnectEnabled: true,
ConnectCAConfig: map[string]interface{}{
"foo": "bar",
"RootCertTTL": "8761h", // 365 * 24h + 1
},
},
expected: &structs.CAConfiguration{
Provider: "consul",
Config: map[string]interface{}{
"LeafCertTTL": "72h",
"IntermediateCertTTL": "8760h", // 365 * 24h
"RootCertTTL": "8761h", // 365 * 24h + 1
"foo": "bar",
},
},
},
}
for name, tcase := range cases {
t.Run(name, func(t *testing.T) {
actual, err := tcase.config.ConnectCAConfiguration()
if tcase.err != "" {
testutil.RequireErrorContains(t, err, tcase.err)
} else {
require.NoError(t, err)
require.Equal(t, tcase.expected, actual)
}
})
}
}