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:
parent
5e019393d3
commit
d8d89d4b59
|
@ -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.
|
||||
```
|
|
@ -154,6 +154,10 @@ func MergeServiceConfig(defaults *structs.ServiceConfigResponse, service *struct
|
|||
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 := make(map[structs.PeeredServiceName]structs.Upstream)
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ func Test_MergeServiceConfig_TransparentProxy(t *testing.T) {
|
|||
ProxyConfig: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
MutualTLSMode: structs.MutualTLSModePermissive,
|
||||
Expose: structs.ExposeConfig{
|
||||
Checks: true,
|
||||
Paths: []structs.ExposePath{
|
||||
|
@ -76,6 +77,7 @@ func Test_MergeServiceConfig_TransparentProxy(t *testing.T) {
|
|||
OutboundListenerPort: 10101,
|
||||
DialedDirectly: true,
|
||||
},
|
||||
MutualTLSMode: structs.MutualTLSModePermissive,
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
|
|
|
@ -47,6 +47,7 @@ func ComputeResolvedServiceConfig(
|
|||
thisReply.ProxyConfig = mapCopy.(map[string]interface{})
|
||||
thisReply.Mode = proxyConf.Mode
|
||||
thisReply.TransparentProxy = proxyConf.TransparentProxy
|
||||
thisReply.MutualTLSMode = proxyConf.MutualTLSMode
|
||||
thisReply.MeshGateway = proxyConf.MeshGateway
|
||||
thisReply.Expose = proxyConf.Expose
|
||||
thisReply.EnvoyExtensions = proxyConf.EnvoyExtensions
|
||||
|
@ -120,6 +121,10 @@ func ComputeResolvedServiceConfig(
|
|||
thisReply.ProxyConfig = proxyConf
|
||||
}
|
||||
|
||||
if serviceConf.MutualTLSMode != structs.MutualTLSModeDefault {
|
||||
thisReply.MutualTLSMode = serviceConf.MutualTLSMode
|
||||
}
|
||||
|
||||
thisReply.Meta = serviceConf.Meta
|
||||
// Service defaults' envoy extensions are appended to the proxy defaults extensions so that proxy defaults
|
||||
// extensions are applied first.
|
||||
|
|
|
@ -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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -18,6 +18,10 @@ import (
|
|||
"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 {
|
||||
}
|
||||
|
||||
|
@ -229,14 +233,16 @@ func ensureConfigEntryTxn(tx WriteTxn, idx uint64, statusUpdate bool, conf struc
|
|||
return fmt.Errorf("failed configuration lookup: %s", err)
|
||||
}
|
||||
|
||||
var existingConf structs.ConfigEntry
|
||||
raftIndex := conf.GetRaftIndex()
|
||||
if existing != nil {
|
||||
existingIdx := existing.(structs.ConfigEntry).GetRaftIndex()
|
||||
existingConf = existing.(structs.ConfigEntry)
|
||||
existingIdx := existingConf.GetRaftIndex()
|
||||
raftIndex.CreateIndex = existingIdx.CreateIndex
|
||||
|
||||
// Handle optional upsert logic.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +262,7 @@ func ensureConfigEntryTxn(tx WriteTxn, idx uint64, statusUpdate bool, conf struc
|
|||
}
|
||||
raftIndex.ModifyIndex = idx
|
||||
|
||||
err = validateProposedConfigEntryInGraph(tx, q, conf)
|
||||
err = validateProposedConfigEntryInGraph(tx, q, conf, existingConf)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err // Err is already sufficiently decorated.
|
||||
}
|
||||
|
@ -544,7 +550,7 @@ func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry)
|
|||
func validateProposedConfigEntryInGraph(
|
||||
tx ReadTxn,
|
||||
kindName configentry.KindName,
|
||||
newEntry structs.ConfigEntry,
|
||||
newEntry, existingEntry structs.ConfigEntry,
|
||||
) error {
|
||||
switch kindName.Kind {
|
||||
case structs.ProxyDefaults:
|
||||
|
@ -552,7 +558,25 @@ func validateProposedConfigEntryInGraph(
|
|||
if kindName.Name != structs.ProxyConfigGlobal {
|
||||
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:
|
||||
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.ServiceSplitter:
|
||||
case structs.ServiceResolver:
|
||||
|
@ -583,6 +607,54 @@ func validateProposedConfigEntryInGraph(
|
|||
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 {
|
||||
_, entry, err := configEntryTxn(tx, nil, otherKind, kindName.Name, &kindName.EnterpriseMeta)
|
||||
if err != nil {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -125,6 +125,26 @@ type WarningConfigEntry interface {
|
|||
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
|
||||
// across the entire cluster.
|
||||
type ServiceConfigEntry struct {
|
||||
|
@ -133,6 +153,7 @@ type ServiceConfigEntry struct {
|
|||
Protocol string
|
||||
Mode ProxyMode `json:",omitempty"`
|
||||
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
||||
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
|
||||
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
||||
Expose ExposeConfig `json:",omitempty"`
|
||||
ExternalSNI string `json:",omitempty" alias:"external_sni"`
|
||||
|
@ -267,6 +288,10 @@ func (e *ServiceConfigEntry) Validate() error {
|
|||
validationErr = multierror.Append(validationErr, err)
|
||||
}
|
||||
|
||||
if err := e.MutualTLSMode.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validationErr
|
||||
}
|
||||
|
||||
|
@ -372,6 +397,7 @@ type ProxyConfigEntry struct {
|
|||
Config map[string]interface{}
|
||||
Mode ProxyMode `json:",omitempty"`
|
||||
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
||||
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
|
||||
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
||||
Expose ExposeConfig `json:",omitempty"`
|
||||
AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"`
|
||||
|
@ -452,6 +478,10 @@ func (e *ProxyConfigEntry) Validate() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := e.MutualTLSMode.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.validateEnterpriseMeta()
|
||||
}
|
||||
|
||||
|
@ -1157,6 +1187,7 @@ type ServiceConfigResponse struct {
|
|||
MeshGateway MeshGatewayConfig `json:",omitempty"`
|
||||
Expose ExposeConfig `json:",omitempty"`
|
||||
TransparentProxy TransparentProxyConfig `json:",omitempty"`
|
||||
MutualTLSMode MutualTLSMode `json:",omitempty"`
|
||||
Mode ProxyMode `json:",omitempty"`
|
||||
Destination DestinationConfig `json:",omitempty"`
|
||||
AccessLogs AccessLogsConfig `json:",omitempty"`
|
||||
|
|
|
@ -16,6 +16,10 @@ type MeshConfigEntry struct {
|
|||
// when enabled.
|
||||
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"`
|
||||
|
||||
HTTP *MeshHTTPConfig `json:",omitempty"`
|
||||
|
|
|
@ -350,6 +350,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
mesh_gateway {
|
||||
mode = "remote"
|
||||
}
|
||||
mutual_tls_mode = "permissive"
|
||||
`,
|
||||
camel: `
|
||||
Kind = "proxy-defaults"
|
||||
|
@ -369,6 +370,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
MeshGateway {
|
||||
Mode = "remote"
|
||||
}
|
||||
MutualTLSMode = "permissive"
|
||||
`,
|
||||
expect: &ProxyConfigEntry{
|
||||
Kind: "proxy-defaults",
|
||||
|
@ -388,6 +390,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
MeshGateway: MeshGatewayConfig{
|
||||
Mode: MeshGatewayModeRemote,
|
||||
},
|
||||
MutualTLSMode: MutualTLSModePermissive,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -404,6 +407,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
mesh_gateway {
|
||||
mode = "remote"
|
||||
}
|
||||
mutual_tls_mode = "permissive"
|
||||
balance_inbound_connections = "exact_balance"
|
||||
upstream_config {
|
||||
overrides = [
|
||||
|
@ -447,6 +451,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
MeshGateway {
|
||||
Mode = "remote"
|
||||
}
|
||||
MutualTLSMode = "permissive"
|
||||
BalanceInboundConnections = "exact_balance"
|
||||
UpstreamConfig {
|
||||
Overrides = [
|
||||
|
@ -490,6 +495,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
MeshGateway: MeshGatewayConfig{
|
||||
Mode: MeshGatewayModeRemote,
|
||||
},
|
||||
MutualTLSMode: MutualTLSModePermissive,
|
||||
BalanceInboundConnections: "exact_balance",
|
||||
UpstreamConfig: &UpstreamConfiguration{
|
||||
Overrides: []*UpstreamConfig{
|
||||
|
@ -1811,6 +1817,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
transparent_proxy {
|
||||
mesh_destinations_only = true
|
||||
}
|
||||
allow_enabling_permissive_mutual_tls = true
|
||||
tls {
|
||||
incoming {
|
||||
tls_min_version = "TLSv1_1"
|
||||
|
@ -1845,6 +1852,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
TransparentProxy {
|
||||
MeshDestinationsOnly = true
|
||||
}
|
||||
AllowEnablingPermissiveMutualTLS = true
|
||||
TLS {
|
||||
Incoming {
|
||||
TLSMinVersion = "TLSv1_1"
|
||||
|
@ -1878,6 +1886,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
TransparentProxy: TransparentProxyMeshConfig{
|
||||
MeshDestinationsOnly: true,
|
||||
},
|
||||
AllowEnablingPermissiveMutualTLS: true,
|
||||
TLS: &MeshTLSConfig{
|
||||
Incoming: &MeshDirectionalTLSConfig{
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -289,6 +289,9 @@ type ConnectProxyConfig struct {
|
|||
// transparent mode.
|
||||
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 AccessLogsConfig `json:",omitempty" alias:"access_logs"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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.
|
||||
// This include L4 authorization filters and TLS context.
|
||||
func (s *ResourceGenerator) finalizePublicListenerFromConfig(l *envoy_listener_v3.Listener, cfgSnap *proxycfg.ConfigSnapshot, useHTTPFilter bool) error {
|
||||
|
|
|
@ -1099,6 +1099,16 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
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)...)
|
||||
|
|
162
agent/xds/testdata/listeners/connect-proxy-with-tproxy-and-permissive-mtls.latest.golden
vendored
Normal file
162
agent/xds/testdata/listeners/connect-proxy-with-tproxy-and-permissive-mtls.latest.golden
vendored
Normal 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"
|
||||
}
|
|
@ -109,6 +109,21 @@ type TransparentProxyConfig struct {
|
|||
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.
|
||||
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
|
||||
type ExposeConfig struct {
|
||||
|
@ -290,6 +305,7 @@ type ServiceConfigEntry struct {
|
|||
Protocol string `json:",omitempty"`
|
||||
Mode ProxyMode `json:",omitempty"`
|
||||
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
||||
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
|
||||
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
||||
Expose ExposeConfig `json:",omitempty"`
|
||||
ExternalSNI string `json:",omitempty" alias:"external_sni"`
|
||||
|
@ -320,6 +336,7 @@ type ProxyConfigEntry struct {
|
|||
Namespace string `json:",omitempty"`
|
||||
Mode ProxyMode `json:",omitempty"`
|
||||
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
||||
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
|
||||
Config map[string]interface{} `json:",omitempty"`
|
||||
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
||||
Expose ExposeConfig `json:",omitempty"`
|
||||
|
|
|
@ -22,6 +22,10 @@ type MeshConfigEntry struct {
|
|||
// in transparent mode.
|
||||
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"`
|
||||
|
||||
HTTP *MeshHTTPConfig `json:",omitempty"`
|
||||
|
|
|
@ -28,6 +28,7 @@ func TestAPI_ConfigEntries(t *testing.T) {
|
|||
"foo": "bar",
|
||||
"bar": 1.0,
|
||||
},
|
||||
MutualTLSMode: MutualTLSModeStrict,
|
||||
Meta: map[string]string{
|
||||
"foo": "bar",
|
||||
"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.Name, readProxy.Name)
|
||||
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())
|
||||
|
||||
global_proxy.Config["baz"] = true
|
||||
|
@ -103,6 +105,7 @@ func TestAPI_ConfigEntries(t *testing.T) {
|
|||
Kind: ServiceDefaults,
|
||||
Name: "foo",
|
||||
Protocol: "udp",
|
||||
MutualTLSMode: MutualTLSModeStrict,
|
||||
Meta: map[string]string{
|
||||
"foo": "bar",
|
||||
"gir": "zim",
|
||||
|
@ -149,6 +152,7 @@ func TestAPI_ConfigEntries(t *testing.T) {
|
|||
require.Equal(t, service.Kind, readService.Kind)
|
||||
require.Equal(t, service.Name, readService.Name)
|
||||
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.GetMeta())
|
||||
require.Equal(t, service.MaxInboundConnections, readService.MaxInboundConnections)
|
||||
|
@ -220,6 +224,7 @@ func TestAPI_ConfigEntries(t *testing.T) {
|
|||
t.Run("Mesh", func(t *testing.T) {
|
||||
mesh := &MeshConfigEntry{
|
||||
TransparentProxy: TransparentProxyMeshConfig{MeshDestinationsOnly: true},
|
||||
AllowEnablingPermissiveMutualTLS: true,
|
||||
Meta: map[string]string{
|
||||
"foo": "bar",
|
||||
"gir": "zim",
|
||||
|
|
|
@ -1035,6 +1035,7 @@ func MeshConfigToStructs(s *MeshConfig, t *structs.MeshConfigEntry) {
|
|||
if s.TransparentProxy != nil {
|
||||
TransparentProxyMeshConfigToStructs(s.TransparentProxy, &t.TransparentProxy)
|
||||
}
|
||||
t.AllowEnablingPermissiveMutualTLS = s.AllowEnablingPermissiveMutualTLS
|
||||
if s.TLS != nil {
|
||||
var x structs.MeshTLSConfig
|
||||
MeshTLSConfigToStructs(s.TLS, &x)
|
||||
|
@ -1061,6 +1062,7 @@ func MeshConfigFromStructs(t *structs.MeshConfigEntry, s *MeshConfig) {
|
|||
TransparentProxyMeshConfigFromStructs(&t.TransparentProxy, &x)
|
||||
s.TransparentProxy = &x
|
||||
}
|
||||
s.AllowEnablingPermissiveMutualTLS = t.AllowEnablingPermissiveMutualTLS
|
||||
if t.TLS != nil {
|
||||
var x MeshTLSConfig
|
||||
MeshTLSConfigFromStructs(t.TLS, &x)
|
||||
|
@ -1269,6 +1271,7 @@ func ServiceDefaultsToStructs(s *ServiceDefaults, t *structs.ServiceConfigEntry)
|
|||
if s.TransparentProxy != nil {
|
||||
TransparentProxyConfigToStructs(s.TransparentProxy, &t.TransparentProxy)
|
||||
}
|
||||
t.MutualTLSMode = mutualTLSModeToStructs(s.MutualTLSMode)
|
||||
if s.MeshGateway != nil {
|
||||
MeshGatewayConfigToStructs(s.MeshGateway, &t.MeshGateway)
|
||||
}
|
||||
|
@ -1304,6 +1307,7 @@ func ServiceDefaultsFromStructs(t *structs.ServiceConfigEntry, s *ServiceDefault
|
|||
TransparentProxyConfigFromStructs(&t.TransparentProxy, &x)
|
||||
s.TransparentProxy = &x
|
||||
}
|
||||
s.MutualTLSMode = mutualTLSModeFromStructs(t.MutualTLSMode)
|
||||
{
|
||||
var x MeshGatewayConfig
|
||||
MeshGatewayConfigFromStructs(&t.MeshGateway, &x)
|
||||
|
|
|
@ -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 {
|
||||
switch a {
|
||||
case structs.MeshGatewayModeDefault:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -58,6 +58,7 @@ message MeshConfig {
|
|||
MeshHTTPConfig HTTP = 3;
|
||||
map<string, string> Meta = 4;
|
||||
PeeringMeshConfig Peering = 5;
|
||||
bool AllowEnablingPermissiveMutualTLS = 6;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
|
@ -475,6 +476,8 @@ message ServiceDefaults {
|
|||
map<string, string> Meta = 13;
|
||||
// mog: func-to=EnvoyExtensionsToStructs func-from=EnvoyExtensionsFromStructs
|
||||
repeated hashicorp.consul.internal.common.EnvoyExtension EnvoyExtensions = 14;
|
||||
// mog: func-to=mutualTLSModeToStructs func-from=mutualTLSModeFromStructs
|
||||
MutualTLSMode MutualTLSMode = 15;
|
||||
}
|
||||
|
||||
enum ProxyMode {
|
||||
|
@ -483,6 +486,12 @@ enum ProxyMode {
|
|||
ProxyModeDirect = 2;
|
||||
}
|
||||
|
||||
enum MutualTLSMode {
|
||||
MutualTLSModeDefault = 0;
|
||||
MutualTLSModeStrict = 1;
|
||||
MutualTLSModePermissive = 2;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
//
|
||||
// target=github.com/hashicorp/consul/agent/structs.TransparentProxyConfig
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
func TestNewCheckServiceNodeFromStructs_RoundTrip(t *testing.T) {
|
||||
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
|
||||
fuzzer.Fuzz(&target)
|
||||
|
||||
|
@ -77,6 +77,19 @@ func randInt32(i *int, c fuzz.Continue) {
|
|||
*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
|
||||
// for fields enumerated in the ignore-fields annotation.
|
||||
func randStructsUpstream(u *structs.Upstream, c fuzz.Continue) {
|
||||
|
|
|
@ -36,6 +36,7 @@ const (
|
|||
// target=github.com/hashicorp/consul/agent/structs.ConnectProxyConfig
|
||||
// output=service.gen.go
|
||||
// name=Structs
|
||||
// ignore-fields=MutualTLSMode
|
||||
type ConnectProxyConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
|
|
@ -20,6 +20,7 @@ import "private/pbservice/healthcheck.proto";
|
|||
// target=github.com/hashicorp/consul/agent/structs.ConnectProxyConfig
|
||||
// output=service.gen.go
|
||||
// name=Structs
|
||||
// ignore-fields=MutualTLSMode
|
||||
message ConnectProxyConfig {
|
||||
// DestinationServiceName is required and is the name of the service to accept
|
||||
// traffic for.
|
||||
|
|
Loading…
Reference in New Issue