diff --git a/agent/agent.go b/agent/agent.go index 4ac1a4314e..ea3c807a33 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -5,7 +5,6 @@ import ( "crypto/sha512" "crypto/tls" "encoding/json" - "errors" "fmt" "io" "io/ioutil" @@ -30,7 +29,6 @@ import ( "github.com/hashicorp/consul/logger" "github.com/hashicorp/consul/types" "github.com/hashicorp/consul/watch" - "github.com/hashicorp/go-sockaddr/template" "github.com/hashicorp/go-uuid" "github.com/hashicorp/raft" "github.com/hashicorp/serf/coordinate" @@ -228,55 +226,6 @@ func New(c *Config) (*Agent, error) { httpAddrs: httpAddrs, tokens: new(token.Store), } - if err := a.resolveTmplAddrs(); err != nil { - return nil, err - } - - // Try to get an advertise address - switch { - case a.config.AdvertiseAddr != "": - ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddr) - if err != nil { - return nil, fmt.Errorf("Advertise address resolution failed: %v", err) - } - if net.ParseIP(ipStr) == nil { - return nil, fmt.Errorf("Failed to parse advertise address: %v", ipStr) - } - a.config.AdvertiseAddr = ipStr - - case a.config.BindAddr != "" && !ipaddr.IsAny(a.config.BindAddr): - a.config.AdvertiseAddr = a.config.BindAddr - - default: - ip, err := consul.GetPrivateIP() - if ipaddr.IsAnyV6(a.config.BindAddr) { - ip, err = consul.GetPublicIPv6() - } - if err != nil { - return nil, fmt.Errorf("Failed to get advertise address: %v", err) - } - a.config.AdvertiseAddr = ip.String() - } - - // Try to get an advertise address for the wan - if a.config.AdvertiseAddrWan != "" { - ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddrWan) - if err != nil { - return nil, fmt.Errorf("Advertise WAN address resolution failed: %v", err) - } - if net.ParseIP(ipStr) == nil { - return nil, fmt.Errorf("Failed to parse advertise address for WAN: %v", ipStr) - } - a.config.AdvertiseAddrWan = ipStr - } else { - a.config.AdvertiseAddrWan = a.config.AdvertiseAddr - } - - // Create the default set of tagged addresses. - a.config.TaggedAddresses = map[string]string{ - "lan": a.config.AdvertiseAddr, - "wan": a.config.AdvertiseAddrWan, - } // Set up the initial state of the token store based on the config. a.tokens.UpdateUserToken(a.config.ACLToken) @@ -820,113 +769,6 @@ func (a *Agent) consulConfig() (*consul.Config, error) { return base, nil } -// parseSingleIPTemplate is used as a helper function to parse out a single IP -// address from a config parameter. -func parseSingleIPTemplate(ipTmpl string) (string, error) { - out, err := template.Parse(ipTmpl) - if err != nil { - return "", fmt.Errorf("Unable to parse address template %q: %v", ipTmpl, err) - } - - ips := strings.Split(out, " ") - switch len(ips) { - case 0: - return "", errors.New("No addresses found, please configure one.") - case 1: - return ips[0], nil - default: - return "", fmt.Errorf("Multiple addresses found (%q), please configure one.", out) - } -} - -// resolveTmplAddrs iterates over the myriad of addresses in the agent's config -// and performs go-sockaddr/template Parse on each known address in case the -// user specified a template config for any of their values. -func (a *Agent) resolveTmplAddrs() error { - if a.config.AdvertiseAddr != "" { - ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddr) - if err != nil { - return fmt.Errorf("Advertise address resolution failed: %v", err) - } - a.config.AdvertiseAddr = ipStr - } - - if a.config.Addresses.DNS != "" { - ipStr, err := parseSingleIPTemplate(a.config.Addresses.DNS) - if err != nil { - return fmt.Errorf("DNS address resolution failed: %v", err) - } - a.config.Addresses.DNS = ipStr - } - - if a.config.Addresses.HTTP != "" { - ipStr, err := parseSingleIPTemplate(a.config.Addresses.HTTP) - if err != nil { - return fmt.Errorf("HTTP address resolution failed: %v", err) - } - a.config.Addresses.HTTP = ipStr - } - - if a.config.Addresses.HTTPS != "" { - ipStr, err := parseSingleIPTemplate(a.config.Addresses.HTTPS) - if err != nil { - return fmt.Errorf("HTTPS address resolution failed: %v", err) - } - a.config.Addresses.HTTPS = ipStr - } - - if a.config.AdvertiseAddrWan != "" { - ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddrWan) - if err != nil { - return fmt.Errorf("Advertise WAN address resolution failed: %v", err) - } - a.config.AdvertiseAddrWan = ipStr - } - - if a.config.BindAddr != "" { - ipStr, err := parseSingleIPTemplate(a.config.BindAddr) - if err != nil { - return fmt.Errorf("Bind address resolution failed: %v", err) - } - a.config.BindAddr = ipStr - } - - if a.config.ClientAddr != "" { - ipStr, err := parseSingleIPTemplate(a.config.ClientAddr) - if err != nil { - return fmt.Errorf("Client address resolution failed: %v", err) - } - a.config.ClientAddr = ipStr - } - - if a.config.SerfLanBindAddr != "" { - ipStr, err := parseSingleIPTemplate(a.config.SerfLanBindAddr) - if err != nil { - return fmt.Errorf("Serf LAN Address resolution failed: %v", err) - } - a.config.SerfLanBindAddr = ipStr - } - - if a.config.SerfWanBindAddr != "" { - ipStr, err := parseSingleIPTemplate(a.config.SerfWanBindAddr) - if err != nil { - return fmt.Errorf("Serf WAN Address resolution failed: %v", err) - } - a.config.SerfWanBindAddr = ipStr - } - - // Parse all tagged addresses - for k, v := range a.config.TaggedAddresses { - ipStr, err := parseSingleIPTemplate(v) - if err != nil { - return fmt.Errorf("%s address resolution failed: %v", k, err) - } - a.config.TaggedAddresses[k] = ipStr - } - - return nil -} - // makeRandomID will generate a random UUID for a node. func (a *Agent) makeRandomID() (string, error) { id, err := uuid.GenerateUUID() diff --git a/agent/agent_test.go b/agent/agent_test.go index ab7463c644..e5cf02edcc 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -117,6 +117,7 @@ func TestAgent_CheckAdvertiseAddrsSettings(t *testing.T) { cfg.AdvertiseAddrs.SerfLan, _ = net.ResolveTCPAddr("tcp", "127.0.0.42:1233") cfg.AdvertiseAddrs.SerfWan, _ = net.ResolveTCPAddr("tcp", "127.0.0.43:1234") cfg.AdvertiseAddrs.RPC, _ = net.ResolveTCPAddr("tcp", "127.0.0.44:1235") + cfg.SetupTaggedAndAdvertiseAddrs() a := NewTestAgent(t.Name(), cfg) defer a.Shutdown() diff --git a/agent/config.go b/agent/config.go index dd21cd6088..c6679064ec 100644 --- a/agent/config.go +++ b/agent/config.go @@ -16,10 +16,12 @@ import ( "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul/structs" + "github.com/hashicorp/consul/ipaddr" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" "github.com/hashicorp/consul/watch" + "github.com/hashicorp/go-sockaddr/template" "github.com/mitchellh/mapstructure" ) @@ -2121,6 +2123,101 @@ func ReadConfigPaths(paths []string) (*Config, error) { return result, nil } +// ResolveTmplAddrs iterates over the myriad of addresses in the agent's config +// and performs go-sockaddr/template Parse on each known address in case the +// user specified a template config for any of their values. +func (c *Config) ResolveTmplAddrs() (err error) { + parse := func(addr *string, socketAllowed bool, name string) { + if *addr == "" || err != nil { + return + } + var ip string + ip, err = parseSingleIPTemplate(*addr) + if err != nil { + err = fmt.Errorf("Resolution of %s failed: %v", name, err) + return + } + ipAddr := net.ParseIP(ip) + if !socketAllowed && ipAddr == nil { + err = fmt.Errorf("Failed to parse %s: %v", name, ip) + return + } + if socketAllowed && socketPath(ip) == "" && ipAddr == nil { + err = fmt.Errorf("Failed to parse %s, %q is not a valid IP address or socket", name, ip) + return + } + + *addr = ip + } + + if c == nil { + return + } + parse(&c.Addresses.DNS, true, "DNS address") + parse(&c.Addresses.HTTP, true, "HTTP address") + parse(&c.Addresses.HTTPS, true, "HTTPS address") + parse(&c.AdvertiseAddr, false, "Advertise address") + parse(&c.AdvertiseAddrWan, false, "Advertise WAN address") + parse(&c.BindAddr, true, "Bind address") + parse(&c.ClientAddr, true, "Client address") + parse(&c.SerfLanBindAddr, false, "Serf LAN address") + parse(&c.SerfWanBindAddr, false, "Serf WAN address") + + return +} + +// SetupTaggedAndAdvertiseAddrs configures advertise addresses and sets up a map of tagged addresses +func (cfg *Config) SetupTaggedAndAdvertiseAddrs() error { + if cfg.AdvertiseAddr == "" { + switch { + + case cfg.BindAddr != "" && !ipaddr.IsAny(cfg.BindAddr): + cfg.AdvertiseAddr = cfg.BindAddr + + default: + ip, err := consul.GetPrivateIP() + if ipaddr.IsAnyV6(cfg.BindAddr) { + ip, err = consul.GetPublicIPv6() + } + if err != nil { + return fmt.Errorf("Failed to get advertise address: %v", err) + } + cfg.AdvertiseAddr = ip.String() + } + } + + // Try to get an advertise address for the wan + if cfg.AdvertiseAddrWan == "" { + cfg.AdvertiseAddrWan = cfg.AdvertiseAddr + } + + // Create the default set of tagged addresses. + cfg.TaggedAddresses = map[string]string{ + "lan": cfg.AdvertiseAddr, + "wan": cfg.AdvertiseAddrWan, + } + return nil +} + +// parseSingleIPTemplate is used as a helper function to parse out a single IP +// address from a config parameter. +func parseSingleIPTemplate(ipTmpl string) (string, error) { + out, err := template.Parse(ipTmpl) + if err != nil { + return "", fmt.Errorf("Unable to parse address template %q: %v", ipTmpl, err) + } + + ips := strings.Split(out, " ") + switch len(ips) { + case 0: + return "", errors.New("No addresses found, please configure one.") + case 1: + return ips[0], nil + default: + return "", fmt.Errorf("Multiple addresses found (%q), please configure one.", out) + } +} + // Implement the sort interface for dirEnts func (d dirEnts) Len() int { return len(d) diff --git a/agent/config_test.go b/agent/config_test.go index 9ad77ab4a1..101f87b8f2 100644 --- a/agent/config_test.go +++ b/agent/config_test.go @@ -50,18 +50,34 @@ func TestConfigEncryptBytes(t *testing.T) { func TestDecodeConfig(t *testing.T) { tests := []struct { - desc string - in string - c *Config - err error + desc string + in string + c *Config + err error + parseTemplateErr error }{ // special flows { in: `{"bad": "no way jose"}`, err: errors.New("Config has invalid keys: bad"), }, + { + in: `{"advertise_addr":"unix:///path/to/file"}`, + parseTemplateErr: errors.New("Failed to parse Advertise address: unix:///path/to/file"), + c: &Config{AdvertiseAddr: "unix:///path/to/file"}, + }, + { + in: `{"advertise_addr_wan":"unix:///path/to/file"}`, + parseTemplateErr: errors.New("Failed to parse Advertise WAN address: unix:///path/to/file"), + c: &Config{AdvertiseAddrWan: "unix:///path/to/file"}, + }, + { + in: `{"addresses":{"http":"notunix://blah"}}`, + parseTemplateErr: errors.New("Failed to parse HTTP address, \"notunix://blah\" is not a valid IP address or socket"), + c: &Config{Addresses: AddressConfig{HTTP: "notunix://blah"}}, + }, - // happy flows in alphabeical order + // happy flows in alphabetical order { in: `{"acl_agent_master_token":"a"}`, c: &Config{ACLAgentMasterToken: "a"}, @@ -103,28 +119,56 @@ func TestDecodeConfig(t *testing.T) { c: &Config{ACLTTL: 2 * time.Second, ACLTTLRaw: "2s"}, }, { - in: `{"addresses":{"dns":"a"}}`, - c: &Config{Addresses: AddressConfig{DNS: "a"}}, + in: `{"addresses":{"dns":"1.2.3.4"}}`, + c: &Config{Addresses: AddressConfig{DNS: "1.2.3.4"}}, }, { - in: `{"addresses":{"http":"a"}}`, - c: &Config{Addresses: AddressConfig{HTTP: "a"}}, + in: `{"addresses":{"dns":"{{\"1.2.3.4\"}}"}}`, + c: &Config{Addresses: AddressConfig{DNS: "1.2.3.4"}}, }, { - in: `{"addresses":{"https":"a"}}`, - c: &Config{Addresses: AddressConfig{HTTPS: "a"}}, + in: `{"addresses":{"http":"1.2.3.4"}}`, + c: &Config{Addresses: AddressConfig{HTTP: "1.2.3.4"}}, + }, + { + in: `{"addresses":{"http":"unix:///var/foo/bar"}}`, + c: &Config{Addresses: AddressConfig{HTTP: "unix:///var/foo/bar"}}, + }, + { + in: `{"addresses":{"http":"{{\"1.2.3.4\"}}"}}`, + c: &Config{Addresses: AddressConfig{HTTP: "1.2.3.4"}}, + }, + { + in: `{"addresses":{"https":"1.2.3.4"}}`, + c: &Config{Addresses: AddressConfig{HTTPS: "1.2.3.4"}}, + }, + { + in: `{"addresses":{"https":"unix:///var/foo/bar"}}`, + c: &Config{Addresses: AddressConfig{HTTPS: "unix:///var/foo/bar"}}, + }, + { + in: `{"addresses":{"https":"{{\"1.2.3.4\"}}"}}`, + c: &Config{Addresses: AddressConfig{HTTPS: "1.2.3.4"}}, }, { in: `{"addresses":{"rpc":"a"}}`, c: &Config{Addresses: AddressConfig{RPC: "a"}}, }, { - in: `{"advertise_addr":"a"}`, - c: &Config{AdvertiseAddr: "a"}, + in: `{"advertise_addr":"1.2.3.4"}`, + c: &Config{AdvertiseAddr: "1.2.3.4"}, }, { - in: `{"advertise_addr_wan":"a"}`, - c: &Config{AdvertiseAddrWan: "a"}, + in: `{"advertise_addr":"{{\"1.2.3.4\"}}"}`, + c: &Config{AdvertiseAddr: "1.2.3.4"}, + }, + { + in: `{"advertise_addr_wan":"1.2.3.4"}`, + c: &Config{AdvertiseAddrWan: "1.2.3.4"}, + }, + { + in: `{"advertise_addr_wan":"{{\"1.2.3.4\"}}"}`, + c: &Config{AdvertiseAddrWan: "1.2.3.4"}, }, { in: `{"advertise_addrs":{"rpc":"1.2.3.4:5678"}}`, @@ -202,8 +246,12 @@ func TestDecodeConfig(t *testing.T) { c: &Config{Autopilot: Autopilot{CleanupDeadServers: Bool(true)}}, }, { - in: `{"bind_addr":"a"}`, - c: &Config{BindAddr: "a"}, + in: `{"bind_addr":"1.2.3.4"}`, + c: &Config{BindAddr: "1.2.3.4"}, + }, + { + in: `{"bind_addr":"{{\"1.2.3.4\"}}"}`, + c: &Config{BindAddr: "1.2.3.4"}, }, { in: `{"bootstrap":true}`, @@ -230,8 +278,12 @@ func TestDecodeConfig(t *testing.T) { c: &Config{CertFile: "a"}, }, { - in: `{"client_addr":"a"}`, - c: &Config{ClientAddr: "a"}, + in: `{"client_addr":"1.2.3.4"}`, + c: &Config{ClientAddr: "1.2.3.4"}, + }, + { + in: `{"client_addr":"{{\"1.2.3.4\"}}"}`, + c: &Config{ClientAddr: "1.2.3.4"}, }, { in: `{"data_dir":"a"}`, @@ -531,12 +583,30 @@ func TestDecodeConfig(t *testing.T) { c: &Config{RetryMaxAttemptsWan: 123}, }, { - in: `{"serf_lan_bind":"a"}`, - c: &Config{SerfLanBindAddr: "a"}, + in: `{"serf_lan_bind":"1.2.3.4"}`, + c: &Config{SerfLanBindAddr: "1.2.3.4"}, }, { - in: `{"serf_wan_bind":"a"}`, - c: &Config{SerfWanBindAddr: "a"}, + in: `{"serf_lan_bind":"unix:///var/foo/bar"}`, + c: &Config{SerfLanBindAddr: "unix:///var/foo/bar"}, + parseTemplateErr: errors.New("Failed to parse Serf LAN address: unix:///var/foo/bar"), + }, + { + in: `{"serf_lan_bind":"{{\"1.2.3.4\"}}"}`, + c: &Config{SerfLanBindAddr: "1.2.3.4"}, + }, + { + in: `{"serf_wan_bind":"1.2.3.4"}`, + c: &Config{SerfWanBindAddr: "1.2.3.4"}, + }, + { + in: `{"serf_wan_bind":"unix:///var/foo/bar"}`, + c: &Config{SerfWanBindAddr: "unix:///var/foo/bar"}, + parseTemplateErr: errors.New("Failed to parse Serf WAN address: unix:///var/foo/bar"), + }, + { + in: `{"serf_wan_bind":"{{\"1.2.3.4\"}}"}`, + c: &Config{SerfWanBindAddr: "1.2.3.4"}, }, { in: `{"server":true}`, @@ -1165,6 +1235,10 @@ func TestDecodeConfig(t *testing.T) { if got, want := err, tt.err; !reflect.DeepEqual(got, want) { t.Fatalf("got error %v want %v", got, want) } + err = c.ResolveTmplAddrs() + if got, want := err, tt.parseTemplateErr; !reflect.DeepEqual(got, want) { + t.Fatalf("got error %v on ResolveTmplAddrs, expected %v", err, want) + } got, want := c, tt.c verify.Values(t, "", got, want) }) diff --git a/agent/testagent.go b/agent/testagent.go index cf5d98c4db..8269d7d335 100644 --- a/agent/testagent.go +++ b/agent/testagent.go @@ -334,6 +334,7 @@ func TestConfig() *Config { ccfg.CoordinateUpdatePeriod = 100 * time.Millisecond ccfg.ServerHealthInterval = 10 * time.Millisecond + cfg.SetupTaggedAndAdvertiseAddrs() return cfg } diff --git a/command/agent.go b/command/agent.go index 0df8848b18..bea22a6479 100644 --- a/command/agent.go +++ b/command/agent.go @@ -462,6 +462,16 @@ func (cmd *AgentCommand) readConfig() *agent.Config { cfg.Version = cmd.Version cfg.VersionPrerelease = cmd.VersionPrerelease + if err := cfg.ResolveTmplAddrs(); err != nil { + cmd.UI.Error(fmt.Sprintf("Failed to parse config: %v", err)) + return nil + } + + if err := cfg.SetupTaggedAndAdvertiseAddrs(); err != nil { + cmd.UI.Error(fmt.Sprintf("Failed to set up tagged and advertise addresses: %v", err)) + return nil + } + return cfg } diff --git a/command/agent_test.go b/command/agent_test.go index 9f22f01646..b8a98508e4 100644 --- a/command/agent_test.go +++ b/command/agent_test.go @@ -176,6 +176,7 @@ func TestReadCliConfig(t *testing.T) { args: []string{ "-data-dir", tmpDir, "-node", `"a"`, + "-bind", "1.2.3.4", "-advertise-wan", "1.2.3.4", "-serf-wan-bind", "4.3.2.1", "-serf-lan-bind", "4.3.2.2", @@ -207,6 +208,7 @@ func TestReadCliConfig(t *testing.T) { "-data-dir", tmpDir, "-node-meta", "somekey:somevalue", "-node-meta", "otherkey:othervalue", + "-bind", "1.2.3.4", }, ShutdownCh: shutdownCh, BaseCommand: baseCommand(cli.NewMockUi()), @@ -229,6 +231,7 @@ func TestReadCliConfig(t *testing.T) { "-node", `"server1"`, "-server", "-data-dir", tmpDir, + "-bind", "1.2.3.4", }, ShutdownCh: shutdownCh, BaseCommand: baseCommand(ui), @@ -256,6 +259,7 @@ func TestReadCliConfig(t *testing.T) { args: []string{ "-data-dir", tmpDir, "-node", `"client"`, + "-bind", "1.2.3.4", }, ShutdownCh: shutdownCh, BaseCommand: baseCommand(ui), @@ -301,6 +305,7 @@ func TestAgent_HostBasedIDs(t *testing.T) { cmd := &AgentCommand{ args: []string{ "-data-dir", tmpDir, + "-bind", "127.0.0.1", }, BaseCommand: baseCommand(cli.NewMockUi()), } @@ -317,6 +322,7 @@ func TestAgent_HostBasedIDs(t *testing.T) { args: []string{ "-data-dir", tmpDir, "-disable-host-node-id=false", + "-bind", "127.0.0.1", }, BaseCommand: baseCommand(cli.NewMockUi()), }