Permissive mTLS (#17035)

This implements permissive mTLS , which allows toggling services into "permissive" mTLS mode.
Permissive mTLS mode allows incoming "non Consul-mTLS" traffic to be forward unmodified to the application.

* Update service-defaults and proxy-defaults config entries with a MutualTLSMode field
* Update the mesh config entry with an AllowEnablingPermissiveMutualTLS field and implement the necessary validation. AllowEnablingPermissiveMutualTLS must be true to allow changing to MutualTLSMode=permissive, but this does not require that all proxy-defaults and service-defaults are currently in strict mode.
* Update xDS listener config to add a "permissive filter chain" when MutualTLSMode=permissive for a particular service. The permissive filter chain matches incoming traffic by the destination port. If the destination port matches the service port from the catalog, then no mTLS is required and the traffic sent is forwarded unmodified to the application.
This commit is contained in:
Paul Glass 2023-04-19 14:45:00 -05:00 committed by GitHub
parent 5e019393d3
commit d8d89d4b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 2101 additions and 1321 deletions

3
.changelog/17035.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
mesh: Add new permissive mTLS mode that allows sidecar proxies to forward incoming traffic unmodified to the application. This adds `AllowEnablingPermissiveMutualTLS` setting to the mesh config entry and the `MutualTLSMode` setting to proxy-defaults and service-defaults.
```

View File

@ -154,6 +154,10 @@ func MergeServiceConfig(defaults *structs.ServiceConfigResponse, service *struct
ns.Proxy.TransparentProxy.DialedDirectly = defaults.TransparentProxy.DialedDirectly ns.Proxy.TransparentProxy.DialedDirectly = defaults.TransparentProxy.DialedDirectly
} }
if ns.Proxy.MutualTLSMode == structs.MutualTLSModeDefault {
ns.Proxy.MutualTLSMode = defaults.MutualTLSMode
}
// remoteUpstreams contains synthetic Upstreams generated from central config (service-defaults.UpstreamConfigs). // remoteUpstreams contains synthetic Upstreams generated from central config (service-defaults.UpstreamConfigs).
remoteUpstreams := make(map[structs.PeeredServiceName]structs.Upstream) remoteUpstreams := make(map[structs.PeeredServiceName]structs.Upstream)

View File

@ -35,6 +35,7 @@ func Test_MergeServiceConfig_TransparentProxy(t *testing.T) {
ProxyConfig: map[string]interface{}{ ProxyConfig: map[string]interface{}{
"foo": "bar", "foo": "bar",
}, },
MutualTLSMode: structs.MutualTLSModePermissive,
Expose: structs.ExposeConfig{ Expose: structs.ExposeConfig{
Checks: true, Checks: true,
Paths: []structs.ExposePath{ Paths: []structs.ExposePath{
@ -76,6 +77,7 @@ func Test_MergeServiceConfig_TransparentProxy(t *testing.T) {
OutboundListenerPort: 10101, OutboundListenerPort: 10101,
DialedDirectly: true, DialedDirectly: true,
}, },
MutualTLSMode: structs.MutualTLSModePermissive,
Config: map[string]interface{}{ Config: map[string]interface{}{
"foo": "bar", "foo": "bar",
}, },

View File

@ -47,6 +47,7 @@ func ComputeResolvedServiceConfig(
thisReply.ProxyConfig = mapCopy.(map[string]interface{}) thisReply.ProxyConfig = mapCopy.(map[string]interface{})
thisReply.Mode = proxyConf.Mode thisReply.Mode = proxyConf.Mode
thisReply.TransparentProxy = proxyConf.TransparentProxy thisReply.TransparentProxy = proxyConf.TransparentProxy
thisReply.MutualTLSMode = proxyConf.MutualTLSMode
thisReply.MeshGateway = proxyConf.MeshGateway thisReply.MeshGateway = proxyConf.MeshGateway
thisReply.Expose = proxyConf.Expose thisReply.Expose = proxyConf.Expose
thisReply.EnvoyExtensions = proxyConf.EnvoyExtensions thisReply.EnvoyExtensions = proxyConf.EnvoyExtensions
@ -120,6 +121,10 @@ func ComputeResolvedServiceConfig(
thisReply.ProxyConfig = proxyConf thisReply.ProxyConfig = proxyConf
} }
if serviceConf.MutualTLSMode != structs.MutualTLSModeDefault {
thisReply.MutualTLSMode = serviceConf.MutualTLSMode
}
thisReply.Meta = serviceConf.Meta thisReply.Meta = serviceConf.Meta
// Service defaults' envoy extensions are appended to the proxy defaults extensions so that proxy defaults // Service defaults' envoy extensions are appended to the proxy defaults extensions so that proxy defaults
// extensions are applied first. // extensions are applied first.

View File

@ -494,6 +494,50 @@ func Test_ComputeResolvedServiceConfig(t *testing.T) {
}, },
}, },
}, },
{
name: "service-defaults inherits mutual_tls_mode from proxy-defaults",
args: args{
scReq: &structs.ServiceConfigRequest{
Name: "sid",
},
entries: &ResolvedServiceConfigSet{
ProxyDefaults: map[string]*structs.ProxyConfigEntry{
acl.DefaultEnterpriseMeta().PartitionOrDefault(): {
MutualTLSMode: structs.MutualTLSModePermissive,
},
},
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{
sid: {},
},
},
},
want: &structs.ServiceConfigResponse{
MutualTLSMode: structs.MutualTLSModePermissive,
},
},
{
name: "service-defaults overrides mutual_tls_mode in proxy-defaults",
args: args{
scReq: &structs.ServiceConfigRequest{
Name: "sid",
},
entries: &ResolvedServiceConfigSet{
ProxyDefaults: map[string]*structs.ProxyConfigEntry{
acl.DefaultEnterpriseMeta().PartitionOrDefault(): {
MutualTLSMode: structs.MutualTLSModeStrict,
},
},
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{
sid: {
MutualTLSMode: structs.MutualTLSModePermissive,
},
},
},
},
want: &structs.ServiceConfigResponse{
MutualTLSMode: structs.MutualTLSModePermissive,
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -18,6 +18,10 @@ import (
"github.com/hashicorp/consul/lib/maps" "github.com/hashicorp/consul/lib/maps"
) )
var (
permissiveModeNotAllowedError = errors.New("cannot set MutualTLSMode=permissive because AllowEnablingPermissiveMutualTLS=false in the mesh config entry")
)
type ConfigEntryLinkIndex struct { type ConfigEntryLinkIndex struct {
} }
@ -229,14 +233,16 @@ func ensureConfigEntryTxn(tx WriteTxn, idx uint64, statusUpdate bool, conf struc
return fmt.Errorf("failed configuration lookup: %s", err) return fmt.Errorf("failed configuration lookup: %s", err)
} }
var existingConf structs.ConfigEntry
raftIndex := conf.GetRaftIndex() raftIndex := conf.GetRaftIndex()
if existing != nil { if existing != nil {
existingIdx := existing.(structs.ConfigEntry).GetRaftIndex() existingConf = existing.(structs.ConfigEntry)
existingIdx := existingConf.GetRaftIndex()
raftIndex.CreateIndex = existingIdx.CreateIndex raftIndex.CreateIndex = existingIdx.CreateIndex
// Handle optional upsert logic. // Handle optional upsert logic.
if updatableConf, ok := conf.(structs.UpdatableConfigEntry); ok { if updatableConf, ok := conf.(structs.UpdatableConfigEntry); ok {
if err := updatableConf.UpdateOver(existing.(structs.ConfigEntry)); err != nil { if err := updatableConf.UpdateOver(existingConf); err != nil {
return err return err
} }
} }
@ -256,7 +262,7 @@ func ensureConfigEntryTxn(tx WriteTxn, idx uint64, statusUpdate bool, conf struc
} }
raftIndex.ModifyIndex = idx raftIndex.ModifyIndex = idx
err = validateProposedConfigEntryInGraph(tx, q, conf) err = validateProposedConfigEntryInGraph(tx, q, conf, existingConf)
if err != nil { if err != nil {
return err // Err is already sufficiently decorated. return err // Err is already sufficiently decorated.
} }
@ -445,7 +451,7 @@ func deleteConfigEntryTxn(tx WriteTxn, idx uint64, kind, name string, entMeta *a
} }
} }
err = validateProposedConfigEntryInGraph(tx, q, nil) err = validateProposedConfigEntryInGraph(tx, q, nil, c)
if err != nil { if err != nil {
return err // Err is already sufficiently decorated. return err // Err is already sufficiently decorated.
} }
@ -544,7 +550,7 @@ func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry)
func validateProposedConfigEntryInGraph( func validateProposedConfigEntryInGraph(
tx ReadTxn, tx ReadTxn,
kindName configentry.KindName, kindName configentry.KindName,
newEntry structs.ConfigEntry, newEntry, existingEntry structs.ConfigEntry,
) error { ) error {
switch kindName.Kind { switch kindName.Kind {
case structs.ProxyDefaults: case structs.ProxyDefaults:
@ -552,7 +558,25 @@ func validateProposedConfigEntryInGraph(
if kindName.Name != structs.ProxyConfigGlobal { if kindName.Name != structs.ProxyConfigGlobal {
return nil return nil
} }
if newPD, ok := newEntry.(*structs.ProxyConfigEntry); ok && newPD != nil {
var existingMode structs.MutualTLSMode
if existingPD, ok := existingEntry.(*structs.ProxyConfigEntry); ok && existingPD != nil {
existingMode = existingPD.MutualTLSMode
}
if err := checkMutualTLSMode(tx, kindName, newPD.MutualTLSMode, existingMode); err != nil {
return err
}
}
case structs.ServiceDefaults: case structs.ServiceDefaults:
if newSD, ok := newEntry.(*structs.ServiceConfigEntry); ok && newSD != nil {
var existingMode structs.MutualTLSMode
if existingSD, ok := existingEntry.(*structs.ServiceConfigEntry); ok && existingSD != nil {
existingMode = existingSD.MutualTLSMode
}
if err := checkMutualTLSMode(tx, kindName, newSD.MutualTLSMode, existingMode); err != nil {
return err
}
}
case structs.ServiceRouter: case structs.ServiceRouter:
case structs.ServiceSplitter: case structs.ServiceSplitter:
case structs.ServiceResolver: case structs.ServiceResolver:
@ -583,6 +607,54 @@ func validateProposedConfigEntryInGraph(
return validateProposedConfigEntryInServiceGraph(tx, kindName, newEntry) return validateProposedConfigEntryInServiceGraph(tx, kindName, newEntry)
} }
// checkMutualTLSMode validates the MutualTLSMode (in proxy-defaults or
// service-defaults) against the AllowEnablingPermissiveMutualTLS setting in the
// mesh config entry, as follows:
//
// - If AllowEnablingPermissiveMutualTLS=true, any value of MutualTLSMode is allowed.
// - If AllowEnablingPermissiveMutualTLS=false, *changing* to MutualTLSMode=permissive is not allowed
//
// If MutualTLSMode=permissive is already stored, but the setting is not being changed
// by this transaction, then the permissive setting is allowed (does not cause a validation error).
func checkMutualTLSMode(tx ReadTxn, kindName configentry.KindName, newMode, existingMode structs.MutualTLSMode) error {
// Setting the mode to something not permissive is always allowed.
if newMode != structs.MutualTLSModePermissive {
return nil
}
// If the MutualTLSMode has not been changed, then do not error. This allows
// remaining in MutualTLSMode=permissive without causing validation failures
// after AllowEnablingPermissiveMutualTLS=false is set.
if existingMode == newMode {
return nil
}
// The mesh config entry exists in the default namespace in the given partition.
metaInDefaultNS := acl.NewEnterpriseMetaWithPartition(
kindName.EnterpriseMeta.PartitionOrDefault(),
acl.DefaultNamespaceName,
)
_, mesh, err := configEntryTxn(tx, nil, structs.MeshConfig, structs.MeshConfigMesh, &metaInDefaultNS)
if err != nil {
return fmt.Errorf("unable to validate MutualTLSMode against mesh config entry: %w", err)
}
permissiveAllowed := false
if mesh != nil {
meshConfig, ok := mesh.(*structs.MeshConfigEntry)
if !ok {
return fmt.Errorf("unable to validate MutualTLSMode: invalid type from mesh config entry lookup: %T", mesh)
}
permissiveAllowed = meshConfig.AllowEnablingPermissiveMutualTLS
}
// If permissive is not allowed, then any value for MutualTLSMode is allowed.
if !permissiveAllowed && newMode == structs.MutualTLSModePermissive {
return permissiveModeNotAllowedError
}
return nil
}
func checkGatewayClash(tx ReadTxn, kindName configentry.KindName, otherKind string) error { func checkGatewayClash(tx ReadTxn, kindName configentry.KindName, otherKind string) error {
_, entry, err := configEntryTxn(tx, nil, otherKind, kindName.Name, &kindName.EnterpriseMeta) _, entry, err := configEntryTxn(tx, nil, otherKind, kindName.Name, &kindName.EnterpriseMeta)
if err != nil { if err != nil {

View File

@ -3182,3 +3182,210 @@ func TestStateStore_ConfigEntry_VirtualIP(t *testing.T) {
}) })
} }
} }
func TestStore_MutualTLSMode_Validation_InitialWrite(t *testing.T) {
cases := []struct {
// setup
mesh *structs.MeshConfigEntry
mtlsMode structs.MutualTLSMode
expErr error
}{
// Mesh config entry does not exist. Should default to AllowEnablingPermissiveMutualTLS=false.
{
mtlsMode: structs.MutualTLSModeDefault,
},
{
mtlsMode: structs.MutualTLSModeStrict,
},
{
mtlsMode: structs.MutualTLSModePermissive,
expErr: permissiveModeNotAllowedError,
},
// Mesh config entry contains AllowEnablingPermissiveMutualTLS=false
{
mesh: &structs.MeshConfigEntry{},
mtlsMode: structs.MutualTLSModeDefault,
},
{
mesh: &structs.MeshConfigEntry{},
mtlsMode: structs.MutualTLSModeStrict,
},
{
mesh: &structs.MeshConfigEntry{},
mtlsMode: structs.MutualTLSModePermissive,
expErr: permissiveModeNotAllowedError,
},
// Mesh config entry exists with AllowEnablingPermissiveMutualTLS=true.
{
mesh: &structs.MeshConfigEntry{AllowEnablingPermissiveMutualTLS: true},
mtlsMode: structs.MutualTLSModeDefault,
},
{
mesh: &structs.MeshConfigEntry{AllowEnablingPermissiveMutualTLS: true},
mtlsMode: structs.MutualTLSModeStrict,
},
{
mesh: &structs.MeshConfigEntry{AllowEnablingPermissiveMutualTLS: true},
mtlsMode: structs.MutualTLSModePermissive,
},
}
for _, c := range cases {
c := c
var name string
if c.mesh == nil {
name = fmt.Sprintf("when mesh config entry not found")
} else {
name = fmt.Sprintf("when AllowEnablingPermissiveMutualTLS=%v", c.mesh.AllowEnablingPermissiveMutualTLS)
}
if c.expErr != nil {
name += " cannot"
} else {
name += " can"
}
name += fmt.Sprintf(" set MutualTLSMode=%q", c.mtlsMode)
t.Run(name, func(t *testing.T) {
s := testConfigStateStore(t)
var err error
var idx uint64
if c.mesh != nil {
idx, err = writeConfigAndBumpIndexForTest(s, idx, c.mesh)
require.NoError(t, err)
}
idx, err = writeConfigAndBumpIndexForTest(s, idx, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
MutualTLSMode: c.mtlsMode,
})
require.Equal(t, c.expErr, err)
_, err = writeConfigAndBumpIndexForTest(s, idx, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "test-svc",
MutualTLSMode: c.mtlsMode,
})
require.Equal(t, c.expErr, err)
})
}
}
func TestStore_MutualTLSMode_Validation_SubsequentWrite(t *testing.T) {
cases := []struct {
allowPermissive bool
initialModes []structs.MutualTLSMode
transitions map[structs.MutualTLSMode]error
}{
{
allowPermissive: false,
initialModes: []structs.MutualTLSMode{
structs.MutualTLSModeDefault,
structs.MutualTLSModeStrict,
},
transitions: map[structs.MutualTLSMode]error{
structs.MutualTLSModeDefault: nil,
structs.MutualTLSModeStrict: nil,
// Cannot transition from "" -> "permissive"
// Cannot transition from "strict" -> "permissive"
structs.MutualTLSModePermissive: permissiveModeNotAllowedError,
},
},
{
allowPermissive: false,
initialModes: []structs.MutualTLSMode{
structs.MutualTLSModePermissive,
},
transitions: map[structs.MutualTLSMode]error{
structs.MutualTLSModeDefault: nil,
structs.MutualTLSModeStrict: nil,
// Can transition from "permissive" -> "permissive"
structs.MutualTLSModePermissive: nil,
},
},
{
allowPermissive: true,
initialModes: []structs.MutualTLSMode{
structs.MutualTLSModeDefault,
structs.MutualTLSModeStrict,
structs.MutualTLSModePermissive,
},
transitions: map[structs.MutualTLSMode]error{
// Can transition from any mode to any other mode when allowPermissive=true
structs.MutualTLSModeDefault: nil,
structs.MutualTLSModeStrict: nil,
structs.MutualTLSModePermissive: nil,
},
},
}
for _, c := range cases {
c := c
for _, initialMode := range c.initialModes {
for newMode, expErr := range c.transitions {
name := fmt.Sprintf("when AllowEnablingPermissiveMutualTLS=%v", c.allowPermissive)
if expErr != nil {
name += " cannot"
} else {
name += " can"
}
name += fmt.Sprintf(" transition MutualTLSMode from %q to %q", initialMode, newMode)
t.Run(name, func(t *testing.T) {
s := testConfigStateStore(t)
// Setup initial state.
idx, err := writeConfigAndBumpIndexForTest(s, 0, &structs.MeshConfigEntry{
AllowEnablingPermissiveMutualTLS: true, // set to true to allow writing any initial mode.
})
require.NoError(t, err)
idx, err = writeConfigAndBumpIndexForTest(s, idx, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
MutualTLSMode: initialMode,
})
require.NoError(t, err)
idx, err = writeConfigAndBumpIndexForTest(s, idx, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "test-svc",
MutualTLSMode: initialMode,
})
require.NoError(t, err)
// Set AllowEnablingPermissiveMutualTLS for the test case.
idx, err = writeConfigAndBumpIndexForTest(s, idx, &structs.MeshConfigEntry{
AllowEnablingPermissiveMutualTLS: c.allowPermissive,
})
require.NoError(t, err)
// Test switching to the other mode.
idx, err = writeConfigAndBumpIndexForTest(s, idx, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
MutualTLSMode: newMode,
})
require.Equal(t, expErr, err)
_, err = writeConfigAndBumpIndexForTest(s, idx, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "test-svc",
MutualTLSMode: newMode,
})
require.Equal(t, expErr, err)
})
}
}
}
}
func writeConfigAndBumpIndexForTest(s *Store, idx uint64, entry structs.ConfigEntry) (uint64, error) {
err := s.EnsureConfigEntry(idx, entry)
if err == nil {
idx++
}
return idx, err
}

View File

@ -125,6 +125,26 @@ type WarningConfigEntry interface {
ConfigEntry ConfigEntry
} }
type MutualTLSMode string
const (
MutualTLSModeDefault MutualTLSMode = ""
MutualTLSModeStrict MutualTLSMode = "strict"
MutualTLSModePermissive MutualTLSMode = "permissive"
)
func (m MutualTLSMode) validate() error {
switch m {
case MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive:
return nil
}
return fmt.Errorf("Invalid MutualTLSMode %q. Must be one of %q, %q, or %q.", m,
MutualTLSModeDefault,
MutualTLSModeStrict,
MutualTLSModePermissive,
)
}
// ServiceConfiguration is the top-level struct for the configuration of a service // ServiceConfiguration is the top-level struct for the configuration of a service
// across the entire cluster. // across the entire cluster.
type ServiceConfigEntry struct { type ServiceConfigEntry struct {
@ -133,6 +153,7 @@ type ServiceConfigEntry struct {
Protocol string Protocol string
Mode ProxyMode `json:",omitempty"` Mode ProxyMode `json:",omitempty"`
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"` Expose ExposeConfig `json:",omitempty"`
ExternalSNI string `json:",omitempty" alias:"external_sni"` ExternalSNI string `json:",omitempty" alias:"external_sni"`
@ -267,6 +288,10 @@ func (e *ServiceConfigEntry) Validate() error {
validationErr = multierror.Append(validationErr, err) validationErr = multierror.Append(validationErr, err)
} }
if err := e.MutualTLSMode.validate(); err != nil {
return err
}
return validationErr return validationErr
} }
@ -372,6 +397,7 @@ type ProxyConfigEntry struct {
Config map[string]interface{} Config map[string]interface{}
Mode ProxyMode `json:",omitempty"` Mode ProxyMode `json:",omitempty"`
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"` Expose ExposeConfig `json:",omitempty"`
AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"` AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"`
@ -452,6 +478,10 @@ func (e *ProxyConfigEntry) Validate() error {
return err return err
} }
if err := e.MutualTLSMode.validate(); err != nil {
return err
}
return e.validateEnterpriseMeta() return e.validateEnterpriseMeta()
} }
@ -1157,6 +1187,7 @@ type ServiceConfigResponse struct {
MeshGateway MeshGatewayConfig `json:",omitempty"` MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"` Expose ExposeConfig `json:",omitempty"`
TransparentProxy TransparentProxyConfig `json:",omitempty"` TransparentProxy TransparentProxyConfig `json:",omitempty"`
MutualTLSMode MutualTLSMode `json:",omitempty"`
Mode ProxyMode `json:",omitempty"` Mode ProxyMode `json:",omitempty"`
Destination DestinationConfig `json:",omitempty"` Destination DestinationConfig `json:",omitempty"`
AccessLogs AccessLogsConfig `json:",omitempty"` AccessLogs AccessLogsConfig `json:",omitempty"`

View File

@ -16,6 +16,10 @@ type MeshConfigEntry struct {
// when enabled. // when enabled.
TransparentProxy TransparentProxyMeshConfig `alias:"transparent_proxy"` TransparentProxy TransparentProxyMeshConfig `alias:"transparent_proxy"`
// AllowEnablingPermissiveMutualTLS must be true in order to allow setting
// MutualTLSMode=permissive in either service-defaults or proxy-defaults.
AllowEnablingPermissiveMutualTLS bool `json:",omitempty" alias:"allow_enabling_permissive_mutual_tls"`
TLS *MeshTLSConfig `json:",omitempty"` TLS *MeshTLSConfig `json:",omitempty"`
HTTP *MeshHTTPConfig `json:",omitempty"` HTTP *MeshHTTPConfig `json:",omitempty"`

View File

@ -350,6 +350,7 @@ func TestDecodeConfigEntry(t *testing.T) {
mesh_gateway { mesh_gateway {
mode = "remote" mode = "remote"
} }
mutual_tls_mode = "permissive"
`, `,
camel: ` camel: `
Kind = "proxy-defaults" Kind = "proxy-defaults"
@ -369,6 +370,7 @@ func TestDecodeConfigEntry(t *testing.T) {
MeshGateway { MeshGateway {
Mode = "remote" Mode = "remote"
} }
MutualTLSMode = "permissive"
`, `,
expect: &ProxyConfigEntry{ expect: &ProxyConfigEntry{
Kind: "proxy-defaults", Kind: "proxy-defaults",
@ -388,6 +390,7 @@ func TestDecodeConfigEntry(t *testing.T) {
MeshGateway: MeshGatewayConfig{ MeshGateway: MeshGatewayConfig{
Mode: MeshGatewayModeRemote, Mode: MeshGatewayModeRemote,
}, },
MutualTLSMode: MutualTLSModePermissive,
}, },
}, },
{ {
@ -404,6 +407,7 @@ func TestDecodeConfigEntry(t *testing.T) {
mesh_gateway { mesh_gateway {
mode = "remote" mode = "remote"
} }
mutual_tls_mode = "permissive"
balance_inbound_connections = "exact_balance" balance_inbound_connections = "exact_balance"
upstream_config { upstream_config {
overrides = [ overrides = [
@ -447,6 +451,7 @@ func TestDecodeConfigEntry(t *testing.T) {
MeshGateway { MeshGateway {
Mode = "remote" Mode = "remote"
} }
MutualTLSMode = "permissive"
BalanceInboundConnections = "exact_balance" BalanceInboundConnections = "exact_balance"
UpstreamConfig { UpstreamConfig {
Overrides = [ Overrides = [
@ -490,6 +495,7 @@ func TestDecodeConfigEntry(t *testing.T) {
MeshGateway: MeshGatewayConfig{ MeshGateway: MeshGatewayConfig{
Mode: MeshGatewayModeRemote, Mode: MeshGatewayModeRemote,
}, },
MutualTLSMode: MutualTLSModePermissive,
BalanceInboundConnections: "exact_balance", BalanceInboundConnections: "exact_balance",
UpstreamConfig: &UpstreamConfiguration{ UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{ Overrides: []*UpstreamConfig{
@ -1811,6 +1817,7 @@ func TestDecodeConfigEntry(t *testing.T) {
transparent_proxy { transparent_proxy {
mesh_destinations_only = true mesh_destinations_only = true
} }
allow_enabling_permissive_mutual_tls = true
tls { tls {
incoming { incoming {
tls_min_version = "TLSv1_1" tls_min_version = "TLSv1_1"
@ -1845,6 +1852,7 @@ func TestDecodeConfigEntry(t *testing.T) {
TransparentProxy { TransparentProxy {
MeshDestinationsOnly = true MeshDestinationsOnly = true
} }
AllowEnablingPermissiveMutualTLS = true
TLS { TLS {
Incoming { Incoming {
TLSMinVersion = "TLSv1_1" TLSMinVersion = "TLSv1_1"
@ -1878,6 +1886,7 @@ func TestDecodeConfigEntry(t *testing.T) {
TransparentProxy: TransparentProxyMeshConfig{ TransparentProxy: TransparentProxyMeshConfig{
MeshDestinationsOnly: true, MeshDestinationsOnly: true,
}, },
AllowEnablingPermissiveMutualTLS: true,
TLS: &MeshTLSConfig{ TLS: &MeshTLSConfig{
Incoming: &MeshDirectionalTLSConfig{ Incoming: &MeshDirectionalTLSConfig{
TLSMinVersion: types.TLSv1_1, TLSMinVersion: types.TLSv1_1,
@ -2840,6 +2849,22 @@ func TestServiceConfigEntry(t *testing.T) {
}, },
}, },
}, },
"validate: invalid MutualTLSMode in service-defaults": {
entry: &ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
MutualTLSMode: MutualTLSMode("invalid-mtls-mode"),
},
validateErr: `Invalid MutualTLSMode "invalid-mtls-mode". Must be one of "", "strict", or "permissive".`,
},
"validate: invalid MutualTLSMode in proxy-defaults": {
entry: &ServiceConfigEntry{
Kind: ProxyDefaults,
Name: ProxyConfigGlobal,
MutualTLSMode: MutualTLSMode("invalid-mtls-mode"),
},
validateErr: `Invalid MutualTLSMode "invalid-mtls-mode". Must be one of "", "strict", or "permissive".`,
},
} }
testConfigEntryNormalizeAndValidate(t, cases) testConfigEntryNormalizeAndValidate(t, cases)
} }

View File

@ -289,6 +289,9 @@ type ConnectProxyConfig struct {
// transparent mode. // transparent mode.
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
// MutualTLSMode allows configuring the proxy to allow non-mTLS traffic.
MutualTLSMode MutualTLSMode `json:"-" bexpr:"-"`
// AccessLogs configures the output and format of Envoy access logs // AccessLogs configures the output and format of Envoy access logs
AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"` AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"`
} }

View File

@ -1430,9 +1430,52 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
return nil, fmt.Errorf("failed to attach Consul filters and TLS context to custom public listener: %v", err) return nil, fmt.Errorf("failed to attach Consul filters and TLS context to custom public listener: %v", err)
} }
// When permissive mTLS mode is enabled, include an additional filter chain
// that matches on the `destination_port == <service port>`. Traffic sent
// directly to the service port is passed through to the application
// unmodified.
if cfgSnap.Proxy.MutualTLSMode == structs.MutualTLSModePermissive {
chain, err := makePermissiveFilterChain(cfgSnap, filterOpts)
if err != nil {
return nil, fmt.Errorf("unable to add permissive mtls filter chain: %w", err)
}
if chain == nil {
s.Logger.Debug("no service port defined for service in permissive mTLS mode; not adding filter chain for non-mTLS traffic")
} else {
l.FilterChains = append(l.FilterChains, chain)
// With tproxy, the REDIRECT iptables target rewrites the destination ip/port
// to the proxy ip/port (e.g. 127.0.0.1:20000) for incoming packets.
// We need the original_dst filter to recover the original destination address.
l.UseOriginalDst = &wrapperspb.BoolValue{Value: true}
}
}
return l, err return l, err
} }
func makePermissiveFilterChain(cfgSnap *proxycfg.ConfigSnapshot, opts listenerFilterOpts) (*envoy_listener_v3.FilterChain, error) {
servicePort := cfgSnap.Proxy.LocalServicePort
if servicePort <= 0 {
// No service port means the service does not accept incoming traffic, so
// the connect proxy does not need to listen for incoming non-mTLS traffic.
return nil, nil
}
opts.statPrefix += "permissive_"
filter, err := makeTCPProxyFilter(opts)
if err != nil {
return nil, err
}
chain := &envoy_listener_v3.FilterChain{
FilterChainMatch: &envoy_listener_v3.FilterChainMatch{
DestinationPort: &wrapperspb.UInt32Value{Value: uint32(servicePort)},
},
Filters: []*envoy_listener_v3.Filter{filter},
}
return chain, nil
}
// finalizePublicListenerFromConfig is used for best-effort injection of Consul filter-chains onto listeners. // finalizePublicListenerFromConfig is used for best-effort injection of Consul filter-chains onto listeners.
// This include L4 authorization filters and TLS context. // This include L4 authorization filters and TLS context.
func (s *ResourceGenerator) finalizePublicListenerFromConfig(l *envoy_listener_v3.Listener, cfgSnap *proxycfg.ConfigSnapshot, useHTTPFilter bool) error { func (s *ResourceGenerator) finalizePublicListenerFromConfig(l *envoy_listener_v3.Listener, cfgSnap *proxycfg.ConfigSnapshot, useHTTPFilter bool) error {

View File

@ -1099,6 +1099,16 @@ func TestListenersFromSnapshot(t *testing.T) {
nil) nil)
}, },
}, },
{
name: "connect-proxy-with-tproxy-and-permissive-mtls",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
ns.Proxy.MutualTLSMode = structs.MutualTLSModePermissive
ns.Proxy.Mode = structs.ProxyModeTransparent
},
nil)
},
},
} }
tests = append(tests, makeListenerDiscoChainTests(false)...) tests = append(tests, makeListenerDiscoChainTests(false)...)

View File

@ -0,0 +1,162 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "db:127.0.0.1:9191",
"address": {
"socketAddress": {
"address": "127.0.0.1",
"portValue": 9191
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.filters.network.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "upstream.db.default.default.dc1",
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"trafficDirection": "OUTBOUND"
},
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "outbound_listener:127.0.0.1:15001",
"address": {
"socketAddress": {
"address": "127.0.0.1",
"portValue": 15001
}
},
"defaultFilterChain": {
"filters": [
{
"name": "envoy.filters.network.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "upstream.original-destination",
"cluster": "original-destination"
}
}
]
},
"listenerFilters": [
{
"name": "envoy.filters.listener.original_dst",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst"
}
}
],
"trafficDirection": "OUTBOUND"
},
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "prepared_query:geo-cache:127.10.10.10:8181",
"address": {
"socketAddress": {
"address": "127.10.10.10",
"portValue": 8181
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.filters.network.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "upstream.prepared_query_geo-cache",
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"trafficDirection": "OUTBOUND"
},
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "public_listener:0.0.0.0:9999",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 9999
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.filters.network.rbac",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
"rules": {},
"statPrefix": "connect_authz"
}
},
{
"name": "envoy.filters.network.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "public_listener",
"cluster": "local_app"
}
}
],
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
"commonTlsContext": {
"tlsParams": {},
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
}
}
],
"validationContext": {
"trustedCa": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
}
}
},
"requireClientCertificate": true
}
}
},
{
"filterChainMatch": {
"destinationPort": 8080
},
"filters": [
{
"name": "envoy.filters.network.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "permissive_public_listener",
"cluster": "local_app"
}
}
]
}
],
"useOriginalDst": true,
"trafficDirection": "INBOUND"
}
],
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -109,6 +109,21 @@ type TransparentProxyConfig struct {
DialedDirectly bool `json:",omitempty" alias:"dialed_directly"` DialedDirectly bool `json:",omitempty" alias:"dialed_directly"`
} }
type MutualTLSMode string
const (
// MutualTLSModeDefault represents no specific mode and should
// be used to indicate that a different layer of the configuration
// chain should take precedence.
MutualTLSModeDefault MutualTLSMode = ""
// MutualTLSModeStrict requires mTLS for incoming traffic.
MutualTLSModeStrict MutualTLSMode = "strict"
// MutualTLSModePermissive allows incoming non-mTLS traffic.
MutualTLSModePermissive MutualTLSMode = "permissive"
)
// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect. // ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
// Users can expose individual paths and/or all HTTP/GRPC paths for checks. // Users can expose individual paths and/or all HTTP/GRPC paths for checks.
type ExposeConfig struct { type ExposeConfig struct {
@ -290,6 +305,7 @@ type ServiceConfigEntry struct {
Protocol string `json:",omitempty"` Protocol string `json:",omitempty"`
Mode ProxyMode `json:",omitempty"` Mode ProxyMode `json:",omitempty"`
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"` Expose ExposeConfig `json:",omitempty"`
ExternalSNI string `json:",omitempty" alias:"external_sni"` ExternalSNI string `json:",omitempty" alias:"external_sni"`
@ -320,6 +336,7 @@ type ProxyConfigEntry struct {
Namespace string `json:",omitempty"` Namespace string `json:",omitempty"`
Mode ProxyMode `json:",omitempty"` Mode ProxyMode `json:",omitempty"`
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
Config map[string]interface{} `json:",omitempty"` Config map[string]interface{} `json:",omitempty"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"` Expose ExposeConfig `json:",omitempty"`

View File

@ -22,6 +22,10 @@ type MeshConfigEntry struct {
// in transparent mode. // in transparent mode.
TransparentProxy TransparentProxyMeshConfig `alias:"transparent_proxy"` TransparentProxy TransparentProxyMeshConfig `alias:"transparent_proxy"`
// AllowEnablingPermissiveMutualTLS must be true in order to allow setting
// MutualTLSMode=permissive in either service-defaults or proxy-defaults.
AllowEnablingPermissiveMutualTLS bool `json:",omitempty" alias:"allow_enabling_permissive_mutual_tls"`
TLS *MeshTLSConfig `json:",omitempty"` TLS *MeshTLSConfig `json:",omitempty"`
HTTP *MeshHTTPConfig `json:",omitempty"` HTTP *MeshHTTPConfig `json:",omitempty"`

View File

@ -28,6 +28,7 @@ func TestAPI_ConfigEntries(t *testing.T) {
"foo": "bar", "foo": "bar",
"bar": 1.0, "bar": 1.0,
}, },
MutualTLSMode: MutualTLSModeStrict,
Meta: map[string]string{ Meta: map[string]string{
"foo": "bar", "foo": "bar",
"gir": "zim", "gir": "zim",
@ -52,7 +53,8 @@ func TestAPI_ConfigEntries(t *testing.T) {
require.Equal(t, global_proxy.Kind, readProxy.Kind) require.Equal(t, global_proxy.Kind, readProxy.Kind)
require.Equal(t, global_proxy.Name, readProxy.Name) require.Equal(t, global_proxy.Name, readProxy.Name)
require.Equal(t, global_proxy.Config, readProxy.Config) require.Equal(t, global_proxy.Config, readProxy.Config)
require.Equal(t, global_proxy.Meta, readProxy.Meta) require.Equal(t, global_proxy.MutualTLSMode, readProxy.MutualTLSMode)
require.Equal(t, global_proxy.Meta, readProxy.GetMeta())
require.Equal(t, global_proxy.Meta, readProxy.GetMeta()) require.Equal(t, global_proxy.Meta, readProxy.GetMeta())
global_proxy.Config["baz"] = true global_proxy.Config["baz"] = true
@ -100,9 +102,10 @@ func TestAPI_ConfigEntries(t *testing.T) {
t.Run("Service Defaults", func(t *testing.T) { t.Run("Service Defaults", func(t *testing.T) {
service := &ServiceConfigEntry{ service := &ServiceConfigEntry{
Kind: ServiceDefaults, Kind: ServiceDefaults,
Name: "foo", Name: "foo",
Protocol: "udp", Protocol: "udp",
MutualTLSMode: MutualTLSModeStrict,
Meta: map[string]string{ Meta: map[string]string{
"foo": "bar", "foo": "bar",
"gir": "zim", "gir": "zim",
@ -149,6 +152,7 @@ func TestAPI_ConfigEntries(t *testing.T) {
require.Equal(t, service.Kind, readService.Kind) require.Equal(t, service.Kind, readService.Kind)
require.Equal(t, service.Name, readService.Name) require.Equal(t, service.Name, readService.Name)
require.Equal(t, service.Protocol, readService.Protocol) require.Equal(t, service.Protocol, readService.Protocol)
require.Equal(t, service.MutualTLSMode, readService.MutualTLSMode)
require.Equal(t, service.Meta, readService.Meta) require.Equal(t, service.Meta, readService.Meta)
require.Equal(t, service.Meta, readService.GetMeta()) require.Equal(t, service.Meta, readService.GetMeta())
require.Equal(t, service.MaxInboundConnections, readService.MaxInboundConnections) require.Equal(t, service.MaxInboundConnections, readService.MaxInboundConnections)
@ -219,7 +223,8 @@ func TestAPI_ConfigEntries(t *testing.T) {
t.Run("Mesh", func(t *testing.T) { t.Run("Mesh", func(t *testing.T) {
mesh := &MeshConfigEntry{ mesh := &MeshConfigEntry{
TransparentProxy: TransparentProxyMeshConfig{MeshDestinationsOnly: true}, TransparentProxy: TransparentProxyMeshConfig{MeshDestinationsOnly: true},
AllowEnablingPermissiveMutualTLS: true,
Meta: map[string]string{ Meta: map[string]string{
"foo": "bar", "foo": "bar",
"gir": "zim", "gir": "zim",

View File

@ -1035,6 +1035,7 @@ func MeshConfigToStructs(s *MeshConfig, t *structs.MeshConfigEntry) {
if s.TransparentProxy != nil { if s.TransparentProxy != nil {
TransparentProxyMeshConfigToStructs(s.TransparentProxy, &t.TransparentProxy) TransparentProxyMeshConfigToStructs(s.TransparentProxy, &t.TransparentProxy)
} }
t.AllowEnablingPermissiveMutualTLS = s.AllowEnablingPermissiveMutualTLS
if s.TLS != nil { if s.TLS != nil {
var x structs.MeshTLSConfig var x structs.MeshTLSConfig
MeshTLSConfigToStructs(s.TLS, &x) MeshTLSConfigToStructs(s.TLS, &x)
@ -1061,6 +1062,7 @@ func MeshConfigFromStructs(t *structs.MeshConfigEntry, s *MeshConfig) {
TransparentProxyMeshConfigFromStructs(&t.TransparentProxy, &x) TransparentProxyMeshConfigFromStructs(&t.TransparentProxy, &x)
s.TransparentProxy = &x s.TransparentProxy = &x
} }
s.AllowEnablingPermissiveMutualTLS = t.AllowEnablingPermissiveMutualTLS
if t.TLS != nil { if t.TLS != nil {
var x MeshTLSConfig var x MeshTLSConfig
MeshTLSConfigFromStructs(t.TLS, &x) MeshTLSConfigFromStructs(t.TLS, &x)
@ -1269,6 +1271,7 @@ func ServiceDefaultsToStructs(s *ServiceDefaults, t *structs.ServiceConfigEntry)
if s.TransparentProxy != nil { if s.TransparentProxy != nil {
TransparentProxyConfigToStructs(s.TransparentProxy, &t.TransparentProxy) TransparentProxyConfigToStructs(s.TransparentProxy, &t.TransparentProxy)
} }
t.MutualTLSMode = mutualTLSModeToStructs(s.MutualTLSMode)
if s.MeshGateway != nil { if s.MeshGateway != nil {
MeshGatewayConfigToStructs(s.MeshGateway, &t.MeshGateway) MeshGatewayConfigToStructs(s.MeshGateway, &t.MeshGateway)
} }
@ -1304,6 +1307,7 @@ func ServiceDefaultsFromStructs(t *structs.ServiceConfigEntry, s *ServiceDefault
TransparentProxyConfigFromStructs(&t.TransparentProxy, &x) TransparentProxyConfigFromStructs(&t.TransparentProxy, &x)
s.TransparentProxy = &x s.TransparentProxy = &x
} }
s.MutualTLSMode = mutualTLSModeFromStructs(t.MutualTLSMode)
{ {
var x MeshGatewayConfig var x MeshGatewayConfig
MeshGatewayConfigFromStructs(&t.MeshGateway, &x) MeshGatewayConfigFromStructs(&t.MeshGateway, &x)

View File

@ -340,6 +340,32 @@ func proxyModeToStructs(a ProxyMode) structs.ProxyMode {
} }
} }
func mutualTLSModeFromStructs(a structs.MutualTLSMode) MutualTLSMode {
switch a {
case structs.MutualTLSModeDefault:
return MutualTLSMode_MutualTLSModeDefault
case structs.MutualTLSModeStrict:
return MutualTLSMode_MutualTLSModeStrict
case structs.MutualTLSModePermissive:
return MutualTLSMode_MutualTLSModePermissive
default:
return MutualTLSMode_MutualTLSModeDefault
}
}
func mutualTLSModeToStructs(a MutualTLSMode) structs.MutualTLSMode {
switch a {
case MutualTLSMode_MutualTLSModeDefault:
return structs.MutualTLSModeDefault
case MutualTLSMode_MutualTLSModeStrict:
return structs.MutualTLSModeStrict
case MutualTLSMode_MutualTLSModePermissive:
return structs.MutualTLSModePermissive
default:
return structs.MutualTLSModeDefault
}
}
func meshGatewayModeFromStructs(a structs.MeshGatewayMode) MeshGatewayMode { func meshGatewayModeFromStructs(a structs.MeshGatewayMode) MeshGatewayMode {
switch a { switch a {
case structs.MeshGatewayModeDefault: case structs.MeshGatewayModeDefault:

File diff suppressed because it is too large Load Diff

View File

@ -58,6 +58,7 @@ message MeshConfig {
MeshHTTPConfig HTTP = 3; MeshHTTPConfig HTTP = 3;
map<string, string> Meta = 4; map<string, string> Meta = 4;
PeeringMeshConfig Peering = 5; PeeringMeshConfig Peering = 5;
bool AllowEnablingPermissiveMutualTLS = 6;
} }
// mog annotation: // mog annotation:
@ -475,6 +476,8 @@ message ServiceDefaults {
map<string, string> Meta = 13; map<string, string> Meta = 13;
// mog: func-to=EnvoyExtensionsToStructs func-from=EnvoyExtensionsFromStructs // mog: func-to=EnvoyExtensionsToStructs func-from=EnvoyExtensionsFromStructs
repeated hashicorp.consul.internal.common.EnvoyExtension EnvoyExtensions = 14; repeated hashicorp.consul.internal.common.EnvoyExtension EnvoyExtensions = 14;
// mog: func-to=mutualTLSModeToStructs func-from=mutualTLSModeFromStructs
MutualTLSMode MutualTLSMode = 15;
} }
enum ProxyMode { enum ProxyMode {
@ -483,6 +486,12 @@ enum ProxyMode {
ProxyModeDirect = 2; ProxyModeDirect = 2;
} }
enum MutualTLSMode {
MutualTLSModeDefault = 0;
MutualTLSModeStrict = 1;
MutualTLSModePermissive = 2;
}
// mog annotation: // mog annotation:
// //
// target=github.com/hashicorp/consul/agent/structs.TransparentProxyConfig // target=github.com/hashicorp/consul/agent/structs.TransparentProxyConfig

View File

@ -19,7 +19,7 @@ import (
func TestNewCheckServiceNodeFromStructs_RoundTrip(t *testing.T) { func TestNewCheckServiceNodeFromStructs_RoundTrip(t *testing.T) {
repeat(t, func(t *testing.T, fuzzer *fuzz.Fuzzer) { repeat(t, func(t *testing.T, fuzzer *fuzz.Fuzzer) {
fuzzer.Funcs(randInt32, randUint32, randInterface, randStructsUpstream, randEnterpriseMeta) fuzzer.Funcs(randInt32, randUint32, randInterface, randStructsUpstream, randEnterpriseMeta, randStructsConnectProxyConfig)
var target structs.CheckServiceNode var target structs.CheckServiceNode
fuzzer.Fuzz(&target) fuzzer.Fuzz(&target)
@ -77,6 +77,19 @@ func randInt32(i *int, c fuzz.Continue) {
*i = int(c.Rand.Int31()) *i = int(c.Rand.Int31())
} }
// randStructsConnectProxyConfig is a custom fuzzer function which skips
// generating values for fields enumerated in the ignore-fields annotation.
func randStructsConnectProxyConfig(p *structs.ConnectProxyConfig, c fuzz.Continue) {
v := reflect.ValueOf(p).Elem()
for i := 0; i < v.NumField(); i++ {
switch v.Type().Field(i).Name {
case "MutualTLSMode":
continue
}
c.Fuzz(v.Field(i).Addr().Interface())
}
}
// randStructsUpstream is a custom fuzzer function which skips generating values // randStructsUpstream is a custom fuzzer function which skips generating values
// for fields enumerated in the ignore-fields annotation. // for fields enumerated in the ignore-fields annotation.
func randStructsUpstream(u *structs.Upstream, c fuzz.Continue) { func randStructsUpstream(u *structs.Upstream, c fuzz.Continue) {

View File

@ -36,6 +36,7 @@ const (
// target=github.com/hashicorp/consul/agent/structs.ConnectProxyConfig // target=github.com/hashicorp/consul/agent/structs.ConnectProxyConfig
// output=service.gen.go // output=service.gen.go
// name=Structs // name=Structs
// ignore-fields=MutualTLSMode
type ConnectProxyConfig struct { type ConnectProxyConfig struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache

View File

@ -20,6 +20,7 @@ import "private/pbservice/healthcheck.proto";
// target=github.com/hashicorp/consul/agent/structs.ConnectProxyConfig // target=github.com/hashicorp/consul/agent/structs.ConnectProxyConfig
// output=service.gen.go // output=service.gen.go
// name=Structs // name=Structs
// ignore-fields=MutualTLSMode
message ConnectProxyConfig { message ConnectProxyConfig {
// DestinationServiceName is required and is the name of the service to accept // DestinationServiceName is required and is the name of the service to accept
// traffic for. // traffic for.