jobspec: update gateway.ingress.service Consul API fields (#20176)

Add support for further configuring `gateway.ingress.service` blocks to bring
this block up-to-date with currently available Consul API fields (except for
namespace and admin partition, which will need be handled under a different
PR). These fields are sent to Consul as part of the job endpoint submission hook
for Connect gateways.

Co-authored-by: Horacio Monsalvo <horacio.monsalvo@southworks.com>
This commit is contained in:
Tim Gross
2024-03-22 13:50:48 -04:00
committed by GitHub
parent 2556ff9a0e
commit 10dd738a03
13 changed files with 930 additions and 101 deletions

3
.changelog/16753.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
consul/connect: Added support for TLS configuration, headers configuration, and request limit configuration to ingress service block
```

View File

@@ -4,6 +4,7 @@
package api
import (
"slices"
"time"
"golang.org/x/exp/maps"
@@ -400,12 +401,52 @@ func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy {
}
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway.
// ConsulGatewayTLSSDSConfig is used to configure the gateway's TLS listener to
// load certificates from an external Secret Discovery Service (SDS)
type ConsulGatewayTLSSDSConfig struct {
// ClusterName specifies the name of the SDS cluster where Consul should
// retrieve certificates.
ClusterName string `hcl:"cluster_name,optional" mapstructure:"cluster_name"`
// CertResource specifies an SDS resource name
CertResource string `hcl:"cert_resource,optional" mapstructure:"cert_resource"`
}
func (c *ConsulGatewayTLSSDSConfig) Copy() *ConsulGatewayTLSSDSConfig {
if c == nil {
return nil
}
return &ConsulGatewayTLSSDSConfig{
ClusterName: c.ClusterName,
CertResource: c.CertResource,
}
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway. Both
// ConsulIngressConfigEntry and ConsulIngressService use this struct. For more
// details, consult the Consul documentation:
// https://developer.hashicorp.com/consul/docs/connect/config-entries/ingress-gateway#listeners-services-tls
type ConsulGatewayTLSConfig struct {
// Enabled indicates whether TLS is enabled for the configuration entry
Enabled bool `hcl:"enabled,optional"`
// TLSMinVersion specifies the minimum TLS version supported for gateway
// listeners.
TLSMinVersion string `hcl:"tls_min_version,optional" mapstructure:"tls_min_version"`
// TLSMaxVersion specifies the maxmimum TLS version supported for gateway
// listeners.
TLSMaxVersion string `hcl:"tls_max_version,optional" mapstructure:"tls_max_version"`
// CipherSuites specifies a list of cipher suites that gateway listeners
// support when negotiating connections using TLS 1.2 or older.
CipherSuites []string `hcl:"cipher_suites,optional" mapstructure:"cipher_suites"`
// SDS specifies parameters that configure the listener to load TLS
// certificates from an external Secrets Discovery Service (SDS).
SDS *ConsulGatewayTLSSDSConfig `hcl:"sds,block" mapstructure:"sds"`
}
func (tc *ConsulGatewayTLSConfig) Canonicalize() {
@@ -420,6 +461,7 @@ func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
Enabled: tc.Enabled,
TLSMinVersion: tc.TLSMinVersion,
TLSMaxVersion: tc.TLSMaxVersion,
SDS: tc.SDS.Copy(),
}
if len(tc.CipherSuites) != 0 {
cipherSuites := make([]string, len(tc.CipherSuites))
@@ -430,13 +472,90 @@ func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
return result
}
// ConsulIngressService is used to configure a service fronted by the ingress gateway.
// ConsulHTTPHeaderModifiers is a set of rules for HTTP header modification that
// should be performed by proxies as the request passes through them. It can
// operate on either request or response headers depending on the context in
// which it is used.
type ConsulHTTPHeaderModifiers struct {
// Add is a set of name -> value pairs that should be appended to the
// request or response (i.e. allowing duplicates if the same header already
// exists).
Add map[string]string `hcl:"add,block" mapstructure:"add"`
// Set is a set of name -> value pairs that should be added to the request
// or response, overwriting any existing header values of the same name.
Set map[string]string `hcl:"set,block" mapstructure:"set"`
// Remove is the set of header names that should be stripped from the
// request or response.
Remove []string `hcl:"remove,optional" mapstructure:"remove"`
}
func (h *ConsulHTTPHeaderModifiers) Copy() *ConsulHTTPHeaderModifiers {
if h == nil {
return nil
}
return &ConsulHTTPHeaderModifiers{
Add: maps.Clone(h.Add),
Set: maps.Clone(h.Set),
Remove: slices.Clone(h.Remove),
}
}
func (h *ConsulHTTPHeaderModifiers) Canonicalize() {
if h == nil {
return
}
if len(h.Add) == 0 {
h.Add = nil
}
if len(h.Set) == 0 {
h.Set = nil
}
if len(h.Remove) == 0 {
h.Remove = nil
}
}
// ConsulIngressService is used to configure a service fronted by the ingress
// gateway. For more details, consult the Consul documentation:
// https://developer.hashicorp.com/consul/docs/connect/config-entries/ingress-gateway
type ConsulIngressService struct {
// Namespace is not yet supported.
// Namespace string
// Name of the service exposed through this listener.
Name string `hcl:"name,optional"`
// Hosts specifies one or more hosts that the listening services can receive
// requests on.
Hosts []string `hcl:"hosts,optional"`
// TLS specifies a TLS configuration override for a specific service. If
// unset this will fallback to the ConsulIngressConfigEntry's own TLS field.
TLS *ConsulGatewayTLSConfig `hcl:"tls,block" mapstructure:"tls"`
// RequestHeaders specifies a set of HTTP-specific header modification rules
// applied to requests routed through the gateway
RequestHeaders *ConsulHTTPHeaderModifiers `hcl:"request_headers,block" mapstructure:"request_headers"`
// ResponseHeader specifies a set of HTTP-specific header modification rules
// applied to responses routed through the gateway
ResponseHeaders *ConsulHTTPHeaderModifiers `hcl:"response_headers,block" mapstructure:"response_headers"`
// MaxConnections specifies the maximum number of HTTP/1.1 connections a
// service instance is allowed to establish against the upstream
MaxConnections *uint32 `hcl:"max_connections,optional" mapstructure:"max_connections"`
// MaxPendingRequests specifies the maximum number of requests that are
// allowed to queue while waiting to establish a connection
MaxPendingRequests *uint32 `hcl:"max_pending_requests,optional" mapstructure:"max_pending_requests"`
// MaxConcurrentRequests specifies the maximum number of concurrent HTTP/2
// traffic requests that are allowed at a single point in time
MaxConcurrentRequests *uint32 `hcl:"max_concurrent_requests,optional" mapstructure:"max_concurrent_requests"`
}
func (s *ConsulIngressService) Canonicalize() {
@@ -447,6 +566,9 @@ func (s *ConsulIngressService) Canonicalize() {
if len(s.Hosts) == 0 {
s.Hosts = nil
}
s.RequestHeaders.Canonicalize()
s.ResponseHeaders.Canonicalize()
}
func (s *ConsulIngressService) Copy() *ConsulIngressService {
@@ -454,16 +576,19 @@ func (s *ConsulIngressService) Copy() *ConsulIngressService {
return nil
}
var hosts []string = nil
if n := len(s.Hosts); n > 0 {
hosts = make([]string, n)
copy(hosts, s.Hosts)
}
ns := new(ConsulIngressService)
*ns = *s
return &ConsulIngressService{
Name: s.Name,
Hosts: hosts,
}
ns.Hosts = slices.Clone(s.Hosts)
ns.RequestHeaders = s.RequestHeaders.Copy()
ns.ResponseHeaders = s.ResponseHeaders.Copy()
ns.TLS = s.TLS.Copy()
ns.MaxConnections = pointerCopy(s.MaxConnections)
ns.MaxPendingRequests = pointerCopy(s.MaxPendingRequests)
ns.MaxConcurrentRequests = pointerCopy(s.MaxConcurrentRequests)
return ns
}
const (
@@ -521,7 +646,11 @@ type ConsulIngressConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
// TLS specifies a TLS configuration for the gateway.
TLS *ConsulGatewayTLSConfig `hcl:"tls,block"`
// Listeners specifies a list of listeners in the mesh for the
// gateway. Listeners are uniquely identified by their port number.
Listeners []*ConsulIngressListener `hcl:"listener,block"`
}

View File

@@ -415,6 +415,33 @@ func TestConsulIngressConfigEntry_Copy(t *testing.T) {
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1", "1.1.1.1:9000"},
TLS: &ConsulGatewayTLSConfig{
SDS: &ConsulGatewayTLSSDSConfig{
ClusterName: "foo",
CertResource: "bar",
},
},
RequestHeaders: &ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
Set: map[string]string{
"test1": "testvalue1",
},
Remove: []string{"test2"},
},
ResponseHeaders: &ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
Set: map[string]string{
"test1": "testvalue1",
},
Remove: []string{"test2"},
},
MaxConnections: pointerOf(uint32(5120)),
MaxPendingRequests: pointerOf(uint32(512)),
MaxConcurrentRequests: pointerOf(uint32(2048)),
}, {
Name: "service2",
Hosts: []string{"2.2.2.2"},

View File

@@ -33,3 +33,12 @@ func formatFloat(f float64, maxPrec int) string {
func pointerOf[A any](a A) *A {
return &a
}
// pointerCopy returns a new pointer to a.
func pointerCopy[A any](a *A) *A {
if a == nil {
return nil
}
na := *a
return &na
}

View File

@@ -1745,6 +1745,18 @@ func apiConnectGatewayTLSConfig(in *api.ConsulGatewayTLSConfig) *structs.ConsulG
TLSMinVersion: in.TLSMinVersion,
TLSMaxVersion: in.TLSMaxVersion,
CipherSuites: slices.Clone(in.CipherSuites),
SDS: apiConnectGatewayTLSSDSConfig(in.SDS),
}
}
func apiConnectGatewayTLSSDSConfig(in *api.ConsulGatewayTLSSDSConfig) *structs.ConsulGatewayTLSSDSConfig {
if in == nil {
return nil
}
return &structs.ConsulGatewayTLSSDSConfig{
ClusterName: in.ClusterName,
CertResource: in.CertResource,
}
}
@@ -1792,6 +1804,24 @@ func apiConnectIngressServiceToStructs(in *api.ConsulIngressService) *structs.Co
return &structs.ConsulIngressService{
Name: in.Name,
Hosts: slices.Clone(in.Hosts),
TLS: apiConnectGatewayTLSConfig(in.TLS),
RequestHeaders: apiConsulHTTPHeaderModifiersToStructs(in.RequestHeaders),
ResponseHeaders: apiConsulHTTPHeaderModifiersToStructs(in.ResponseHeaders),
MaxConnections: in.MaxConnections,
MaxPendingRequests: in.MaxPendingRequests,
MaxConcurrentRequests: in.MaxConcurrentRequests,
}
}
func apiConsulHTTPHeaderModifiersToStructs(in *api.ConsulHTTPHeaderModifiers) *structs.ConsulHTTPHeaderModifiers {
if in == nil {
return nil
}
return &structs.ConsulHTTPHeaderModifiers{
Add: maps.Clone(in.Add),
Set: maps.Clone(in.Set),
Remove: slices.Clone(in.Remove),
}
}

View File

@@ -4182,6 +4182,33 @@ func TestConversion_ApiConsulConnectToStructs(t *testing.T) {
Services: []*structs.ConsulIngressService{{
Name: "ingress1",
Hosts: []string{"host1"},
TLS: &structs.ConsulGatewayTLSConfig{
SDS: &structs.ConsulGatewayTLSSDSConfig{
ClusterName: "foo",
CertResource: "bar",
},
},
RequestHeaders: &structs.ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
Set: map[string]string{
"test1": "testvalue1",
},
Remove: []string{"test2"},
},
ResponseHeaders: &structs.ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
Set: map[string]string{
"test1": "testvalue1",
},
Remove: []string{"test2"},
},
MaxConnections: pointer.Of(uint32(5120)),
MaxPendingRequests: pointer.Of(uint32(512)),
MaxConcurrentRequests: pointer.Of(uint32(2048)),
}},
}},
},
@@ -4202,6 +4229,33 @@ func TestConversion_ApiConsulConnectToStructs(t *testing.T) {
Services: []*api.ConsulIngressService{{
Name: "ingress1",
Hosts: []string{"host1"},
TLS: &api.ConsulGatewayTLSConfig{
SDS: &api.ConsulGatewayTLSSDSConfig{
ClusterName: "foo",
CertResource: "bar",
},
},
RequestHeaders: &api.ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
Set: map[string]string{
"test1": "testvalue1",
},
Remove: []string{"test2"},
},
ResponseHeaders: &api.ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
Set: map[string]string{
"test1": "testvalue1",
},
Remove: []string{"test2"},
},
MaxConnections: pointer.Of(uint32(5120)),
MaxPendingRequests: pointer.Of(uint32(512)),
MaxConcurrentRequests: pointer.Of(uint32(2048)),
}},
}},
},

View File

@@ -425,10 +425,84 @@ func parseGatewayProxy(o *ast.ObjectItem) (*api.ConsulGatewayProxy, error) {
return &proxy, nil
}
func parseConsulHTTPHeaderModifiers(o *ast.ObjectItem) (*api.ConsulHTTPHeaderModifiers, error) {
valid := []string{
"add",
"set",
"remove",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
return nil, multierror.Prefix(err, "httpHeaderModifiers ->")
}
var httpHeaderModifiers api.ConsulHTTPHeaderModifiers
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return nil, err
}
delete(m, "add")
delete(m, "set")
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &httpHeaderModifiers,
})
if err != nil {
return nil, err
}
if err := dec.Decode(m); err != nil {
return nil, err
}
// Filter list
var listVal *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return nil, fmt.Errorf("'httpHeaderModifiers: should be an object")
}
// Parse Add
if addO := listVal.Filter("add"); len(addO.Items) > 0 {
for _, o := range addO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return nil, err
}
if err := mapstructure.WeakDecode(m, &httpHeaderModifiers.Add); err != nil {
return nil, err
}
}
}
// Parse Set
if setO := listVal.Filter("set"); len(setO.Items) > 0 {
for _, o := range setO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return nil, err
}
if err := mapstructure.WeakDecode(m, &httpHeaderModifiers.Set); err != nil {
return nil, err
}
}
}
return &httpHeaderModifiers, nil
}
func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, error) {
valid := []string{
"name",
"hosts",
"tls",
"request_headers",
"response_headers",
"max_connections",
"max_pending_requests",
"max_concurrent_requests",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
@@ -441,6 +515,10 @@ func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, er
return nil, err
}
delete(m, "tls")
delete(m, "request_headers")
delete(m, "response_headers")
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &service,
})
@@ -452,6 +530,37 @@ func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, er
return nil, err
}
var listVal *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return nil, fmt.Errorf("service: should be an object")
}
// Parse TLS
if tlsO := listVal.Filter("tls"); len(tlsO.Items) > 0 {
service.TLS, err = parseConsulGatewayTLS(tlsO.Items[0])
if err != nil {
return nil, err
}
}
// Parse Request Headers
if rqstHO := listVal.Filter("request_headers"); len(rqstHO.Items) > 0 {
service.RequestHeaders, err = parseConsulHTTPHeaderModifiers(rqstHO.Items[0])
if err != nil {
return nil, err
}
}
// Parse Response Headers
if rspHO := listVal.Filter("response_headers"); len(rspHO.Items) > 0 {
service.ResponseHeaders, err = parseConsulHTTPHeaderModifiers(rspHO.Items[0])
if err != nil {
return nil, err
}
}
return &service, nil
}
@@ -541,12 +650,43 @@ func parseConsulIngressListener(o *ast.ObjectItem) (*api.ConsulIngressListener,
return &listener, nil
}
func parseConsulGatewayTLSSDS(o *ast.ObjectItem) (*api.ConsulGatewayTLSSDSConfig, error) {
valid := []string{
"cluster_name",
"cert_resource",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
return nil, multierror.Prefix(err, "sds ->")
}
var sds api.ConsulGatewayTLSSDSConfig
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return nil, err
}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &sds,
})
if err != nil {
return nil, err
}
if err := dec.Decode(m); err != nil {
return nil, err
}
return &sds, nil
}
func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, error) {
valid := []string{
"enabled",
"tls_min_version",
"tls_max_version",
"cipher_suites",
"sds",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
@@ -559,6 +699,8 @@ func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, erro
return nil, err
}
delete(m, "sds")
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &tls,
})
@@ -570,6 +712,22 @@ func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, erro
return nil, err
}
// Parse SDS
var listVal *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return nil, fmt.Errorf("tls: should be an object")
}
so := listVal.Filter("sds")
if len(so.Items) > 0 {
tls.SDS, err = parseConsulGatewayTLSSDS(so.Items[0])
if err != nil {
return nil, err
}
}
return &tls, nil
}

View File

@@ -12,6 +12,7 @@ import (
capi "github.com/hashicorp/consul/api"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/shoenig/test/must"
)
@@ -1682,6 +1683,23 @@ func TestParse(t *testing.T) {
Hosts: []string{
"2.2.2.2:8080",
},
TLS: &api.ConsulGatewayTLSConfig{
SDS: &api.ConsulGatewayTLSSDSConfig{
ClusterName: "foo",
CertResource: "bar",
},
},
RequestHeaders: &api.ConsulHTTPHeaderModifiers{
Add: map[string]string{
"test": "testvalue",
},
},
ResponseHeaders: &api.ConsulHTTPHeaderModifiers{
Remove: []string{"test2"},
},
MaxConnections: pointer.Of(uint32(5120)),
MaxPendingRequests: pointer.Of(uint32(512)),
MaxConcurrentRequests: pointer.Of(uint32(2048)),
}},
},
},

View File

@@ -50,6 +50,23 @@ job "connect_gateway_ingress" {
service {
name = "nginx"
hosts = ["2.2.2.2:8080"]
tls {
sds {
cluster_name = "foo"
cert_resource = "bar"
}
}
request_headers {
add {
test = "testvalue"
}
}
response_headers {
remove = ["test2"]
}
max_connections = 5120
max_pending_requests = 512
max_concurrent_requests = 2048
}
}
}

View File

@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"maps"
"slices"
"strings"
"sync"
@@ -587,9 +588,21 @@ func convertIngressCE(namespace, service string, entry *structs.ConsulIngressCon
for _, listener := range entry.Listeners {
var services []api.IngressService = nil
for _, s := range listener.Services {
var sds *api.GatewayTLSSDSConfig = nil
if s.TLS != nil {
sds = convertGatewayTLSSDSConfig(s.TLS.SDS)
}
services = append(services, api.IngressService{
Name: s.Name,
Hosts: slices.Clone(s.Hosts),
RequestHeaders: convertHTTPHeaderModifiers(s.RequestHeaders),
ResponseHeaders: convertHTTPHeaderModifiers(s.ResponseHeaders),
MaxConnections: s.MaxConnections,
MaxPendingRequests: s.MaxPendingRequests,
MaxConcurrentRequests: s.MaxConcurrentRequests,
TLS: &api.GatewayServiceTLSConfig{
SDS: sds,
},
})
}
listeners = append(listeners, api.IngressListener{
@@ -611,11 +624,48 @@ func convertIngressCE(namespace, service string, entry *structs.ConsulIngressCon
Namespace: namespace,
Kind: api.IngressGateway,
Name: service,
TLS: tls,
TLS: *convertGatewayTLSConfig(entry.TLS),
Listeners: listeners,
}
}
func convertHTTPHeaderModifiers(in *structs.ConsulHTTPHeaderModifiers) *api.HTTPHeaderModifiers {
if in != nil {
return &api.HTTPHeaderModifiers{
Add: maps.Clone(in.Add),
Set: maps.Clone(in.Set),
Remove: slices.Clone(in.Remove),
}
}
return &api.HTTPHeaderModifiers{}
}
func convertGatewayTLSConfig(in *structs.ConsulGatewayTLSConfig) *api.GatewayTLSConfig {
if in != nil {
return &api.GatewayTLSConfig{
Enabled: in.Enabled,
TLSMinVersion: in.TLSMinVersion,
TLSMaxVersion: in.TLSMaxVersion,
CipherSuites: slices.Clone(in.CipherSuites),
SDS: convertGatewayTLSSDSConfig(in.SDS),
}
}
return &api.GatewayTLSConfig{}
}
func convertGatewayTLSSDSConfig(in *structs.ConsulGatewayTLSSDSConfig) *api.GatewayTLSSDSConfig {
if in != nil {
return &api.GatewayTLSSDSConfig{
ClusterName: in.ClusterName,
CertResource: in.CertResource,
}
}
return &api.GatewayTLSSDSConfig{}
}
func convertTerminatingCE(namespace, service string, entry *structs.ConsulTerminatingConfigEntry) api.ConfigEntry {
var linked []api.LinkedService = nil
for _, s := range entry.Services {

View File

@@ -1314,6 +1314,11 @@ func connectGatewayTLSConfigDiff(prev, next *ConsulGatewayTLSConfig, contextual
// Diff the primitive field.
diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
// Diff SDS object
if sdsDiff := primitiveObjectDiff(prev.SDS, next.SDS, nil, "SDS", contextual); sdsDiff != nil {
diff.Objects = append(diff.Objects, sdsDiff)
}
return diff
}
@@ -1443,6 +1448,38 @@ func connectGatewayIngressServiceDiff(prev, next *ConsulIngressService, contextu
newPrimitiveFlat = flatmap.Flatten(next, nil, true)
}
// Diff pointer types.
if prev != nil {
if prev.MaxConnections != nil {
oldPrimitiveFlat["MaxConnections"] = fmt.Sprintf("%v", *prev.MaxConnections)
}
}
if next != nil {
if next.MaxConnections != nil {
newPrimitiveFlat["MaxConnections"] = fmt.Sprintf("%v", *next.MaxConnections)
}
}
if prev != nil {
if prev.MaxPendingRequests != nil {
oldPrimitiveFlat["MaxPendingRequests"] = fmt.Sprintf("%v", *prev.MaxPendingRequests)
}
}
if next != nil {
if next.MaxPendingRequests != nil {
newPrimitiveFlat["MaxPendingRequests"] = fmt.Sprintf("%v", *next.MaxPendingRequests)
}
}
if prev != nil {
if prev.MaxConcurrentRequests != nil {
oldPrimitiveFlat["MaxConcurrentRequests"] = fmt.Sprintf("%v", *prev.MaxConcurrentRequests)
}
}
if next != nil {
if next.MaxConcurrentRequests != nil {
newPrimitiveFlat["MaxConcurrentRequests"] = fmt.Sprintf("%v", *next.MaxConcurrentRequests)
}
}
// Diff the primitive fields.
diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
@@ -1451,6 +1488,55 @@ func connectGatewayIngressServiceDiff(prev, next *ConsulIngressService, contextu
diff.Objects = append(diff.Objects, hDiffs)
}
// Diff the ConsulGatewayTLSConfig objects.
tlsConfigDiff := connectGatewayTLSConfigDiff(prev.TLS, next.TLS, contextual)
if tlsConfigDiff != nil {
diff.Objects = append(diff.Objects, tlsConfigDiff)
}
// Diff the ConsulHTTPHeaderModifiers objects (RequestHeaders).
reqModifiersDiff := connectGatewayHTTPHeaderModifiersDiff(prev.RequestHeaders, next.RequestHeaders, "RequestHeaders", contextual)
if reqModifiersDiff != nil {
diff.Objects = append(diff.Objects, reqModifiersDiff)
}
// Diff the ConsulHTTPHeaderModifiers objects (ResponseHeaders).
respModifiersDiff := connectGatewayHTTPHeaderModifiersDiff(prev.ResponseHeaders, next.ResponseHeaders, "ResponseHeaders", contextual)
if respModifiersDiff != nil {
diff.Objects = append(diff.Objects, respModifiersDiff)
}
return diff
}
func connectGatewayHTTPHeaderModifiersDiff(prev, next *ConsulHTTPHeaderModifiers, name string, contextual bool) *ObjectDiff {
diff := &ObjectDiff{Type: DiffTypeNone, Name: name}
var oldPrimitiveFlat, newPrimitiveFlat map[string]string
if reflect.DeepEqual(prev, next) {
return nil
} else if prev == nil {
prev = new(ConsulHTTPHeaderModifiers)
diff.Type = DiffTypeAdded
newPrimitiveFlat = flatmap.Flatten(next, nil, true)
} else if next == nil {
next = new(ConsulHTTPHeaderModifiers)
diff.Type = DiffTypeDeleted
oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
} else {
diff.Type = DiffTypeEdited
oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
newPrimitiveFlat = flatmap.Flatten(next, nil, true)
}
// Diff the primitive fields.
diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
// Diff the Remove Headers.
if rDiffs := stringSetDiff(prev.Remove, next.Remove, "Remove", contextual); rDiffs != nil {
diff.Objects = append(diff.Objects, rDiffs)
}
return diff
}

View File

@@ -1994,12 +1994,61 @@ func (p *ConsulGatewayProxy) Validate() error {
return nil
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway.
// ConsulGatewayTLSSDSConfig is used to configure the gateway's TLS listener to
// load certificates from an external Secret Discovery Service (SDS)
type ConsulGatewayTLSSDSConfig struct {
// ClusterName specifies the name of the SDS cluster where Consul should
// retrieve certificates.
ClusterName string
// CertResource specifies an SDS resource name
CertResource string
}
func (c *ConsulGatewayTLSSDSConfig) Copy() *ConsulGatewayTLSSDSConfig {
if c == nil {
return nil
}
return &ConsulGatewayTLSSDSConfig{
ClusterName: c.ClusterName,
CertResource: c.CertResource,
}
}
func (c *ConsulGatewayTLSSDSConfig) Equal(o *ConsulGatewayTLSSDSConfig) bool {
if c == nil || o == nil {
return c == o
}
return c.ClusterName == o.ClusterName &&
c.CertResource == o.CertResource
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway. Both
// ConsulIngressConfigEntry and ConsulIngressService use this struct. For more
// details, consult the Consul documentation:
// https://developer.hashicorp.com/consul/docs/connect/config-entries/ingress-gateway#listeners-services-tls
type ConsulGatewayTLSConfig struct {
// Enabled indicates whether TLS is enabled for the configuration entry
Enabled bool
// TLSMinVersion specifies the minimum TLS version supported for gateway
// listeners.
TLSMinVersion string
// TLSMaxVersion specifies the maxmimum TLS version supported for gateway
// listeners.
TLSMaxVersion string
// CipherSuites specifies a list of cipher suites that gateway listeners
// support when negotiating connections using TLS 1.2 or older.
CipherSuites []string
// SDS specifies parameters that configure the listener to load TLS
// certificates from an external Secrets Discovery Service (SDS).
SDS *ConsulGatewayTLSSDSConfig
}
func (c *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
@@ -2012,6 +2061,7 @@ func (c *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
TLSMinVersion: c.TLSMinVersion,
TLSMaxVersion: c.TLSMaxVersion,
CipherSuites: slices.Clone(c.CipherSuites),
SDS: c.SDS.Copy(),
}
}
@@ -2023,13 +2073,95 @@ func (c *ConsulGatewayTLSConfig) Equal(o *ConsulGatewayTLSConfig) bool {
return c.Enabled == o.Enabled &&
c.TLSMinVersion == o.TLSMinVersion &&
c.TLSMaxVersion == o.TLSMaxVersion &&
helper.SliceSetEq(c.CipherSuites, o.CipherSuites)
helper.SliceSetEq(c.CipherSuites, o.CipherSuites) &&
c.SDS.Equal(o.SDS)
}
// ConsulHTTPHeaderModifiers is a set of rules for HTTP header modification that
// should be performed by proxies as the request passes through them. It can
// operate on either request or response headers depending on the context in
// which it is used.
type ConsulHTTPHeaderModifiers struct {
// Add is a set of name -> value pairs that should be appended to the request
// or response (i.e. allowing duplicates if the same header already exists).
Add map[string]string
// Set is a set of name -> value pairs that should be added to the request or
// response, overwriting any existing header values of the same name.
Set map[string]string
// Remove is the set of header names that should be stripped from the request
// or response.
Remove []string
}
func (h *ConsulHTTPHeaderModifiers) Copy() *ConsulHTTPHeaderModifiers {
if h == nil {
return nil
}
return &ConsulHTTPHeaderModifiers{
Add: maps.Clone(h.Add),
Set: maps.Clone(h.Set),
Remove: slices.Clone(h.Remove),
}
}
func (h *ConsulHTTPHeaderModifiers) Equal(o *ConsulHTTPHeaderModifiers) bool {
if h == nil || o == nil {
return h == o
}
if !maps.Equal(h.Add, o.Add) {
return false
}
if !maps.Equal(h.Set, o.Set) {
return false
}
if !helper.SliceSetEq(h.Remove, o.Remove) {
return false
}
return true
}
// ConsulIngressService is used to configure a service fronted by the ingress gateway.
// For more details, consult the Consul documentation:
// https://developer.hashicorp.com/consul/docs/connect/config-entries/ingress-gateway
type ConsulIngressService struct {
// Name of the service exposed through this listener.
Name string
// Hosts specifies one or more hosts that the listening services can receive
// requests on.
Hosts []string
// TLS specifies a TLS configuration override for a specific service. If
// unset this will fallback to the ConsulIngressConfigEntry's own TLS field.
TLS *ConsulGatewayTLSConfig
// RequestHeaders specifies a set of HTTP-specific header modification rules
// applied to requests routed through the gateway
RequestHeaders *ConsulHTTPHeaderModifiers
// ResponseHeader specifies a set of HTTP-specific header modification rules
// applied to responses routed through the gateway
ResponseHeaders *ConsulHTTPHeaderModifiers
// MaxConnections specifies the maximum number of HTTP/1.1 connections a
// service instance is allowed to establish against the upstream
MaxConnections *uint32
// MaxPendingRequests specifies the maximum number of requests that are
// allowed to queue while waiting to establish a connection
MaxPendingRequests *uint32
// MaxConcurrentRequests specifies the maximum number of concurrent HTTP/2
// traffic requests that are allowed at a single point in time
MaxConcurrentRequests *uint32
}
func (s *ConsulIngressService) Copy() *ConsulIngressService {
@@ -2037,16 +2169,19 @@ func (s *ConsulIngressService) Copy() *ConsulIngressService {
return nil
}
var hosts []string = nil
if n := len(s.Hosts); n > 0 {
hosts = make([]string, n)
copy(hosts, s.Hosts)
}
ns := new(ConsulIngressService)
*ns = *s
return &ConsulIngressService{
Name: s.Name,
Hosts: hosts,
}
ns.Hosts = slices.Clone(s.Hosts)
ns.RequestHeaders = s.RequestHeaders.Copy()
ns.ResponseHeaders = s.ResponseHeaders.Copy()
ns.TLS = s.TLS.Copy()
ns.MaxConnections = pointer.Copy(s.MaxConnections)
ns.MaxPendingRequests = pointer.Copy(s.MaxPendingRequests)
ns.MaxConcurrentRequests = pointer.Copy(s.MaxConcurrentRequests)
return ns
}
func (s *ConsulIngressService) Equal(o *ConsulIngressService) bool {
@@ -2058,7 +2193,35 @@ func (s *ConsulIngressService) Equal(o *ConsulIngressService) bool {
return false
}
return helper.SliceSetEq(s.Hosts, o.Hosts)
if !helper.SliceSetEq(s.Hosts, o.Hosts) {
return false
}
if !s.TLS.Equal(o.TLS) {
return false
}
if !s.RequestHeaders.Equal(o.RequestHeaders) {
return false
}
if !s.ResponseHeaders.Equal(o.ResponseHeaders) {
return false
}
if !pointer.Eq(s.MaxConnections, o.MaxConnections) {
return false
}
if !pointer.Eq(s.MaxPendingRequests, o.MaxPendingRequests) {
return false
}
if !pointer.Eq(s.MaxConcurrentRequests, o.MaxConcurrentRequests) {
return false
}
return true
}
func (s *ConsulIngressService) Validate(protocol string) error {
@@ -2171,7 +2334,12 @@ func ingressServicesEqual(a, b []*ConsulIngressService) bool {
//
// https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields
type ConsulIngressConfigEntry struct {
// TLS specifies a TLS configuration for the gateway.
TLS *ConsulGatewayTLSConfig
// Listeners specifies a list of listeners in the mesh for the
// gateway. Listeners are uniquely identified by their port number.
Listeners []*ConsulIngressListener
}

View File

@@ -10,9 +10,10 @@ description: |-
<Placement groups={['job', 'group', 'service', 'connect', 'gateway']} />
The `gateway` block allows configuration of [Consul Connect Gateways](/consul/docs/connect/gateways). Nomad will
automatically create the necessary Gateway [Configuration Entry](/consul/docs/agent/config-entries)
as well as inject an Envoy proxy task into the Nomad job to serve as the Gateway.
The `gateway` block allows configuration of [Consul Connect
Gateways](/consul/docs/connect/gateways). Nomad will automatically create the
necessary Gateway [Configuration Entry](/consul/docs/agent/config-entries) as
well as inject an Envoy proxy task into the Nomad job to serve as the Gateway.
The `gateway` configuration is valid within the context of a `connect` block.
Additional information about Gateway configurations can be found in Consul's
@@ -48,19 +49,23 @@ Exactly one of `ingress`, `terminating`, or `mesh` must be configured.
### `proxy` Parameters
- `connect_timeout` `(string: "5s")` - The amount of time to allow when making upstream
connections before timing out. Defaults to 5 seconds. If the upstream service has
the configuration option <code>[connect_timeout_ms]</code> set for the `service-resolver`, that
timeout value will take precedence over this gateway proxy option.
- `envoy_gateway_bind_tagged_addresses` `(bool: false)` - Indicates that the gateway
services tagged addresses should be bound to listeners in addition to the default
listener address.
- `envoy_gateway_bind_addresses` <code>(map<string|[address]>: nil)</code> - A map of additional addresses to be bound.
The keys to this map are the same of the listeners to be created and the values are
a map with two keys - address and port, that combined make the address to bind the
listener to. These are bound in addition to the default address.
If `bridge` networking is in use, this map is automatically populated with additional
listeners enabling the Envoy proxy to work from inside the network namespace.
- `connect_timeout` `(string: "5s")` - The amount of time to allow when making
upstream connections before timing out. Defaults to 5 seconds. If the upstream
service has the configuration option <code>[connect_timeout_ms]</code> set for
the `service-resolver`, that timeout value will take precedence over this
gateway proxy option.
- `envoy_gateway_bind_tagged_addresses` `(bool: false)` - Indicates that the
gateway services tagged addresses should be bound to listeners in addition to
the default listener address.
- `envoy_gateway_bind_addresses` <code>(map<string|[address]>: nil)</code> - A
map of additional addresses to be bound. The keys to this map are the same of
the listeners to be created and the values are a map with two keys - address
and port, that combined make the address to bind the listener to. These are
bound in addition to the default address. If `bridge` networking is in use,
this map is automatically populated with additional listeners enabling the
Envoy proxy to work from inside the network namespace.
```
envoy_gateway_bind_addresses "<service>" {
@@ -76,9 +81,9 @@ envoy_gateway_bind_addresses "<service>" {
service address from inside the network namespace.
- `envoy_dns_discovery_type` `(string: optional)` - Determintes how Envoy will
resolve hostnames. Defaults to `LOGICAL_DNS`. Must be one of `STRICT_DNS` or
`LOGICAL_DNS`. Details for each type are available in the [Envoy Documentation](https://www.envoyproxy.io/docs/envoy/v1.16.1/intro/arch_overview/upstream/service_discovery).
This option applies to terminating gateways that route to services addressed by a
hostname.
`LOGICAL_DNS`. Details for each type are available in the [Envoy
Documentation][envoy_sd_docs]. This option applies to terminating gateways
that route to services addressed by a hostname.
- `config` `(map: nil)` - Escape hatch for [Advanced Configuration] of Envoy.
Keys and values support [runtime variable interpolation][interpolation].
@@ -89,51 +94,39 @@ envoy_gateway_bind_addresses "<service>" {
### `ingress` Parameters
- `listener` <code>(array<[listener]> : required)</code> - One or more listeners
that the ingress gateway should setup, uniquely identified by their port
number.
- `tls` <code>([tls]: nil)</code> - TLS configuration for this gateway.
- `listener` <code>(array<[listener]> : required)</code> - One or more listeners that the
ingress gateway should setup, uniquely identified by their port number.
#### `tls` Parameters
- `enabled` `(bool: false)` - Set this configuration to enable TLS for every listener
on the gateway. If TLS is enabled, then each host defined in the `host` field will
be added as a DNSSAN to the gateway's x509 certificate.
- `tls_min_version` `(string: optional)` - Set the default minimum TLS version
supported by the gateway. Refer to
[`TLSMinVersion`](/consul/docs/connect/config-entries/ingress-gateway#tlsminversion)
in the Consul documentation for supported versions.
- `tls_max_version` `(string: optional)` - Set the default maximum TLS version
supported by the gateway. Refer to
[`TLSMaxVersion`](/consul/docs/connect/config-entries/ingress-gateway#tlsmaxversion)
in the Consul documentation for supported versions.
- `cipher_suites` `(array<string>: optional)` - Set the default list of TLS
cipher suites for the gateway's listeners. Refer to
[`CipherSuites`](/consul/docs/connect/config-entries/ingress-gateway#ciphersuites)
in the Consul documentation for the supported cipher suites.
#### `listener` Parameters
- `port` `(int: required)` - The port that the listener should receive traffic on.
- `protocol` `(string: "tcp")` - The protocol associated with the listener. One
of `tcp`, `http`, `http2`, or `grpc`.
~> **Note:** If using any protocol other than `tcp` (for example: `http` or `grpc`), preconfiguring a [service-default] in Consul to
set the [Protocol](/consul/docs/connect/config-entries/service-defaults#protocol)
of the service to the desired protocol is mandatory due to an [open issue](https://github.com/hashicorp/nomad/issues/8647).
~> **Note:** If using any protocol other than `tcp` (for example: `http` or
`grpc`), preconfiguring a [service-default][] in Consul to set the
[Protocol][service-default-protocol] of the service to the desired protocol is
mandatory due to an [open issue](https://github.com/hashicorp/nomad/issues/8647).
- `service` <code>(array<[service]>: required)</code> - One or more services to be
- `service` <code>(array<[listener-service]>: required)</code> - One or more services to be
exposed via this listener. For `tcp` listeners, only a single service is allowed.
#### `service` Parameters
#### Listener `service` Parameters
The `service` blocks for a listener under an `ingress` gateway accept the
following parameters. Note these are different than the `service` blocks under a
`terminating` gateway.
- `name` `(string: required)` - The name of the service that should be exposed through
this listener. This can be either a service registered in the catalog, or a
service defined by other config entries, or a service that is going to be configured
by Nomad. If the wildcard specifier `*` is provided, then ALL services will be
exposed through this listener. This is not supported for a listener with protocol `tcp`.
- `hosts` `(array<string>: nil)` - A list of hosts that specify what requests will
match this service. This cannot be used with a `tcp` listener, and cannot be
specified alongside a wildcard (`*`) service name. If not specified, the default
@@ -148,6 +141,79 @@ envoy_gateway_bind_addresses "<service>" {
hosts are valid DNS records. For example, `*.example.com` is valid while `example.*`
and `*-suffix.example.com` are not.
- `request_headers` `([header modifiers]: <optional>)` - A set of HTTP-specific
header modification rules that will be applied to requests routed to this
service. This cannot be used with a tcp listener.
- `response_headers` `([header modifiers]: <optional>)` - A set of HTTP-specific
header modification rules that will be applied to responses from this
service. This cannot be used with a tcp listener.
- `max_concurrent_requests` `(int: <optional>)` - Specifies the maximum number
of concurrent HTTP/2 traffic requests that are allowed at a single point in
time. If unset, will default to the Envoy proxy's default.
- `max_connections` `(int: <optional>)` - Specifies the maximum number of
HTTP/1.1 connections a service instance is allowed to establish against the
upstream. If unset, will default to the Envoy proxy's default.
- `max_pending_requests` `(int: <optional>)` - Specifies the maximum number of
requests that are allowed to queue while waiting to establish a connection.
If unset, will default to the Envoy proxy's default.
- `tls` <code>([tls]: nil)</code> - TLS configuration for this service.
#### Header modifier parameters
The `request_headers` and `response_headers` blocks of an `ingress.service`
block accept the following parameters. For more details, see the [Consul
documentation][response-headers].
- `add` `(map<string|string>: optional)` - A set of key-value pairs to add to the
headers, where header names are keys and header values are the values. Header
names are not case-sensitive. If header values with the same name already
exist, the value is appended and Consul applies both headers.
- `set` `(map<string|string>: optional)` - A set of key-value pairs to add to the
response header or to replace existing header values with. Use header names as
the keys. Header names are not case-sensitive. If header values with the same
names already exist, Consul replaces the header values.
- `remove` `array(string): optional` - Defines a list of headers to remove. Consul
removes only headers containing exact matches. Header names are not
case-sensitive.
#### `tls` Parameters
- `enabled` `(bool: false)` - Set this configuration to enable TLS for every
listener on the gateway. If TLS is enabled, then each host defined in the
`host` field will be added as a DNSSAN to the gateway's x509 certificate.
- `cipher_suites` `(array<string>: optional)` - Set the default list of TLS
cipher suites for the gateway's listeners. Refer to
[`CipherSuites`](/consul/docs/connect/config-entries/ingress-gateway#ciphersuites)
in the Consul documentation for the supported cipher suites.
- `sds` `(block: optional)` - Defines a set of parameters that configures the
listener to load TLS certificates from an external Secret Discovery Service
([SDS][]).
- `cluster_name` `(string)` - The SDS cluster name to connect to to retrieve
certificates.
- `cert_resource` `(string)` - The SDS resource name to request when fetching
the certificate from the SDS service.
- `tls_max_version` `(string: optional)` - Set the default maximum TLS version
supported by the gateway. Refer to
[`TLSMaxVersion`](/consul/docs/connect/config-entries/ingress-gateway#tlsmaxversion)
in the Consul documentation for supported versions.
- `tls_min_version` `(string: optional)` - Set the default minimum TLS version
supported by the gateway. Refer to
[`TLSMinVersion`](/consul/docs/connect/config-entries/ingress-gateway#tlsminversion)
in the Consul documentation for supported versions.
### `terminating` Parameters
- `service` <code>(array<[linked-service]>: required)</code> - One or more services to be
@@ -156,26 +222,35 @@ envoy_gateway_bind_addresses "<service>" {
addresses. They must also be registered in the same Consul datacenter as the
terminating gateway.
#### `service` Parameters
#### linked `service` Parameters
- `name` `(string: required)` - The name of the service to link with the gateway.
If the wildcard specifier `*` is provided, then ALL services within the Consul
namespace wil lbe linked with the gateway.
The `service` blocks for a `terminating` gateway accept the following
parameters. Note these are different than the `service` blocks for listeners
under an `ingress` gateway.
- `name` `(string: required)` - The name of the service to link with the
gateway. If the wildcard specifier `*` is provided, then ALL services within
the Consul namespace wil lbe linked with the gateway.
- `ca_file` `(string: <optional>)` - A file path to a PEM-encoded certificate
authority. The file must be accessible by the gateway task. The certificate authority
is used to verify the authenticity of the service linked with the gateway. It
can be provided along with a `cert_file` and `key_file` for mutual TLS
authentication, or on its own for one-way TLS authentication. If none is provided
the gateway **will not** encrypt traffic to the destination.
authority. The file must be accessible by the gateway task. The certificate
authority is used to verify the authenticity of the service linked with the
gateway. It can be provided along with a `cert_file` and `key_file` for mutual
TLS authentication, or on its own for one-way TLS authentication. If none is
provided the gateway **will not** encrypt traffic to the destination.
- `cert_file` `(string: <optional>)` - A file path to a PEM-encoded certificate.
The file must be accessible by the gateway task. The certificate is provided to servers
to verify the gateway's authenticity. It must be provided if a `key_file` is provided.
The file must be accessible by the gateway task. The certificate is provided
to servers to verify the gateway's authenticity. It must be provided if a
`key_file` is provided.
- `key_file` `(string: <optional>)` - A file path to a PEM-encoded private key.
The file must be accessible by the gateway task. The key is used with the certificate
to verify the gateway's authenticity. It must be provided if a `cert_file` is provided.
- `sni` `(string: <optional>)` - An optional hostname or domain name to specify during
the TLS handshake.
The file must be accessible by the gateway task. The key is used with the
certificate to verify the gateway's authenticity. It must be provided if a
`cert_file` is provided.
- `sni` `(string: <optional>)` - An optional hostname or domain name to specify
during the TLS handshake.
### `mesh` Parameters
@@ -654,13 +729,18 @@ job "countdash-mesh-two" {
[envoy docker]: https://hub.docker.com/r/envoyproxy/envoy/tags
[ingress]: /nomad/docs/job-specification/gateway#ingress-parameters
[proxy]: /nomad/docs/job-specification/gateway#proxy-parameters
[linked-service]: /nomad/docs/job-specification/gateway#service-parameters-1
[linked-service]: /nomad/docs/job-specification/gateway#linked-service-parameters
[listener]: /nomad/docs/job-specification/gateway#listener-parameters
[interpolation]: /nomad/docs/runtime/interpolation
[service]: /nomad/docs/job-specification/gateway#service-parameters
[listener-service]: /nomad/docs/job-specification/gateway#listener-service-parameters
[service-default]: /consul/docs/connect/config-entries/service-defaults
[sidecar_task]: /nomad/docs/job-specification/sidecar_task
[terminating]: /nomad/docs/job-specification/gateway#terminating-parameters
[tls]: /nomad/docs/job-specification/gateway#tls-parameters
[mesh]: /nomad/docs/job-specification/gateway#mesh-parameters
[meta]: /nomad/docs/job-specification/service#meta
[envoy_sd_docs]: https://www.envoyproxy.io/docs/envoy/v1.16.1/intro/arch_overview/upstream/service_discovery
[SDS]: https://developer.hashicorp.com/consul/docs/connect/config-entries/ingress-gateway#listeners-services-tls-sds
[service-default-protocol]: /consul/docs/connect/config-entries/service-defaults#protocol
[response-headers]: /consul/docs/connect/config-entries/ingress-gateway#listeners-services-responseheaders
[header modifiers]: /nomad/docs/job-specification/gateway#header-modifier-parameters