diff --git a/.changelog/2100.txt b/.changelog/2100.txt new file mode 100644 index 0000000000..4fece0991c --- /dev/null +++ b/.changelog/2100.txt @@ -0,0 +1,3 @@ +```release-note:feature +crd: Add `mutualTLSMode` to the ProxyDefaults and ServiceDefaults CRDs and `allowEnablingPermissiveMutualTLS` to the Mesh CRD to support configuring permissive mutual TLS. +``` diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 2e33eb9653..0710d41280 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -55,6 +55,11 @@ spec: spec: description: MeshSpec defines the desired state of Mesh. properties: + allowEnablingPermissiveMutualTLS: + description: AllowEnablingPermissiveMutualTLS must be true in order + to allow setting MutualTLSMode=permissive in either service-defaults + or proxy-defaults. + type: boolean http: description: HTTP defines the HTTP configuration for the service mesh. properties: diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index f72d1c7cea..362672c1c1 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -180,6 +180,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 5c6ecc7476..18b1c913e4 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -171,6 +171,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string protocol: description: Protocol sets the protocol of the service. This is used by Connect proxies for things like observability features and to diff --git a/control-plane/api/v1alpha1/mesh_types.go b/control-plane/api/v1alpha1/mesh_types.go index 9a2df631f2..162132a47a 100644 --- a/control-plane/api/v1alpha1/mesh_types.go +++ b/control-plane/api/v1alpha1/mesh_types.go @@ -51,6 +51,9 @@ type MeshList struct { type MeshSpec struct { // TransparentProxy controls the configuration specific to proxies in "transparent" mode. Added in v1.10.0. TransparentProxy TransparentProxyMeshConfig `json:"transparentProxy,omitempty"` + // AllowEnablingPermissiveMutualTLS must be true in order to allow setting + // MutualTLSMode=permissive in either service-defaults or proxy-defaults. + AllowEnablingPermissiveMutualTLS bool `json:"allowEnablingPermissiveMutualTLS,omitempty"` // TLS defines the TLS configuration for the service mesh. TLS *MeshTLSConfig `json:"tls,omitempty"` // HTTP defines the HTTP configuration for the service mesh. @@ -192,11 +195,12 @@ func (in *Mesh) SetLastSyncedTime(time *metav1.Time) { func (in *Mesh) ToConsul(datacenter string) capi.ConfigEntry { return &capi.MeshConfigEntry{ - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - TLS: in.Spec.TLS.toConsul(), - HTTP: in.Spec.HTTP.toConsul(), - Peering: in.Spec.Peering.toConsul(), - Meta: meta(datacenter), + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + AllowEnablingPermissiveMutualTLS: in.Spec.AllowEnablingPermissiveMutualTLS, + TLS: in.Spec.TLS.toConsul(), + HTTP: in.Spec.HTTP.toConsul(), + Peering: in.Spec.Peering.toConsul(), + Meta: meta(datacenter), } } diff --git a/control-plane/api/v1alpha1/mesh_types_test.go b/control-plane/api/v1alpha1/mesh_types_test.go index e20ce19d47..f2ea714f60 100644 --- a/control-plane/api/v1alpha1/mesh_types_test.go +++ b/control-plane/api/v1alpha1/mesh_types_test.go @@ -48,6 +48,7 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -72,6 +73,7 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -148,6 +150,7 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -172,6 +175,7 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index f83b12ecfe..1100cd107a 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -67,6 +67,17 @@ type ProxyDefaultsSpec struct { // Note: This cannot be set using the CRD and should be set using annotations on the // services that are part of the mesh. TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"` + // MutualTLSMode controls whether mutual TLS is required for all incoming + // connections when transparent proxy is enabled. This can be set to + // "permissive" or "strict". "strict" is the default which requires mutual + // TLS for incoming connections. In the insecure "permissive" mode, + // connections to the sidecar proxy public listener port require mutual + // TLS, but connections to the service port do not require mutual TLS and + // are proxied to the application unmodified. Note: Intentions are not + // enforced for non-mTLS connections. To keep your services secure, we + // recommend using "strict" mode whenever possible and enabling + // "permissive" mode only when necessary. + MutualTLSMode MutualTLSMode `json:"mutualTLSMode,omitempty"` // Config is an arbitrary map of configuration values used by Connect proxies. // Any values that your proxy allows can be configured globally here. // Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting @@ -174,6 +185,7 @@ func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { Expose: in.Spec.Expose.toConsul(), Config: consulConfig, TransparentProxy: in.Spec.TransparentProxy.toConsul(), + MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), AccessLogs: in.Spec.AccessLogs.toConsul(), EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), @@ -201,6 +213,9 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil { allErrs = append(allErrs, err) } + if err := in.Spec.MutualTLSMode.validate(); err != nil { + allErrs = append(allErrs, field.Invalid(path.Child("mutualTLSMode"), in.Spec.MutualTLSMode, err.Error())) + } if err := in.Spec.Mode.validate(path.Child("mode")); err != nil { allErrs = append(allErrs, err) } diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 16642ad277..07f894f322 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -74,6 +74,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModePermissive, AccessLogs: &AccessLogs{ Enabled: true, DisableListenerLogs: true, @@ -129,6 +130,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModePermissive, AccessLogs: &capi.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, @@ -292,6 +294,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModeStrict, AccessLogs: &AccessLogs{ Enabled: true, DisableListenerLogs: true, @@ -348,6 +351,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModeStrict, AccessLogs: &capi.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, @@ -497,6 +501,17 @@ func TestProxyDefaults_Validate(t *testing.T) { }, expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", }, + "mutualTLSMode": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + MutualTLSMode: MutualTLSMode("asdf"), + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.mutualTLSMode: Invalid value: "asdf": Must be one of "", "strict", or "permissive".`, + }, "accessLogs.type": { input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 304bce2db6..425e9b44e5 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -73,6 +73,17 @@ type ServiceDefaultsSpec struct { // Note: This cannot be set using the CRD and should be set using annotations on the // services that are part of the mesh. TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"` + // MutualTLSMode controls whether mutual TLS is required for all incoming + // connections when transparent proxy is enabled. This can be set to + // "permissive" or "strict". "strict" is the default which requires mutual + // TLS for incoming connections. In the insecure "permissive" mode, + // connections to the sidecar proxy public listener port require mutual + // TLS, but connections to the service port do not require mutual TLS and + // are proxied to the application unmodified. Note: Intentions are not + // enforced for non-mTLS connections. To keep your services secure, we + // recommend using "strict" mode whenever possible and enabling + // "permissive" mode only when necessary. + MutualTLSMode MutualTLSMode `json:"mutualTLSMode,omitempty"` // MeshGateway controls the default mesh gateway configuration for this service. MeshGateway MeshGateway `json:"meshGateway,omitempty"` // Expose controls the default expose path configuration for Envoy. @@ -279,6 +290,7 @@ func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry { Expose: in.Spec.Expose.toConsul(), ExternalSNI: in.Spec.ExternalSNI, TransparentProxy: in.Spec.TransparentProxy.toConsul(), + MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), UpstreamConfig: in.Spec.UpstreamConfig.toConsul(), Destination: in.Spec.Destination.toConsul(), Meta: meta(datacenter), @@ -306,6 +318,9 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil { allErrs = append(allErrs, err) } + if err := in.Spec.MutualTLSMode.validate(); err != nil { + allErrs = append(allErrs, field.Invalid(path.Child("mutualTLSMode"), in.Spec.MutualTLSMode, err.Error())) + } if err := in.Spec.Mode.validate(path.Child("mode")); err != nil { allErrs = append(allErrs, err) } diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 69b749decd..62dc6d456f 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -70,6 +70,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModePermissive, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -197,6 +198,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModePermissive, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -367,6 +369,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModeStrict, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -487,6 +490,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModeStrict, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -680,6 +684,7 @@ func TestServiceDefaults_Validate(t *testing.T) { MeshGateway: MeshGateway{ Mode: "remote", }, + MutualTLSMode: MutualTLSModePermissive, Expose: Expose{ Checks: false, Paths: []ExposePath{ @@ -815,6 +820,17 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: "servicedefaults.consul.hashicorp.com \"my-service\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", }, + "mutualTLSMode": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + MutualTLSMode: MutualTLSMode("asdf"), + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.mutualTLSMode: Invalid value: "asdf": Must be one of "", "strict", or "permissive".`, + }, "mode": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 9aa5e519d4..aa19c339da 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -58,6 +58,35 @@ type TransparentProxy struct { DialedDirectly bool `json:"dialedDirectly,omitempty"` } +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" +) + +func (m MutualTLSMode) validate() error { + switch m { + case MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive: + return nil + } + return fmt.Errorf("Must be one of %q, %q, or %q.", + MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive, + ) +} + +func (m MutualTLSMode) toConsul() capi.MutualTLSMode { + return capi.MutualTLSMode(m) +} + // MeshGateway controls how Mesh Gateways are used for upstream Connect // services. type MeshGateway struct { diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml index 4850ad152e..adbb12bba6 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -51,6 +51,11 @@ spec: spec: description: MeshSpec defines the desired state of Mesh. properties: + allowEnablingPermissiveMutualTLS: + description: AllowEnablingPermissiveMutualTLS must be true in order + to allow setting MutualTLSMode=permissive in either service-defaults + or proxy-defaults. + type: boolean http: description: HTTP defines the HTTP configuration for the service mesh. properties: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 86409d2de0..7084980bf0 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -176,6 +176,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 7744a8fe7a..c41840e4ef 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -167,6 +167,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string protocol: description: Protocol sets the protocol of the service. This is used by Connect proxies for things like observability features and to