diff --git a/.changelog/17334.txt b/.changelog/17334.txt new file mode 100644 index 000000000..f09ca8ad3 --- /dev/null +++ b/.changelog/17334.txt @@ -0,0 +1,3 @@ +```release-note:improvement +checks: Added support for Consul check field tls_server_name +``` diff --git a/api/services.go b/api/services.go index 450236547..95f027810 100644 --- a/api/services.go +++ b/api/services.go @@ -212,6 +212,7 @@ type ServiceCheck struct { Interval time.Duration `hcl:"interval,optional"` Timeout time.Duration `hcl:"timeout,optional"` InitialStatus string `mapstructure:"initial_status" hcl:"initial_status,optional"` + TLSServerName string `mapstructure:"tls_server_name" hcl:"tls_server_name,optional"` TLSSkipVerify bool `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"` Header map[string][]string `hcl:"header,block"` Method string `hcl:"method,optional"` diff --git a/command/agent/consul/service_client.go b/command/agent/consul/service_client.go index be2d4b4c6..fa40a3061 100644 --- a/command/agent/consul/service_client.go +++ b/command/agent/consul/service_client.go @@ -1164,6 +1164,7 @@ func apiCheckRegistrationToCheck(r *api.AgentCheckRegistration) *api.AgentServic Body: r.Body, TCP: r.TCP, Status: r.Status, + TLSServerName: r.TLSServerName, TLSSkipVerify: r.TLSSkipVerify, GRPC: r.GRPC, GRPCUseTLS: r.GRPCUseTLS, @@ -1653,6 +1654,7 @@ func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host if check.TLSSkipVerify { chkReg.TLSSkipVerify = true } + chkReg.TLSServerName = check.TLSServerName base := url.URL{ Scheme: proto, Host: net.JoinHostPort(host, strconv.Itoa(port)), @@ -1681,6 +1683,7 @@ func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host if check.TLSSkipVerify { chkReg.TLSSkipVerify = true } + chkReg.TLSServerName = check.TLSServerName default: return nil, fmt.Errorf("check type %+q not valid", check.Type) diff --git a/command/agent/consul/unit_test.go b/command/agent/consul/unit_test.go index 09a239281..517cd789b 100644 --- a/command/agent/consul/unit_test.go +++ b/command/agent/consul/unit_test.go @@ -972,6 +972,7 @@ func TestCreateCheckReg_GRPC(t *testing.T) { PortLabel: "label", GRPCService: "foo.Bar", GRPCUseTLS: true, + TLSServerName: "localhost", TLSSkipVerify: true, Timeout: time.Second, Interval: time.Minute, @@ -988,13 +989,14 @@ func TestCreateCheckReg_GRPC(t *testing.T) { AgentServiceCheck: api.AgentServiceCheck{ Timeout: "1s", Interval: "1m0s", - GRPC: "localhost:8080/foo.Bar", + GRPC: "127.0.0.1:8080/foo.Bar", GRPCUseTLS: true, + TLSServerName: "localhost", TLSSkipVerify: true, }, } - actual, err := createCheckReg(serviceID, checkID, check, "localhost", 8080, "default") + actual, err := createCheckReg(serviceID, checkID, check, "127.0.0.1", 8080, "default") must.NoError(t, err) must.Eq(t, expected, actual) } diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index 5cef618ca..eeb114a40 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -1484,6 +1484,7 @@ func ApiServicesToStructs(in []*api.Service, group bool) []*structs.Service { Interval: check.Interval, Timeout: check.Timeout, InitialStatus: check.InitialStatus, + TLSServerName: check.TLSServerName, TLSSkipVerify: check.TLSSkipVerify, Header: check.Header, Method: check.Method, diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 2807f76ea..a93fa98a5 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -3085,6 +3085,12 @@ func TestTaskGroupDiff(t *testing.T) { Old: "3", New: "5", }, + { + Type: DiffTypeNone, + Name: "TLSServerName", + Old: "", + New: "", + }, { Type: DiffTypeNone, Name: "TLSSkipVerify", @@ -6625,6 +6631,12 @@ func TestTaskDiff(t *testing.T) { Old: "4", New: "4", }, + { + Type: DiffTypeNone, + Name: "TLSServerName", + Old: "", + New: "", + }, { Type: DiffTypeNone, Name: "TLSSkipVerify", diff --git a/nomad/structs/services.go b/nomad/structs/services.go index 275c00911..d3ff1ce77 100644 --- a/nomad/structs/services.go +++ b/nomad/structs/services.go @@ -68,7 +68,8 @@ 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 + TLSServerName string // ServerName to use for SNI and TLS verification when (Type=https and Protocol=https) or (Type=grpc and GRPCUseTLS=true) + TLSSkipVerify bool // Skip TLS verification when (type=https and Protocol=https) or (type=grpc and grpc_use_tls=true) Method string // HTTP Method to use (GET by default) Header map[string][]string // HTTP Headers for Consul to set when making HTTP checks CheckRestart *CheckRestart // If and when a task should be restarted based on checks @@ -183,6 +184,10 @@ func (sc *ServiceCheck) Equal(o *ServiceCheck) bool { return false } + if sc.TLSServerName != o.TLSServerName { + return false + } + if sc.Timeout != o.Timeout { return false } @@ -378,6 +383,11 @@ func (sc *ServiceCheck) validateNomad() error { return fmt.Errorf("failures_before_critical may only be set for Consul service checks") } + // tls_server_name is consul only + if sc.TLSServerName != "" { + return fmt.Errorf("tls_server_name may only be set for Consul service checks") + } + return nil } @@ -465,6 +475,9 @@ func (sc *ServiceCheck) Hash(serviceID string) string { // use name "true" to maintain ID stability hashBool(h, sc.TLSSkipVerify, "true") + // Only include TLSServerName if set to maintain ID stability with Nomad <1.6.0 + hashStringIfNonEmpty(h, sc.TLSServerName) + // maintain artisanal map hashing to maintain ID stability hashHeader(h, sc.Header) diff --git a/nomad/structs/services_test.go b/nomad/structs/services_test.go index 6b5470b51..0368f307a 100644 --- a/nomad/structs/services_test.go +++ b/nomad/structs/services_test.go @@ -353,6 +353,17 @@ func TestServiceCheck_validateNomad(t *testing.T) { Body: "this is a request payload!", }, }, + { + name: "http with tls_server_name", + sc: &ServiceCheck{ + Type: ServiceCheckHTTP, + Interval: 3 * time.Second, + Timeout: 1 * time.Second, + Path: "/health", + TLSServerName: "foo", + }, + exp: `tls_server_name may only be set for Consul service checks`, + }, } for _, testCase := range testCases { diff --git a/ui/stories/components/diff-viewer.stories.js b/ui/stories/components/diff-viewer.stories.js index 1c8bc20d6..7e6c3b2c5 100644 --- a/ui/stories/components/diff-viewer.stories.js +++ b/ui/stories/components/diff-viewer.stories.js @@ -434,6 +434,13 @@ export let DiffViewerWithManyChanges = () => { Old: '', Type: 'None', }, + { + Annotations: null, + Name: 'TLSServerName', + New: '', + Old: '', + Type: 'None', + }, { Annotations: null, Name: 'TLSSkipVerify', diff --git a/website/content/docs/job-specification/check.mdx b/website/content/docs/job-specification/check.mdx index 9490d3fbb..db13448b4 100644 --- a/website/content/docs/job-specification/check.mdx +++ b/website/content/docs/job-specification/check.mdx @@ -81,6 +81,8 @@ job "example" { - `grpc_use_tls` `(bool: false)` - Use TLS to perform a gRPC health check. May be used with `tls_skip_verify` to use TLS but skip certificate verification. + May be used with `tls_server_name` to specify the ServerName to use for SNI + and validation of the certificate presented by the server being checked. - `initial_status` `(string: )` - Specifies the starting status of the service. Valid options are `passing`, `warning`, and `critical`. Omitting @@ -157,8 +159,27 @@ job "example" { Nomad. For Consul service checks, valid options are `grpc`, `http`, `script`, and `tcp`. For Nomad service checks, valid options are `http` and `tcp`. -- `tls_skip_verify` `(bool: false)` - Skip verifying TLS certificates for HTTPS - checks. Only supported in the Consul service provider. +- `tls_server_name` `(string: "")` - Indicates the ServerName to use for SNI and + validation of the certificate presented by the server being checked, when + performing TLS enabled checks (`https` and `grpc` with `grpc_use_tls`). If left + unspecified, the ServerName will be inferred from the address of the server + being checked unless the address is an IP address. There are two common cases + where this is beneficial: + + - When the check address contains an IP, `tls_server_name` can be specified + for SNI. Note: setting `tls_server_name` will also override the hostname + used to verify the certificate presented by the server being checked. + + - When the check address contains a hostname which isn't be present in the + SAN (Subject Alternative Name) field of the certificate presented by the + server being checked. Note: setting `tls_server_name` will also override + the hostname used for SNI. + + This field is only supported in the Consul service provider. + +- `tls_skip_verify` `(bool: false)` - Skip verification of certificates for + `https` and `grpc` with `grpc_use_tls` checks . Only supported in the Consul + service provider. - `on_update` `(string: "require_healthy")` - Specifies how checks should be evaluated when determining deployment health (including a job's initial