From 346838381b2b3425771c8428e7ebfdcaf0ff0cd7 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 18 Apr 2017 21:28:25 -0700 Subject: [PATCH] Only register HTTPS agent check when Consul>=0.7.2 Support for TLSSkipVerify in other checks coming soon! --- command/agent/agent.go | 97 +++++++++++++++++++++++++++--- command/agent/agent_test.go | 105 +++++++++++++++++++++++++++++++++ command/agent/consul/client.go | 3 + nomad/structs/structs.go | 9 +++ 4 files changed, 205 insertions(+), 9 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 8472a98f1..f20026eb7 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "strings" "sync" "sync/atomic" @@ -54,6 +55,10 @@ type Agent struct { // consulCatalog is the subset of Consul's Catalog API Nomad uses. consulCatalog consul.CatalogAPI + // consulSupportsTLSSkipVerify flags whether or not Nomad can register + // checks with TLSSkipVerify + consulSupportsTLSSkipVerify bool + client *client.Client server *nomad.Server @@ -374,6 +379,16 @@ func (a *Agent) setupServer() error { }, }, } + if conf.TLSConfig.EnableHTTP { + if a.consulSupportsTLSSkipVerify { + httpServ.Checks[0].Protocol = "https" + httpServ.Checks[0].TLSSkipVerify = true + } else { + // No TLSSkipVerify support, don't register https check + a.logger.Printf("[WARN] agent: not registering Nomad HTTPS Health Check because it requires Consul>=0.7.2") + httpServ.Checks = []*structs.ServiceCheck{} + } + } rpcServ := &structs.Service{ Name: a.config.Consul.ServerServiceName, PortLabel: a.config.AdvertiseAddrs.RPC, @@ -404,13 +419,10 @@ func (a *Agent) setupServer() error { } // Add the http port check if TLS isn't enabled - // TODO Add TLS check when Consul 0.7.1 comes out. consulServices := []*structs.Service{ rpcServ, serfServ, - } - if !conf.TLSConfig.EnableHTTP { - consulServices = append(consulServices, httpServ) + httpServ, } if err := a.consulService.RegisterAgent(consulRoleServer, consulServices); err != nil { return err @@ -477,8 +489,6 @@ func (a *Agent) setupClient() error { } // Create the Nomad Client services for Consul - // TODO think how we can re-introduce HTTP/S checks when Consul 0.7.1 comes - // out if *a.config.Consul.AutoAdvertise { httpServ := &structs.Service{ Name: a.config.Consul.ClientServiceName, @@ -496,11 +506,19 @@ func (a *Agent) setupClient() error { }, }, } - if !conf.TLSConfig.EnableHTTP { - if err := a.consulService.RegisterAgent(consulRoleClient, []*structs.Service{httpServ}); err != nil { - return err + if conf.TLSConfig.EnableHTTP { + if a.consulSupportsTLSSkipVerify { + httpServ.Checks[0].Protocol = "https" + httpServ.Checks[0].TLSSkipVerify = true + } else { + // No TLSSkipVerify support, don't register https check + a.logger.Printf("[WARN] agent: not registering Nomad HTTPS Health Check because it requires Consul>=0.7.2") + httpServ.Checks = []*structs.ServiceCheck{} } } + if err := a.consulService.RegisterAgent(consulRoleClient, []*structs.Service{httpServ}); err != nil { + return err + } } return nil @@ -672,6 +690,11 @@ func (a *Agent) setupConsul(consulConfig *config.ConsulConfig) error { return err } + // Determine version for TLSSkipVerify + if self, err := client.Agent().Self(); err != nil { + a.consulSupportsTLSSkipVerify = consulSupportsTLSSkipVerify(self) + } + // Create Consul Catalog client for service discovery. a.consulCatalog = client.Catalog() @@ -680,3 +703,59 @@ func (a *Agent) setupConsul(consulConfig *config.ConsulConfig) error { go a.consulService.Run() return nil } + +// consulSupportsTLSSkipVerify returns true if Consul supports TLSSkipVerify. +func consulSupportsTLSSkipVerify(self map[string]map[string]interface{}) bool { + member, ok := self["Member"] + if !ok { + return false + } + tagsI, ok := member["Tags"] + if !ok { + return false + } + tags, ok := tagsI.(map[string]interface{}) + if !ok { + return false + } + buildI, ok := tags["build"] + if !ok { + return false + } + build, ok := buildI.(string) + if !ok { + return false + } + parts := strings.SplitN(build, ":", 2) + if len(parts) == 0 { + return false + } + parts = strings.Split(parts[0], ".") + if len(parts) != 3 { + return false + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return false + } + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return false + } + patch, err := strconv.Atoi(parts[2]) + if err != nil { + return false + } + if major > 0 || minor > 7 { + // After 0.7.2! + return true + } + if minor < 7 { + return false + } + if patch < 2 { + return false + } + // 0.7.2 or higher! + return true +} diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 89db490d7..24d45ee8d 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -1,6 +1,7 @@ package agent import ( + "encoding/json" "fmt" "io/ioutil" "net" @@ -358,3 +359,107 @@ func TestAgent_ClientConfig(t *testing.T) { t.Fatalf("Expected http addr: %v, got: %v", expectedHttpAddr, c.Node.HTTPAddr) } } + +func TestAgent_ConsulSupportsTLSSkipVerify(t *testing.T) { + assertSupport := func(expected bool, blob string) { + self := map[string]map[string]interface{}{} + if err := json.Unmarshal([]byte("{"+blob+"}"), &self); err != nil { + t.Fatalf("invalid json: %v", err) + } + actual := consulSupportsTLSSkipVerify(self) + if actual != expected { + t.Errorf("expected %t but got %t for:\n%s\n", expected, actual, blob) + } + } + + // 0.6.4 + assertSupport(false, `"Member": { + "Addr": "127.0.0.1", + "DelegateCur": 4, + "DelegateMax": 4, + "DelegateMin": 2, + "Name": "rusty", + "Port": 8301, + "ProtocolCur": 2, + "ProtocolMax": 3, + "ProtocolMin": 1, + "Status": 1, + "Tags": { + "build": "0.6.4:26a0ef8c", + "dc": "dc1", + "port": "8300", + "role": "consul", + "vsn": "2", + "vsn_max": "3", + "vsn_min": "1" + }}`) + + // 0.7.0 + assertSupport(false, `"Member": { + "Addr": "127.0.0.1", + "DelegateCur": 4, + "DelegateMax": 4, + "DelegateMin": 2, + "Name": "rusty", + "Port": 8301, + "ProtocolCur": 2, + "ProtocolMax": 4, + "ProtocolMin": 1, + "Status": 1, + "Tags": { + "build": "0.7.0:'a189091", + "dc": "dc1", + "port": "8300", + "role": "consul", + "vsn": "2", + "vsn_max": "3", + "vsn_min": "2" + }}`) + + // 0.7.2 + assertSupport(true, `"Member": { + "Addr": "127.0.0.1", + "DelegateCur": 4, + "DelegateMax": 4, + "DelegateMin": 2, + "Name": "rusty", + "Port": 8301, + "ProtocolCur": 2, + "ProtocolMax": 5, + "ProtocolMin": 1, + "Status": 1, + "Tags": { + "build": "0.7.2:'a9afa0c", + "dc": "dc1", + "port": "8300", + "role": "consul", + "vsn": "2", + "vsn_max": "3", + "vsn_min": "2" + }}`) + + // 0.8.1 + assertSupport(true, `"Member": { + "Addr": "127.0.0.1", + "DelegateCur": 4, + "DelegateMax": 5, + "DelegateMin": 2, + "Name": "rusty", + "Port": 8301, + "ProtocolCur": 2, + "ProtocolMax": 5, + "ProtocolMin": 1, + "Status": 1, + "Tags": { + "build": "0.8.1:'e9ca44d", + "dc": "dc1", + "id": "3ddc1b59-460e-a100-1d5c-ce3972122664", + "port": "8300", + "raft_vsn": "2", + "role": "consul", + "vsn": "2", + "vsn_max": "3", + "vsn_min": "2", + "wan_join_port": "8302" + }}`) +} diff --git a/command/agent/consul/client.go b/command/agent/consul/client.go index 7f0a9a732..246d4f478 100644 --- a/command/agent/consul/client.go +++ b/command/agent/consul/client.go @@ -666,6 +666,9 @@ func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host if check.Protocol == "" { check.Protocol = "http" } + if check.TLSSkipVerify { + chkReg.TLSSkipVerify = true + } base := url.URL{ Scheme: check.Protocol, Host: net.JoinHostPort(host, strconv.Itoa(port)), diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index f04a525f0..49196b41b 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -2116,6 +2116,7 @@ type ServiceCheck struct { Interval time.Duration // Interval of the check Timeout time.Duration // Timeout of the response from the check before consul fails the check InitialStatus string // Initial status of the check + TLSSkipVerify bool // Skip TLS verification when Protocol=https } func (sc *ServiceCheck) Copy() *ServiceCheck { @@ -2199,6 +2200,10 @@ func (sc *ServiceCheck) RequiresPort() bool { } } +// Hash all ServiceCheck fields and the check's corresponding service ID to +// create an identifier. The identifier is not guaranteed to be unique as if +// the PortLabel is blank, the Service's PortLabel will be used after Hash is +// called. func (sc *ServiceCheck) Hash(serviceID string) string { h := sha1.New() io.WriteString(h, serviceID) @@ -2211,6 +2216,10 @@ func (sc *ServiceCheck) Hash(serviceID string) string { io.WriteString(h, sc.PortLabel) io.WriteString(h, sc.Interval.String()) io.WriteString(h, sc.Timeout.String()) + // Only include TLSSkipVerify if set to maintain ID stability with Nomad <0.6 + if sc.TLSSkipVerify { + io.WriteString(h, "true") + } return fmt.Sprintf("%x", h.Sum(nil)) }