diff --git a/api/common/v1alpha1/tls/tls.go b/api/common/v1alpha1/tls/tls.go index c30a06347a1d..e42e75b3666e 100644 --- a/api/common/v1alpha1/tls/tls.go +++ b/api/common/v1alpha1/tls/tls.go @@ -4,6 +4,8 @@ package tls import ( "slices" + tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + "github.com/kumahq/kuma/pkg/core/validators" ) @@ -62,3 +64,43 @@ func ValidateVersion(version *Version) validators.ValidationError { return verr } + +func ToTlsVersion(version *TlsVersion) tlsv3.TlsParameters_TlsProtocol { + switch *version { + case TLSVersion13: + return tlsv3.TlsParameters_TLSv1_3 + case TLSVersion12: + return tlsv3.TlsParameters_TLSv1_2 + case TLSVersion11: + return tlsv3.TlsParameters_TLSv1_1 + case TLSVersion10: + return tlsv3.TlsParameters_TLSv1_0 + case TLSVersionAuto: + fallthrough + default: + return tlsv3.TlsParameters_TLS_AUTO + } +} + +// +kubebuilder:validation:Enum=ECDHE-ECDSA-AES128-GCM-SHA256;ECDHE-ECDSA-AES256-GCM-SHA384;ECDHE-ECDSA-CHACHA20-POLY1305;ECDHE-RSA-AES128-GCM-SHA256;ECDHE-RSA-AES256-GCM-SHA384;ECDHE-RSA-CHACHA20-POLY1305 +type TlsCipher string + +const ( + EcdheEcdsaAes128GcmSha256 TlsCipher = "ECDHE-ECDSA-AES128-GCM-SHA256" + EcdheEcdsaAes256GcmSha384 TlsCipher = "ECDHE-ECDSA-AES256-GCM-SHA384" + EcdheEcdsaChacha20Poly1305 TlsCipher = "ECDHE-ECDSA-CHACHA20-POLY1305" + EcdheRsaAes128GcmSha256 TlsCipher = "ECDHE-RSA-AES128-GCM-SHA256" + EcdheRsaAes256GcmSha384 TlsCipher = "ECDHE-RSA-AES256-GCM-SHA384" + EcdheRsaChacha20Poly1305 TlsCipher = "ECDHE-RSA-CHACHA20-POLY1305" +) + +var AllCiphers = []string{ + string(EcdheEcdsaAes128GcmSha256), + string(EcdheEcdsaAes256GcmSha384), + string(EcdheEcdsaChacha20Poly1305), + string(EcdheRsaAes128GcmSha256), + string(EcdheRsaAes256GcmSha384), + string(EcdheRsaChacha20Poly1305), +} + +type TlsCiphers []TlsCipher diff --git a/api/common/v1alpha1/tls/zz_generated.deepcopy.go b/api/common/v1alpha1/tls/zz_generated.deepcopy.go index 70587138ad24..7f73f6c5ddd6 100644 --- a/api/common/v1alpha1/tls/zz_generated.deepcopy.go +++ b/api/common/v1alpha1/tls/zz_generated.deepcopy.go @@ -6,6 +6,25 @@ package tls import () +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in TlsCiphers) DeepCopyInto(out *TlsCiphers) { + { + in := &in + *out = make(TlsCiphers, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TlsCiphers. +func (in TlsCiphers) DeepCopy() TlsCiphers { + if in == nil { + return nil + } + out := new(TlsCiphers) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Version) DeepCopyInto(out *Version) { *out = *in diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml index 31568514d32c..9ce974c987b3 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml @@ -6614,8 +6614,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml index bd82c8c58112..257835b7df8a 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml @@ -6614,8 +6614,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml index c8ce4018caa0..b94bff7b3164 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml @@ -6634,8 +6634,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml index f208f4b79398..dece382134c0 100644 --- a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml @@ -8104,8 +8104,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml b/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml index e70ea28eff5e..85dfab3539ae 100644 --- a/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml +++ b/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml @@ -58,8 +58,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/docs/generated/openapi.yaml b/docs/generated/openapi.yaml index 3eeb69d21798..b31025b536c3 100644 --- a/docs/generated/openapi.yaml +++ b/docs/generated/openapi.yaml @@ -10298,6 +10298,8 @@ components: description: >- Mode defines the behavior of inbound listeners with regard to traffic encryption. + + Default: Strict. enum: - Permissive - Strict diff --git a/docs/generated/raw/crds/kuma.io_meshtlses.yaml b/docs/generated/raw/crds/kuma.io_meshtlses.yaml index e70ea28eff5e..85dfab3539ae 100644 --- a/docs/generated/raw/crds/kuma.io_meshtlses.yaml +++ b/docs/generated/raw/crds/kuma.io_meshtlses.yaml @@ -58,8 +58,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/pkg/core/xds/types.go b/pkg/core/xds/types.go index 2935680a6590..f3ad65a5fdda 100644 --- a/pkg/core/xds/types.go +++ b/pkg/core/xds/types.go @@ -5,6 +5,7 @@ import ( "slices" "strings" + tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/pkg/errors" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" @@ -53,34 +54,25 @@ type TagSelectorSet []mesh_proto.TagSelector // DestinationMap holds a set of selectors for all reachable Dataplanes grouped by service name. // DestinationMap is based on ServiceName and not on the OutboundInterface because TrafficRoute can introduce new service destinations that were not included in a outbound section. // Policies that match on outbound connections also match by service destination name and not outbound interface for the same reason. -type DestinationMap map[ServiceName]TagSelectorSet - -type TlsVersion int32 - -const ( - TLSVersionAuto TlsVersion = 0 - TLSVersion10 TlsVersion = 1 - TLSVersion11 TlsVersion = 2 - TLSVersion12 TlsVersion = 3 - TLSVersion13 TlsVersion = 4 +type ( + DestinationMap map[ServiceName]TagSelectorSet + ExternalService struct { + Protocol core_mesh.Protocol + TLSEnabled bool + FallbackToSystemCa bool + CaCert []byte + ClientCert []byte + ClientKey []byte + AllowRenegotiation bool + SkipHostnameVerification bool + ServerName string + SANs []SAN + MinTlsVersion *tlsv3.TlsParameters_TlsProtocol + MaxTlsVersion *tlsv3.TlsParameters_TlsProtocol + OwnerResource *core_model.TypedResourceIdentifier + } ) -type ExternalService struct { - Protocol core_mesh.Protocol - TLSEnabled bool - FallbackToSystemCa bool - CaCert []byte - ClientCert []byte - ClientKey []byte - AllowRenegotiation bool - SkipHostnameVerification bool - ServerName string - SANs []SAN - MinTlsVersion *TlsVersion - MaxTlsVersion *TlsVersion - OwnerResource *core_model.TypedResourceIdentifier -} - type MatchType string const ( diff --git a/pkg/plugins/policies/core/ordered/ordered.go b/pkg/plugins/policies/core/ordered/ordered.go index 2b1eb91eb173..b01e693b7528 100644 --- a/pkg/plugins/policies/core/ordered/ordered.go +++ b/pkg/plugins/policies/core/ordered/ordered.go @@ -24,6 +24,8 @@ var Policies = []plugins.PluginName{ // Routes have to come first plugins.PluginName(meshhttproute_api.MeshHTTPRouteResourceTypeDescriptor.KumactlArg), plugins.PluginName(meshtcproute_api.MeshTCPRouteResourceTypeDescriptor.KumactlArg), + // MeshTLS needs to come before everything because it rebuilds the inbound listeners + plugins.PluginName(meshtls_api.MeshTLSResourceTypeDescriptor.KumactlArg), // For other policies order isn't important at the moment plugins.PluginName(meshloadbalancingstrategy_api.MeshLoadBalancingStrategyResourceTypeDescriptor.KumactlArg), // has to be before MeshAccessLog so the plugin can access log filters that are added to the filter chains @@ -38,7 +40,6 @@ var Policies = []plugins.PluginName{ plugins.PluginName(meshhealthcheck_api.MeshHealthCheckResourceTypeDescriptor.KumactlArg), plugins.PluginName(meshretry_api.MeshRetryResourceTypeDescriptor.KumactlArg), plugins.PluginName(meshmetric_api.MeshMetricResourceTypeDescriptor.KumactlArg), - plugins.PluginName(meshtls_api.MeshTLSResourceTypeDescriptor.KumactlArg), // MeshProxyPatch comes after all others plugins.PluginName(meshproxypatch_api.MeshProxyPatchResourceTypeDescriptor.KumactlArg), } diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go b/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go index 697ec4746372..5f8529f54d7a 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go @@ -27,29 +27,6 @@ type From struct { Default Conf `json:"default,omitempty"` } -// +kubebuilder:validation:Enum=ECDHE-ECDSA-AES128-GCM-SHA256;ECDHE-ECDSA-AES256-GCM-SHA384;ECDHE-ECDSA-CHACHA20-POLY1305;ECDHE-RSA-AES128-GCM-SHA256;ECDHE-RSA-AES256-GCM-SHA384;ECDHE-RSA-CHACHA20-POLY1305 -type TlsCipher string - -const ( - EcdheEcdsaAes128GcmSha256 TlsCipher = "ECDHE-ECDSA-AES128-GCM-SHA256" - EcdheEcdsaAes256GcmSha384 TlsCipher = "ECDHE-ECDSA-AES256-GCM-SHA384" - EcdheEcdsaChacha20Poly1305 TlsCipher = "ECDHE-ECDSA-CHACHA20-POLY1305" - EcdheRsaAes128GcmSha256 TlsCipher = "ECDHE-RSA-AES128-GCM-SHA256" - EcdheRsaAes256GcmSha384 TlsCipher = "ECDHE-RSA-AES256-GCM-SHA384" - EcdheRsaChacha20Poly1305 TlsCipher = "ECDHE-RSA-CHACHA20-POLY1305" -) - -var allCiphers = []string{ - string(EcdheEcdsaAes128GcmSha256), - string(EcdheEcdsaAes256GcmSha384), - string(EcdheEcdsaChacha20Poly1305), - string(EcdheRsaAes128GcmSha256), - string(EcdheRsaAes256GcmSha384), - string(EcdheRsaChacha20Poly1305), -} - -type TlsCiphers []TlsCipher - // +kubebuilder:validation:Enum=Permissive;Strict type Mode string @@ -65,8 +42,9 @@ type Conf struct { TlsVersion *common_tls.Version `json:"tlsVersion,omitempty"` // TlsCiphers section for providing ciphers specification. - TlsCiphers TlsCiphers `json:"tlsCiphers,omitempty"` + TlsCiphers common_tls.TlsCiphers `json:"tlsCiphers,omitempty"` // Mode defines the behavior of inbound listeners with regard to traffic encryption. + // Default: Strict. Mode *Mode `json:"mode,omitempty"` } diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml index f60addfabeb3..dd47d137384a 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml @@ -31,7 +31,9 @@ properties: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml index 82bf36e08cec..c419087a9384 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml @@ -1,7 +1,9 @@ targetRef: - kind: MeshService - name: svc-1 + kind: Mesh from: + - targetRef: + kind: MeshHTTPRoute + default: {} - targetRef: kind: Mesh default: diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml index cb247809e1b9..4200d035b208 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml @@ -1,11 +1,13 @@ violations: -- field: spec.from[0].default.mode +- field: spec.from[0].targetRef.kind + message: value is not supported +- field: spec.from[1].default.mode message: '"mode" must be one of ["Strict", "Permissive"]' -- field: spec.from[0].default.tlsCiphers +- field: spec.from[1].default.tlsCiphers message: '"tlsCiphers" must be one of ["ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES256-GCM-SHA384", "ECDHE-ECDSA-CHACHA20-POLY1305", "ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-RSA-CHACHA20-POLY1305"]' -- field: spec.from[0].default.version.min +- field: spec.from[1].default.tlsVersion.min message: '"min" must be one of ["TLSAuto", "TLS10", "TLS11", "TLS12", "TLS13"]' -- field: spec.from[0].default.version.max +- field: spec.from[1].default.tlsVersion.max message: '"max" must be one of ["TLSAuto", "TLS10", "TLS11", "TLS12", "TLS13"]' diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml index 302b9eb7730a..d8df82277292 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml @@ -1,6 +1,5 @@ targetRef: - kind: MeshService - name: svc-1 + kind: Mesh from: - targetRef: kind: Mesh diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.input.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.input.yaml new file mode 100644 index 000000000000..bfbdf01fe1c9 --- /dev/null +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.input.yaml @@ -0,0 +1,14 @@ +targetRef: + kind: MeshSubset + tags: + kuma.io/service: svc-1 +from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS15 + max: TLS16 + tlsCiphers: + - "NotExistingCipher" + mode: Strict diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.output.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.output.yaml new file mode 100644 index 000000000000..c2a5dfb10075 --- /dev/null +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.output.yaml @@ -0,0 +1,5 @@ +violations: +- field: spec.from[0].default.tlsCiphers + message: 'tlsCiphers can only be defined with top level targetRef kind: Mesh' +- field: spec.from[0].default.tlsVersion + message: 'tlsVersion can only be defined with top level targetRef kind: Mesh' diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go b/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go index 85f1aa419c8a..c6b0fdcb076c 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go @@ -13,7 +13,7 @@ func (r *MeshTLSResource) validate() error { var verr validators.ValidationError path := validators.RootedAt("spec") verr.AddErrorAt(path.Field("targetRef"), validateTop(r.Spec.TargetRef)) - verr.AddErrorAt(path.Field("from"), validateFrom(r.Spec.From)) + verr.AddErrorAt(path.Field("from"), validateFrom(r.Spec.From, r.Spec.TargetRef.Kind)) return verr.OrNil() } @@ -22,27 +22,30 @@ func validateTop(targetRef common_api.TargetRef) validators.ValidationError { SupportedKinds: []common_api.TargetRefKind{ common_api.Mesh, common_api.MeshSubset, - common_api.MeshService, - common_api.MeshServiceSubset, }, }) return targetRefErr } -func validateFrom(from []From) validators.ValidationError { +func validateFrom(from []From, topLevelTargetRef common_api.TargetRefKind) validators.ValidationError { var verr validators.ValidationError fromPath := validators.Root() for idx, fromItem := range from { path := fromPath.Index(idx) - defaultField := path.Field("default") - verr.Add(validateDefault(defaultField, fromItem.Default)) + targetRefErr := mesh.ValidateTargetRef(fromItem.TargetRef, &mesh.ValidateTargetRefOpts{ + SupportedKinds: []common_api.TargetRefKind{ + common_api.Mesh, + }, + }) + verr.AddErrorAt(path.Field("targetRef"), targetRefErr) + verr.Add(validateDefault(path.Field("default"), fromItem.Default, topLevelTargetRef)) } return verr } -func validateDefault(path validators.PathBuilder, conf Conf) validators.ValidationError { +func validateDefault(path validators.PathBuilder, conf Conf, topLevelTargetRef common_api.TargetRefKind) validators.ValidationError { var verr validators.ValidationError if conf.Mode != nil { @@ -51,18 +54,24 @@ func validateDefault(path validators.PathBuilder, conf Conf) validators.Validati } } - if !containsAll(allCiphers, conf.TlsCiphers) { - verr.AddErrorAt(path.Field("tlsCiphers"), validators.MakeFieldMustBeOneOfErr("tlsCiphers", allCiphers...)) + if len(conf.TlsCiphers) > 0 && topLevelTargetRef != common_api.Mesh { + verr.AddViolationAt(path.Field("tlsCiphers"), "tlsCiphers can only be defined with top level targetRef kind: Mesh") + } else if !containsAll(common_tls.AllCiphers, conf.TlsCiphers) { + verr.AddErrorAt(path.Field("tlsCiphers"), validators.MakeFieldMustBeOneOfErr("tlsCiphers", common_tls.AllCiphers...)) } if conf.TlsVersion != nil { - verr.AddErrorAt(path.Field("version"), common_tls.ValidateVersion(conf.TlsVersion)) + if topLevelTargetRef != common_api.Mesh { + verr.AddViolationAt(path.Field("tlsVersion"), "tlsVersion can only be defined with top level targetRef kind: Mesh") + } else { + verr.AddErrorAt(path.Field("tlsVersion"), common_tls.ValidateVersion(conf.TlsVersion)) + } } return verr } -func containsAll(main []string, sub TlsCiphers) bool { +func containsAll(main []string, sub common_tls.TlsCiphers) bool { elementMap := make(map[string]bool) for _, element := range main { diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go b/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go index 5dd69895ea4c..de3f8f530630 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go @@ -56,6 +56,10 @@ var _ = Describe("MeshTLS", func() { name: "meshtls-2", file: "full-invalid", }), + Entry("invalid top level", testCase{ + name: "meshtls-3", + file: "invalid-top-level", + }), ) }) }) diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go b/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go index be03ec4afc16..1ca23ceb47b5 100644 --- a/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go @@ -18,7 +18,7 @@ func (in *Conf) DeepCopyInto(out *Conf) { } if in.TlsCiphers != nil { in, out := &in.TlsCiphers, &out.TlsCiphers - *out = make(TlsCiphers, len(*in)) + *out = make(tls.TlsCiphers, len(*in)) copy(*out, *in) } if in.Mode != nil { @@ -77,22 +77,3 @@ func (in *MeshTLS) DeepCopy() *MeshTLS { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in TlsCiphers) DeepCopyInto(out *TlsCiphers) { - { - in := &in - *out = make(TlsCiphers, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TlsCiphers. -func (in TlsCiphers) DeepCopy() TlsCiphers { - if in == nil { - return nil - } - out := new(TlsCiphers) - in.DeepCopyInto(out) - return *out -} diff --git a/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml b/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml index e70ea28eff5e..85dfab3539ae 100644 --- a/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml +++ b/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml @@ -58,8 +58,9 @@ spec: 'targetRef' properties: mode: - description: Mode defines the behavior of inbound listeners - with regard to traffic encryption. + description: |- + Mode defines the behavior of inbound listeners with regard to traffic encryption. + Default: Strict. enum: - Permissive - Strict diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go index 816027dad381..0abdba342aa6 100644 --- a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go @@ -1,13 +1,32 @@ package v1alpha1 import ( + envoy_cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + envoy_tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + + common_tls "github.com/kumahq/kuma/api/common/v1alpha1/tls" + mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" "github.com/kumahq/kuma/pkg/core" core_plugins "github.com/kumahq/kuma/pkg/core/plugins" core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" core_xds "github.com/kumahq/kuma/pkg/core/xds" + xds_types "github.com/kumahq/kuma/pkg/core/xds/types" "github.com/kumahq/kuma/pkg/plugins/policies/core/matchers" + core_rules "github.com/kumahq/kuma/pkg/plugins/policies/core/rules" + policies_xds "github.com/kumahq/kuma/pkg/plugins/policies/core/xds" api "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/api/v1alpha1" + "github.com/kumahq/kuma/pkg/util/pointer" + "github.com/kumahq/kuma/pkg/util/proto" xds_context "github.com/kumahq/kuma/pkg/xds/context" + envoy_common "github.com/kumahq/kuma/pkg/xds/envoy" + envoy_listeners "github.com/kumahq/kuma/pkg/xds/envoy/listeners" + envoy_names "github.com/kumahq/kuma/pkg/xds/envoy/names" + xds_tls "github.com/kumahq/kuma/pkg/xds/envoy/tls" + "github.com/kumahq/kuma/pkg/xds/generator" ) var ( @@ -26,6 +45,207 @@ func (p plugin) MatchedPolicies(dataplane *core_mesh.DataplaneResource, resource } func (p plugin) Apply(rs *core_xds.ResourceSet, ctx xds_context.Context, proxy *core_xds.Proxy) error { - log.V(1).Info("apply not implemented") + if proxy.Dataplane == nil { + return nil + } + log.V(1).Info("applying", "proxy-name", proxy.Dataplane.GetMeta().GetName()) + policies, ok := proxy.Policies.Dynamic[api.MeshTLSType] + if !ok { + return nil + } + listeners := policies_xds.GatherListeners(rs) + clusters := policies_xds.GatherClusters(rs) + + if err := applyToInbounds(rs, policies.FromRules, listeners.Inbound, proxy, ctx); err != nil { + return err + } + if err := applyToOutbounds(policies.FromRules, clusters.Outbound, clusters.OutboundSplit, proxy.Outbounds, ctx); err != nil { + return err + } + + return nil +} + +func applyToInbounds( + rs *core_xds.ResourceSet, + fromRules core_rules.FromRules, + inboundListeners map[core_rules.InboundListener]*envoy_listener.Listener, + proxy *core_xds.Proxy, + ctx xds_context.Context, +) error { + for _, inbound := range proxy.Dataplane.Spec.GetNetworking().GetInbound() { + iface := proxy.Dataplane.Spec.Networking.ToInboundInterface(inbound) + + listenerKey := core_rules.InboundListener{ + Address: iface.DataplaneIP, + Port: iface.DataplanePort, + } + listener, ok := inboundListeners[listenerKey] + if !ok { + continue + } + conf := core_rules.ComputeConf[api.Conf](fromRules.Rules[listenerKey], core_rules.MeshSubset()) + if conf == nil { + continue + } + l, err := configure(proxy, listener, iface, inbound, conf, ctx) + if err != nil { + return err + } + if l != nil { + rs.Remove(resource.ListenerType, listener.GetName()) + rs.Add(&core_xds.Resource{ + Name: listener.GetName(), + Origin: generator.OriginInbound, + Resource: l, + }) + } + } + return nil +} + +func applyToOutbounds( + fromRules core_rules.FromRules, + outboundClusters map[string]*envoy_cluster.Cluster, + outboundSplitClusters map[string][]*envoy_cluster.Cluster, + outbounds xds_types.Outbounds, + ctx xds_context.Context, +) error { + targetedClusters := policies_xds.GatherTargetedClusters( + outbounds.Filter(xds_types.NonBackendRefFilter), + outboundSplitClusters, + outboundClusters, + ) + for cluster, serviceName := range targetedClusters { + // we shouldn't modify ExternalService + // MeshExternalService has different origin + if ctx.Mesh.IsExternalService(serviceName) { + continue + } + // there is only one rule always because we're in `Mesh/Mesh` + var conf *api.Conf + for _, r := range fromRules.Rules { + conf = core_rules.ComputeConf[api.Conf](r, core_rules.MeshSubset()) + break + } + if conf == nil { + continue + } + if err := configureParams(conf, cluster); err != nil { + return err + } + } + + return nil +} + +func configureParams(conf *api.Conf, cluster *envoy_cluster.Cluster) error { + if cluster.TransportSocket.GetName() != wellknown.TransportSocketTLS { + // we only want to configure TLS Version on listeners protected by Kuma's TLS + return nil + } + + tlsContext := &envoy_tls.UpstreamTlsContext{CommonTlsContext: &envoy_tls.CommonTlsContext{}} + + version := conf.TlsVersion + if version != nil { + if version.Min != nil { + tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{ + TlsMinimumProtocolVersion: common_tls.ToTlsVersion(version.Min), + } + } + if version.Max != nil { + if tlsContext.CommonTlsContext.TlsParams == nil { + tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{ + TlsMaximumProtocolVersion: common_tls.ToTlsVersion(version.Max), + } + } else { + tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = common_tls.ToTlsVersion(version.Max) + } + } + } + + ciphers := conf.TlsCiphers + if ciphers != nil { + if tlsContext.CommonTlsContext.TlsParams != nil { + var cipherSuites []string + for _, c := range ciphers { + cipherSuites = append(cipherSuites, string(c)) + } + tlsContext.CommonTlsContext.TlsParams.CipherSuites = cipherSuites + } + } + + log.V(1).Info("computed outbound tlsContext", "tlsContext", tlsContext) + + dst := envoy_tls.UpstreamTlsContext{} + err := proto.UnmarshalAnyTo(cluster.TransportSocket.GetTypedConfig(), &dst) + if err != nil { + return err + } + + // this relies on nothing before it modifying TlsParams + dst.CommonTlsContext.TlsParams = tlsContext.CommonTlsContext.TlsParams + pbst, err := proto.MarshalAnyDeterministic(&dst) + if err != nil { + return err + } + cluster.TransportSocket = &envoy_core.TransportSocket{ + Name: "envoy.transport_sockets.tls", + ConfigType: &envoy_core.TransportSocket_TypedConfig{ + TypedConfig: pbst, + }, + } + return nil } + +func configure( + proxy *core_xds.Proxy, + listener *envoy_listener.Listener, + iface mesh_proto.InboundInterface, + inbound *mesh_proto.Dataplane_Networking_Inbound, + conf *api.Conf, + xdsCtx xds_context.Context, +) (envoy_common.NamedResource, error) { + mesh := xdsCtx.Mesh.Resource + // Default Strict + mode := pointer.DerefOr(conf.Mode, api.ModeStrict) + protocol := core_mesh.ParseProtocol(inbound.GetProtocol()) + localClusterName := envoy_names.GetLocalClusterName(iface.WorkloadPort) + cluster := envoy_common.NewCluster(envoy_common.WithService(localClusterName)) + service := inbound.GetService() + routes := generator.GenerateRoutes(proxy, iface, cluster) + listenerBuilder := envoy_listeners.NewInboundListenerBuilder(proxy.APIVersion, iface.DataplaneIP, iface.DataplanePort, core_xds.SocketAddressProtocolTCP). + Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())). + Configure(envoy_listeners.TagsMetadata(inbound.GetTags())) + + switch mode { + case api.ModeStrict: + listenerBuilder. + Configure(envoy_listeners.FilterChain(generator.FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure( + envoy_listeners.NetworkRBAC(listener.GetName(), mesh.MTLSEnabled(), proxy.Policies.TrafficPermissions[iface]), + ))) + case api.ModePermissive: + listenerBuilder. + Configure(envoy_listeners.TLSInspector()). + Configure(envoy_listeners.FilterChain( + generator.FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure( + envoy_listeners.MatchTransportProtocol("raw_buffer"))), + ). + Configure(envoy_listeners.FilterChain( + // we need to differentiate between just TLS and Kuma's TLS, because with permissive mode + // the app itself might be protected by TLS. + generator.FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure( + envoy_listeners.MatchTransportProtocol("tls"))), + ). + Configure(envoy_listeners.FilterChain( + generator.FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure( + envoy_listeners.MatchTransportProtocol("tls"), + envoy_listeners.MatchApplicationProtocols(xds_tls.KumaALPNProtocols...), + envoy_listeners.NetworkRBAC(listener.GetName(), xdsCtx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[iface]), + )), + ) + } + return listenerBuilder.Build() +} diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_suite_test.go b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_suite_test.go new file mode 100644 index 000000000000..2c5db9fae3ac --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_suite_test.go @@ -0,0 +1,11 @@ +package v1alpha1_test + +import ( + "testing" + + "github.com/kumahq/kuma/pkg/test" +) + +func TestPlugin(t *testing.T) { + test.RunSpecs(t, "MeshTLS") +} diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_test.go new file mode 100644 index 000000000000..e3ed14089454 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_test.go @@ -0,0 +1,213 @@ +package v1alpha1_test + +import ( + "fmt" + "os" + "path" + + envoy_resource "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + core_plugins "github.com/kumahq/kuma/pkg/core/plugins" + core_model "github.com/kumahq/kuma/pkg/core/resources/model" + core_xds "github.com/kumahq/kuma/pkg/core/xds" + xds_types "github.com/kumahq/kuma/pkg/core/xds/types" + core_rules "github.com/kumahq/kuma/pkg/plugins/policies/core/rules" + api "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/api/v1alpha1" + plugin "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/plugin/v1alpha1" + "github.com/kumahq/kuma/pkg/test/matchers" + "github.com/kumahq/kuma/pkg/test/resources/builders" + test_model "github.com/kumahq/kuma/pkg/test/resources/model" + "github.com/kumahq/kuma/pkg/test/resources/samples" + xds_builders "github.com/kumahq/kuma/pkg/test/xds/builders" + util_proto "github.com/kumahq/kuma/pkg/util/proto" + envoy_common "github.com/kumahq/kuma/pkg/xds/envoy" + "github.com/kumahq/kuma/pkg/xds/envoy/clusters" + "github.com/kumahq/kuma/pkg/xds/envoy/listeners" + "github.com/kumahq/kuma/pkg/xds/generator" +) + +func getResource( + resourceSet *core_xds.ResourceSet, + typ envoy_resource.Type, +) []byte { + resources, err := resourceSet.ListOf(typ).ToDeltaDiscoveryResponse() + Expect(err).ToNot(HaveOccurred()) + actual, err := util_proto.ToYAML(resources) + Expect(err).ToNot(HaveOccurred()) + + return actual +} + +var _ = Describe("MeshTLS", func() { + type testCase struct { + caseName string + meshBuilder *builders.MeshBuilder + } + DescribeTable("should generate proper Envoy config", + func(given testCase) { + // given + mesh := given.meshBuilder + context := *xds_builders.Context(). + WithMeshBuilder(mesh). + Build() + resourceSet := core_xds.NewResourceSet() + secretsTracker := envoy_common.NewSecretsTracker("default", nil) + resources := getResources(secretsTracker, mesh) + resourceSet.Add(resources...) + + policy := getPolicy(given.caseName) + + proxy := xds_builders.Proxy(). + WithSecretsTracker(secretsTracker). + WithApiVersion(envoy_common.APIV3). + WithOutbounds(xds_types.Outbounds{&xds_types.Outbound{ + LegacyOutbound: builders.Outbound(). + WithService("outgoing"). + WithAddress("127.0.0.1"). + WithPort(27777).Build(), + }}). + WithDataplane( + builders.Dataplane(). + WithName("test"). + WithMesh("default"). + WithAddress("127.0.0.1"). + WithTransparentProxying(15006, 15001, "ipv4"). + AddOutbound( + builders.Outbound(). + WithAddress("127.0.0.1"). + WithPort(27777). + WithService("outgoing"), + ). + AddInbound( + builders.Inbound(). + WithAddress("127.0.0.1"). + WithPort(17777). + WithService("backend"), + ). + AddInbound( + builders.Inbound(). + WithAddress("127.0.0.1"). + WithPort(17778). + WithService("frontend"), + ), + ). + WithPolicies(xds_builders.MatchedPolicies().WithFromPolicy(api.MeshTLSType, getFromRules(policy.Spec.From))). + Build() + + plugin := plugin.NewPlugin().(core_plugins.PolicyPlugin) + + // when + Expect(plugin.Apply(resourceSet, context, proxy)).To(Succeed()) + + // then + Expect(getResource(resourceSet, envoy_resource.ListenerType)). + To(matchers.MatchGoldenYAML(fmt.Sprintf("testdata/%s.listeners.golden.yaml", given.caseName))) + Expect(getResource(resourceSet, envoy_resource.ClusterType)). + To(matchers.MatchGoldenYAML(fmt.Sprintf("testdata/%s.clusters.golden.yaml", given.caseName))) + }, + Entry("strict with no mTLS on the mesh", testCase{ + caseName: "strict-no-mtls", + meshBuilder: samples.MeshDefaultBuilder(), + }), + Entry("permissive with no mTLS on the mesh", testCase{ + caseName: "permissive-no-mtls", + meshBuilder: samples.MeshDefaultBuilder(), + }), + Entry("strict with permissive mTLS on the mesh", testCase{ + caseName: "strict-with-permissive-mtls", + meshBuilder: samples.MeshMTLSBuilder().WithPermissiveMTLSBackends(), + }), + Entry("permissive with permissive mTLS on the mesh", testCase{ + caseName: "permissive-with-permissive-mtls", + meshBuilder: samples.MeshMTLSBuilder().WithPermissiveMTLSBackends(), + }), + ) +}) + +func getResources(secretsTracker core_xds.SecretsTracker, mesh *builders.MeshBuilder) []*core_xds.Resource { + return []*core_xds.Resource{ + { + Name: "inbound:127.0.0.1:17777", + Origin: generator.OriginInbound, + Resource: listeners.NewInboundListenerBuilder(envoy_common.APIV3, "127.0.0.1", 17777, core_xds.SocketAddressProtocolTCP). + Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy_common.APIV3, envoy_common.AnonymousResource). + Configure(listeners.HttpConnectionManager("127.0.0.1:17777", false)). + Configure( + listeners.HttpInboundRoutes( + "backend", + envoy_common.Routes{ + { + Clusters: []envoy_common.Cluster{envoy_common.NewCluster( + envoy_common.WithService("backend"), + envoy_common.WithWeight(100), + )}, + }, + }, + ), + ), + )).MustBuild(), + }, + { + Name: "inbound:127.0.0.1:17778", + Origin: generator.OriginInbound, + Resource: listeners.NewInboundListenerBuilder(envoy_common.APIV3, "127.0.0.1", 17778, core_xds.SocketAddressProtocolTCP). + Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy_common.APIV3, envoy_common.AnonymousResource). + Configure(listeners.TcpProxyDeprecated("127.0.0.1:17778", envoy_common.NewCluster(envoy_common.WithName("frontend")))), + )).MustBuild(), + }, + { + Name: "outgoing", + Origin: generator.OriginOutbound, + Resource: clusters.NewClusterBuilder(envoy_common.APIV3, "outgoing"). + Configure(clusters.ClientSideMTLS(secretsTracker, mesh.Build(), "outgoing", true, nil)). + MustBuild(), + }, + } +} + +func getPolicy(caseName string) *api.MeshTLSResource { + // setup + meshTLS := api.NewMeshTLSResource() + + // when + contents, err := os.ReadFile(path.Join("testdata", caseName+".policy.yaml")) + Expect(err).ToNot(HaveOccurred()) + err = core_model.FromYAML(contents, &meshTLS.Spec) + Expect(err).ToNot(HaveOccurred()) + + meshTLS.SetMeta(&test_model.ResourceMeta{ + Name: "name", + Mesh: core_model.DefaultMesh, + }) + // and + verr := meshTLS.Validate() + Expect(verr).ToNot(HaveOccurred()) + + return meshTLS +} + +func getFromRules(froms []api.From) core_rules.FromRules { + var rules []*core_rules.Rule + + for _, from := range froms { + rules = append(rules, &core_rules.Rule{ + Subset: core_rules.Subset{}, + Conf: from.Default, + }) + } + + return core_rules.FromRules{ + Rules: map[core_rules.InboundListener]core_rules.Rules{ + { + Address: "127.0.0.1", + Port: 17777, + }: rules, + { + Address: "127.0.0.1", + Port: 17778, + }: rules, + }, + } +} diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.clusters.golden.yaml new file mode 100644 index 000000000000..294c7ad43eb5 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.clusters.golden.yaml @@ -0,0 +1,5 @@ +resources: +- name: outgoing + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + name: outgoing diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.listeners.golden.yaml new file mode 100644 index 000000000000..fc92cdf5f85f --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.listeners.golden.yaml @@ -0,0 +1,99 @@ +resources: +- name: inbound:127.0.0.1:17777 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17777 + bindToPort: false + enableReusePort: false + filterChains: + - filterChainMatch: + transportProtocol: raw_buffer + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + - filterChainMatch: + transportProtocol: tls + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + - filterChainMatch: + applicationProtocols: + - kuma + transportProtocol: tls + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend + name: inbound:127.0.0.1:17777 + trafficDirection: INBOUND +- name: inbound:127.0.0.1:17778 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17778 + bindToPort: false + enableReusePort: false + filterChains: + - filterChainMatch: + transportProtocol: raw_buffer + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + - filterChainMatch: + transportProtocol: tls + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + - filterChainMatch: + applicationProtocols: + - kuma + transportProtocol: tls + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: frontend + name: inbound:127.0.0.1:17778 + trafficDirection: INBOUND diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.policy.yaml new file mode 100644 index 000000000000..cb57bb58a742 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.policy.yaml @@ -0,0 +1,12 @@ +targetRef: + kind: Mesh +from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS12 + max: TLS13 + tlsCiphers: + - "ECDHE-ECDSA-AES128-GCM-SHA256" + mode: Permissive diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.clusters.golden.yaml new file mode 100644 index 000000000000..b9150d0172a1 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.clusters.golden.yaml @@ -0,0 +1,33 @@ +resources: +- name: outgoing + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + name: outgoing + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + alpnProtocols: + - kuma + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + exact: spiffe://default/outgoing + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsParams: + cipherSuites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + tlsMaximumProtocolVersion: TLSv1_3 + tlsMinimumProtocolVersion: TLSv1_2 diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.listeners.golden.yaml new file mode 100644 index 000000000000..eb66ed92f7d8 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.listeners.golden.yaml @@ -0,0 +1,163 @@ +resources: +- name: inbound:127.0.0.1:17777 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17777 + bindToPort: false + enableReusePort: false + filterChains: + - filterChainMatch: + transportProtocol: raw_buffer + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + - filterChainMatch: + transportProtocol: tls + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + - filterChainMatch: + applicationProtocols: + - kuma + transportProtocol: tls + filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_127_0_0_1_17777. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsParams: + cipherSuites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + tlsMaximumProtocolVersion: TLSv1_3 + tlsMinimumProtocolVersion: TLSv1_2 + requireClientCertificate: true + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend + name: inbound:127.0.0.1:17777 + trafficDirection: INBOUND +- name: inbound:127.0.0.1:17778 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17778 + bindToPort: false + enableReusePort: false + filterChains: + - filterChainMatch: + transportProtocol: raw_buffer + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + - filterChainMatch: + transportProtocol: tls + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + - filterChainMatch: + applicationProtocols: + - kuma + transportProtocol: tls + filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_127_0_0_1_17778. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsParams: + cipherSuites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + tlsMaximumProtocolVersion: TLSv1_3 + tlsMinimumProtocolVersion: TLSv1_2 + requireClientCertificate: true + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: frontend + name: inbound:127.0.0.1:17778 + trafficDirection: INBOUND diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.policy.yaml new file mode 100644 index 000000000000..cb57bb58a742 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.policy.yaml @@ -0,0 +1,12 @@ +targetRef: + kind: Mesh +from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS12 + max: TLS13 + tlsCiphers: + - "ECDHE-ECDSA-AES128-GCM-SHA256" + mode: Permissive diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.clusters.golden.yaml new file mode 100644 index 000000000000..294c7ad43eb5 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.clusters.golden.yaml @@ -0,0 +1,5 @@ +resources: +- name: outgoing + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + name: outgoing diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.listeners.golden.yaml new file mode 100644 index 000000000000..734cc6ddb380 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.listeners.golden.yaml @@ -0,0 +1,47 @@ +resources: +- name: inbound:127.0.0.1:17777 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17777 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend + name: inbound:127.0.0.1:17777 + trafficDirection: INBOUND +- name: inbound:127.0.0.1:17778 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17778 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: frontend + name: inbound:127.0.0.1:17778 + trafficDirection: INBOUND diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.policy.yaml new file mode 100644 index 000000000000..d8df82277292 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.policy.yaml @@ -0,0 +1,17 @@ +targetRef: + kind: Mesh +from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS11 + max: TLS12 + tlsCiphers: + - "ECDHE-ECDSA-AES128-GCM-SHA256" + - "ECDHE-ECDSA-AES256-GCM-SHA384" + - "ECDHE-ECDSA-CHACHA20-POLY1305" + - "ECDHE-RSA-AES128-GCM-SHA256" + - "ECDHE-RSA-AES256-GCM-SHA384" + - "ECDHE-RSA-CHACHA20-POLY1305" + mode: Strict diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.clusters.golden.yaml new file mode 100644 index 000000000000..e6db585c4951 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.clusters.golden.yaml @@ -0,0 +1,38 @@ +resources: +- name: outgoing + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + name: outgoing + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + alpnProtocols: + - kuma + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + exact: spiffe://default/outgoing + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsParams: + cipherSuites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES256-GCM-SHA384 + - ECDHE-RSA-CHACHA20-POLY1305 + tlsMaximumProtocolVersion: TLSv1_2 + tlsMinimumProtocolVersion: TLSv1_1 diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.listeners.golden.yaml new file mode 100644 index 000000000000..d0c66bf4b08d --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.listeners.golden.yaml @@ -0,0 +1,121 @@ +resources: +- name: inbound:127.0.0.1:17777 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17777 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_127_0_0_1_17777. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17777 + idleTimeout: 7200s + statPrefix: localhost_17777 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsParams: + cipherSuites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES256-GCM-SHA384 + - ECDHE-RSA-CHACHA20-POLY1305 + tlsMaximumProtocolVersion: TLSv1_2 + tlsMinimumProtocolVersion: TLSv1_1 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend + name: inbound:127.0.0.1:17777 + trafficDirection: INBOUND +- name: inbound:127.0.0.1:17778 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 17778 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_127_0_0_1_17778. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:17778 + idleTimeout: 7200s + statPrefix: localhost_17778 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsParams: + cipherSuites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES256-GCM-SHA384 + - ECDHE-RSA-CHACHA20-POLY1305 + tlsMaximumProtocolVersion: TLSv1_2 + tlsMinimumProtocolVersion: TLSv1_1 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: frontend + name: inbound:127.0.0.1:17778 + trafficDirection: INBOUND diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.policy.yaml new file mode 100644 index 000000000000..d8df82277292 --- /dev/null +++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.policy.yaml @@ -0,0 +1,17 @@ +targetRef: + kind: Mesh +from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS11 + max: TLS12 + tlsCiphers: + - "ECDHE-ECDSA-AES128-GCM-SHA256" + - "ECDHE-ECDSA-AES256-GCM-SHA384" + - "ECDHE-ECDSA-CHACHA20-POLY1305" + - "ECDHE-RSA-AES128-GCM-SHA256" + - "ECDHE-RSA-AES256-GCM-SHA384" + - "ECDHE-RSA-CHACHA20-POLY1305" + mode: Strict diff --git a/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go index 18d1702d89bd..507381acc0e5 100644 --- a/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go +++ b/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go @@ -38,7 +38,7 @@ var _ = Describe("RBAC", func() { listener, err := listeners.NewInboundListenerBuilder(envoy.APIV3, "192.168.0.1", 8080, core_xds.SocketAddressProtocolTCP). WithOverwriteName("test_listener"). Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy.APIV3, envoy.AnonymousResource). - Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil))). + Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil), nil, nil)). Configure(listeners.HttpConnectionManager("test_listener", false)))). Build() Expect(err).ToNot(HaveOccurred()) @@ -52,7 +52,7 @@ var _ = Describe("RBAC", func() { listener2, err := listeners.NewInboundListenerBuilder(envoy.APIV3, "192.168.0.1", 8081, core_xds.SocketAddressProtocolTCP). WithOverwriteName("test_listener2"). Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy.APIV3, envoy.AnonymousResource). - Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil))). + Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil), nil, nil)). Configure(listeners.HttpConnectionManager("test_listener2", false)))). Build() Expect(err).ToNot(HaveOccurred()) @@ -66,7 +66,7 @@ var _ = Describe("RBAC", func() { listener3, err := listeners.NewInboundListenerBuilder(envoy.APIV3, "192.168.0.1", 8082, core_xds.SocketAddressProtocolTCP). WithOverwriteName("test_listener3"). Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy.APIV3, envoy.AnonymousResource). - Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil))). + Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil), nil, nil)). Configure(listeners.HttpConnectionManager("test_listener3", false)))). Build() Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go b/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go index 7a84cd835cb6..d38cc6f581ff 100644 --- a/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go +++ b/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go @@ -1,6 +1,7 @@ package clusters_test import ( + tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -126,8 +127,8 @@ var _ = Describe("ClientSideTLSConfigurer", func() { ClientKey: []byte("clientkey"), AllowRenegotiation: true, ServerName: "custom", - MinTlsVersion: pointer.To(xds.TLSVersion10), - MaxTlsVersion: pointer.To(xds.TLSVersion13), + MinTlsVersion: pointer.To(tlsv3.TlsParameters_TLSv1_0), + MaxTlsVersion: pointer.To(tlsv3.TlsParameters_TLSv1_3), }, }, }, diff --git a/pkg/xds/envoy/listeners/filter_chain_configurers.go b/pkg/xds/envoy/listeners/filter_chain_configurers.go index 5eb8cc5078b9..221873f80ad1 100644 --- a/pkg/xds/envoy/listeners/filter_chain_configurers.go +++ b/pkg/xds/envoy/listeners/filter_chain_configurers.go @@ -6,6 +6,7 @@ import ( envoy_extensions_filters_http_compressor_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/compressor/v3" envoy_hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + common_tls "github.com/kumahq/kuma/api/common/v1alpha1/tls" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" core_xds "github.com/kumahq/kuma/pkg/core/xds" @@ -63,10 +64,12 @@ func NetworkDirectResponse(response string) FilterChainBuilderOpt { }) } -func ServerSideMTLS(mesh *core_mesh.MeshResource, secrets core_xds.SecretsTracker) FilterChainBuilderOpt { +func ServerSideMTLS(mesh *core_mesh.MeshResource, secrets core_xds.SecretsTracker, tlsVersion *common_tls.Version, tlsCiphers common_tls.TlsCiphers) FilterChainBuilderOpt { return AddFilterChainConfigurer(&v3.ServerSideMTLSConfigurer{ Mesh: mesh, SecretsTracker: secrets, + TlsVersion: tlsVersion, + TlsCiphers: tlsCiphers, }) } diff --git a/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go b/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go index 3b9d8db21150..6115606cb21b 100644 --- a/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go +++ b/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go @@ -3,7 +3,9 @@ package v3 import ( envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + envoy_tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + common_tls "github.com/kumahq/kuma/api/common/v1alpha1/tls" core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" core_xds "github.com/kumahq/kuma/pkg/core/xds" "github.com/kumahq/kuma/pkg/util/proto" @@ -13,6 +15,8 @@ import ( type ServerSideMTLSConfigurer struct { Mesh *core_mesh.MeshResource SecretsTracker core_xds.SecretsTracker + TlsVersion *common_tls.Version + TlsCiphers common_tls.TlsCiphers } var _ FilterChainConfigurer = &ServerSideMTLSConfigurer{} @@ -25,6 +29,36 @@ func (c *ServerSideMTLSConfigurer) Configure(filterChain *envoy_listener.FilterC if err != nil { return err } + + version := c.TlsVersion + if version != nil { + if version.Min != nil { + tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{ + TlsMinimumProtocolVersion: common_tls.ToTlsVersion(version.Min), + } + } + if version.Max != nil { + if tlsContext.CommonTlsContext.TlsParams == nil { + tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{ + TlsMaximumProtocolVersion: common_tls.ToTlsVersion(version.Max), + } + } else { + tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = common_tls.ToTlsVersion(version.Max) + } + } + } + + ciphers := c.TlsCiphers + if len(ciphers) > 0 { + if tlsContext.CommonTlsContext.TlsParams != nil { + var cipherSuites []string + for _, cipher := range ciphers { + cipherSuites = append(cipherSuites, string(cipher)) + } + tlsContext.CommonTlsContext.TlsParams.CipherSuites = cipherSuites + } + } + if tlsContext != nil { pbst, err := proto.MarshalAnyDeterministic(tlsContext) if err != nil { diff --git a/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go b/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go index c30cf66b8d9b..9eb0dd25f200 100644 --- a/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go +++ b/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go @@ -30,7 +30,7 @@ var _ = Describe("ServerMtlsConfigurer", func() { tracker := envoy_common.NewSecretsTracker(given.mesh.GetMeta().GetName(), nil) listener, err := NewInboundListenerBuilder(envoy_common.APIV3, given.listenerAddress, given.listenerPort, given.listenerProtocol). Configure(FilterChain(NewFilterChainBuilder(envoy_common.APIV3, envoy_common.AnonymousResource). - Configure(ServerSideMTLS(given.mesh, tracker)). + Configure(ServerSideMTLS(given.mesh, tracker, nil, nil)). Configure(TcpProxyDeprecated(given.statsName, given.clusters...)))). Build() // then diff --git a/pkg/xds/envoy/tls/v3/tls.go b/pkg/xds/envoy/tls/v3/tls.go index e5434344c89a..eb4591240839 100644 --- a/pkg/xds/envoy/tls/v3/tls.go +++ b/pkg/xds/envoy/tls/v3/tls.go @@ -97,7 +97,7 @@ func NewSecretConfigSource(secretName string) *envoy_tls.SdsSecretConfig { } } -func UpstreamTlsContextOutsideMesh(systemCaPath string, ca, cert, key []byte, allowRenegotiation, skipHostnameVerification, fallbackToSystemCa bool, hostname, sni string, sans []core_xds.SAN, minTlsVersion, maxTlsVersion *core_xds.TlsVersion) (*envoy_tls.UpstreamTlsContext, error) { +func UpstreamTlsContextOutsideMesh(systemCaPath string, ca, cert, key []byte, allowRenegotiation, skipHostnameVerification, fallbackToSystemCa bool, hostname, sni string, sans []core_xds.SAN, minTlsVersion, maxTlsVersion *envoy_tls.TlsParameters_TlsProtocol) (*envoy_tls.UpstreamTlsContext, error) { tlsContext := &envoy_tls.UpstreamTlsContext{ AllowRenegotiation: allowRenegotiation, Sni: sni, @@ -183,16 +183,16 @@ func UpstreamTlsContextOutsideMesh(systemCaPath string, ca, cert, key []byte, al if minTlsVersion != nil { tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{ - TlsMinimumProtocolVersion: envoy_tls.TlsParameters_TlsProtocol(*minTlsVersion), + TlsMinimumProtocolVersion: *minTlsVersion, } } if maxTlsVersion != nil { if tlsContext.CommonTlsContext.TlsParams == nil { tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{ - TlsMaximumProtocolVersion: envoy_tls.TlsParameters_TlsProtocol(*maxTlsVersion), + TlsMaximumProtocolVersion: *maxTlsVersion, } } else { - tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = envoy_tls.TlsParameters_TlsProtocol(*maxTlsVersion) + tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = *maxTlsVersion } } } diff --git a/pkg/xds/generator/egress/external_services_generator.go b/pkg/xds/generator/egress/external_services_generator.go index 9d4eac525df0..4a9442ebe1dd 100644 --- a/pkg/xds/generator/egress/external_services_generator.go +++ b/pkg/xds/generator/egress/external_services_generator.go @@ -221,7 +221,7 @@ func (g *ExternalServicesGenerator) addFilterChains( ) filterChainBuilder := envoy_listeners.NewFilterChainBuilder(apiVersion, names.GetEgressFilterChainName(esName, meshName)).Configure( - envoy_listeners.ServerSideMTLS(meshResources.Mesh, secretsTracker), + envoy_listeners.ServerSideMTLS(meshResources.Mesh, secretsTracker, nil, nil), envoy_listeners.MatchTransportProtocol("tls"), envoy_listeners.MatchServerNames(sni), envoy_listeners.NetworkRBAC( diff --git a/pkg/xds/generator/inbound_proxy_generator.go b/pkg/xds/generator/inbound_proxy_generator.go index 07e9e78ef9fb..5a7a7214304a 100644 --- a/pkg/xds/generator/inbound_proxy_generator.go +++ b/pkg/xds/generator/inbound_proxy_generator.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" + "github.com/kumahq/kuma/api/common/v1alpha1/tls" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" "github.com/kumahq/kuma/pkg/core/validators" @@ -69,65 +70,11 @@ func (g InboundProxyGenerator) Generate(ctx context.Context, _ *core_xds.Resourc }) cluster := envoy_common.NewCluster(envoy_common.WithService(localClusterName)) - routes := envoy_common.Routes{} - - // Iterate over that RateLimits and generate the relevant Routes. - // We do assume that the rateLimits resource is sorted, so the most - // specific source matches come first. - for _, rl := range proxy.Policies.RateLimitsInbound[endpoint] { - if rl.Spec.GetConf().GetHttp() == nil { - continue - } - - routes = append(routes, envoy_common.NewRoute( - envoy_common.WithCluster(cluster), - envoy_common.WithMatchHeaderRegex(tags.TagsHeaderName, tags.MatchSourceRegex(rl)), - envoy_common.WithRateLimit(rl.Spec), - )) - } - - // Add the default fall-back route - routes = append(routes, envoy_common.NewRoute(envoy_common.WithCluster(cluster))) + routes := GenerateRoutes(proxy, endpoint, cluster) // generate LDS resource service := iface.GetService() inboundListenerName := envoy_names.GetInboundListenerName(endpoint.DataplaneIP, endpoint.DataplanePort) - filterChainBuilder := func(serverSideMTLS bool) *envoy_listeners.FilterChainBuilder { - filterChainBuilder := envoy_listeners.NewFilterChainBuilder(proxy.APIVersion, envoy_common.AnonymousResource) - switch protocol { - // configuration for HTTP case - case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2: - filterChainBuilder. - Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)). - Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)). - Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])). - Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)). - Configure(envoy_listeners.HttpInboundRoutes(service, routes)) - case core_mesh.ProtocolGRPC: - filterChainBuilder. - Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)). - Configure(envoy_listeners.GrpcStats()). - Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)). - Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])). - Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)). - Configure(envoy_listeners.HttpInboundRoutes(service, routes)) - case core_mesh.ProtocolKafka: - filterChainBuilder. - Configure(envoy_listeners.Kafka(localClusterName)). - Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName)))) - case core_mesh.ProtocolTCP: - fallthrough - default: - // configuration for non-HTTP cases - filterChainBuilder.Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName)))) - } - if serverSideMTLS { - filterChainBuilder. - Configure(envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker)) - } - return filterChainBuilder. - Configure(envoy_listeners.Timeout(defaults_mesh.DefaultInboundTimeout(), protocol)) - } listenerBuilder := envoy_listeners.NewInboundListenerBuilder(proxy.APIVersion, endpoint.DataplaneIP, endpoint.DataplanePort, core_xds.SocketAddressProtocolTCP). Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())). @@ -136,24 +83,24 @@ func (g InboundProxyGenerator) Generate(ctx context.Context, _ *core_xds.Resourc switch xdsCtx.Mesh.Resource.GetEnabledCertificateAuthorityBackend().GetMode() { case mesh_proto.CertificateAuthorityBackend_STRICT: listenerBuilder. - Configure(envoy_listeners.FilterChain(filterChainBuilder(true).Configure( + Configure(envoy_listeners.FilterChain(FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure( envoy_listeners.NetworkRBAC(inboundListenerName, xdsCtx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[endpoint]), ))) case mesh_proto.CertificateAuthorityBackend_PERMISSIVE: listenerBuilder. Configure(envoy_listeners.TLSInspector()). Configure(envoy_listeners.FilterChain( - filterChainBuilder(false).Configure( + FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure( envoy_listeners.MatchTransportProtocol("raw_buffer"))), ). Configure(envoy_listeners.FilterChain( // we need to differentiate between just TLS and Kuma's TLS, because with permissive mode // the app itself might be protected by TLS. - filterChainBuilder(false).Configure( + FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure( envoy_listeners.MatchTransportProtocol("tls"))), ). Configure(envoy_listeners.FilterChain( - filterChainBuilder(true).Configure( + FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure( envoy_listeners.MatchTransportProtocol("tls"), envoy_listeners.MatchApplicationProtocols(xds_tls.KumaALPNProtocols...), envoy_listeners.NetworkRBAC(inboundListenerName, xdsCtx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[endpoint]), @@ -175,3 +122,64 @@ func (g InboundProxyGenerator) Generate(ctx context.Context, _ *core_xds.Resourc } return resources, nil } + +func FilterChainBuilder(serverSideMTLS bool, protocol core_mesh.Protocol, proxy *core_xds.Proxy, localClusterName string, xdsCtx xds_context.Context, endpoint mesh_proto.InboundInterface, service string, routes *envoy_common.Routes, tlsVersion *tls.Version, ciphers tls.TlsCiphers) *envoy_listeners.FilterChainBuilder { + filterChainBuilder := envoy_listeners.NewFilterChainBuilder(proxy.APIVersion, envoy_common.AnonymousResource) + switch protocol { + // configuration for HTTP case + case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2: + filterChainBuilder. + Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)). + Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)). + Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])). + Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)). + Configure(envoy_listeners.HttpInboundRoutes(service, *routes)) + case core_mesh.ProtocolGRPC: + filterChainBuilder. + Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)). + Configure(envoy_listeners.GrpcStats()). + Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)). + Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])). + Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)). + Configure(envoy_listeners.HttpInboundRoutes(service, *routes)) + case core_mesh.ProtocolKafka: + filterChainBuilder. + Configure(envoy_listeners.Kafka(localClusterName)). + Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName)))) + case core_mesh.ProtocolTCP: + fallthrough + default: + // configuration for non-HTTP cases + filterChainBuilder.Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName)))) + } + if serverSideMTLS { + filterChainBuilder. + Configure(envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker, tlsVersion, ciphers)) + } + return filterChainBuilder. + Configure(envoy_listeners.Timeout(defaults_mesh.DefaultInboundTimeout(), protocol)) +} + +func GenerateRoutes(proxy *core_xds.Proxy, endpoint mesh_proto.InboundInterface, cluster *envoy_common.ClusterImpl) envoy_common.Routes { + routes := envoy_common.Routes{} + + // Iterate over that RateLimits and generate the relevant Routes. + // We do assume that the rateLimits resource is sorted, so the most + // specific source matches come first. + for _, rl := range proxy.Policies.RateLimitsInbound[endpoint] { + if rl.Spec.GetConf().GetHttp() == nil { + continue + } + + routes = append(routes, envoy_common.NewRoute( + envoy_common.WithCluster(cluster), + envoy_common.WithMatchHeaderRegex(tags.TagsHeaderName, tags.MatchSourceRegex(rl)), + envoy_common.WithRateLimit(rl.Spec), + )) + } + + // Add the default fall-back route + routes = append(routes, envoy_common.NewRoute(envoy_common.WithCluster(cluster))) + + return routes +} diff --git a/pkg/xds/generator/inbound_proxy_generator_test.go b/pkg/xds/generator/inbound_proxy_generator_test.go index 90fe2953aa7b..2ac37a00702a 100644 --- a/pkg/xds/generator/inbound_proxy_generator_test.go +++ b/pkg/xds/generator/inbound_proxy_generator_test.go @@ -250,5 +250,10 @@ var _ = Describe("InboundProxyGenerator", func() { expected: "6-envoy-config.golden.yaml", mode: mesh_proto.CertificateAuthorityBackend_PERMISSIVE, }), + Entry("07. transparent_proxying=true, ip_addresses=2, ports=2, mode=strict", testCase{ + dataplaneFile: "7-dataplane.input.yaml", + expected: "7-envoy-config.golden.yaml", + mode: mesh_proto.CertificateAuthorityBackend_STRICT, + }), ) }) diff --git a/pkg/xds/generator/prometheus_endpoint_generator.go b/pkg/xds/generator/prometheus_endpoint_generator.go index cfdddc365edf..4288cd3b7640 100644 --- a/pkg/xds/generator/prometheus_endpoint_generator.go +++ b/pkg/xds/generator/prometheus_endpoint_generator.go @@ -113,7 +113,7 @@ func (g PrometheusEndpointGenerator) Generate(ctx context.Context, _ *core_xds.R case mesh_proto.PrometheusTlsConfig_activeMTLSBackend: listenerBuilder = listenerBuilder.Configure(envoy_listeners.FilterChain( envoy_listeners.NewFilterChainBuilder(proxy.APIVersion, envoy_common.AnonymousResource).Configure( - envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker), + envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker, nil, nil), envoy_listeners.StaticEndpoints(prometheusListenerName, []*envoy_common.StaticEndpointPath{ { diff --git a/pkg/xds/generator/testdata/inbound-proxy/7-dataplane.input.yaml b/pkg/xds/generator/testdata/inbound-proxy/7-dataplane.input.yaml new file mode 100644 index 000000000000..8b5af1b8c8b3 --- /dev/null +++ b/pkg/xds/generator/testdata/inbound-proxy/7-dataplane.input.yaml @@ -0,0 +1,36 @@ +networking: + transparentProxying: + redirectPortOutbound: 15001 + redirectPortInbound: 15006 + address: 192.168.0.1 + inbound: + - port: 80 + servicePort: 8080 + tags: + kuma.io/service: backend1 + kuma.io/protocol: http + - port: 443 + servicePort: 8443 + tags: + kuma.io/service: backend2 + - address: 192.168.0.2 + port: 80 + servicePort: 8080 + tags: + kuma.io/service: backend3 + kuma.io/protocol: http + - address: 192.168.0.2 + port: 443 + servicePort: 8443 + tags: + kuma.io/service: backend4 + - address: 127.0.0.1 + port: 8080 + servicePort: 1234 + tags: + kuma.io/service: loopback + - address: ::1 + port: 8081 + servicePort: 12345 + tags: + kuma.io/service: loopbackv6 diff --git a/pkg/xds/generator/testdata/inbound-proxy/7-envoy-config.golden.yaml b/pkg/xds/generator/testdata/inbound-proxy/7-envoy-config.golden.yaml new file mode 100644 index 000000000000..444c5a4e3be2 --- /dev/null +++ b/pkg/xds/generator/testdata/inbound-proxy/7-envoy-config.golden.yaml @@ -0,0 +1,513 @@ +resources: +- name: localhost:1234 + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + altStatName: localhost_1234 + connectTimeout: 10s + loadAssignment: + clusterName: localhost:1234 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 127.0.0.1 + portValue: 1234 + name: localhost:1234 + type: STATIC +- name: localhost:12345 + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + altStatName: localhost_12345 + connectTimeout: 10s + loadAssignment: + clusterName: localhost:12345 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: ::1 + portValue: 12345 + name: localhost:12345 + type: STATIC +- name: localhost:8080 + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + altStatName: localhost_8080 + connectTimeout: 10s + loadAssignment: + clusterName: localhost:8080 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 192.168.0.2 + portValue: 8080 + name: localhost:8080 + type: STATIC + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + commonHttpProtocolOptions: + idleTimeout: 7200s + explicitHttpConfig: + httpProtocolOptions: {} + upstreamBindConfig: + sourceAddress: + address: 127.0.0.6 + portValue: 0 +- name: localhost:8443 + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + altStatName: localhost_8443 + connectTimeout: 10s + loadAssignment: + clusterName: localhost:8443 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 192.168.0.2 + portValue: 8443 + name: localhost:8443 + type: STATIC + upstreamBindConfig: + sourceAddress: + address: 127.0.0.6 + portValue: 0 +- name: inbound:127.0.0.1:8080 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 8080 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_127_0_0_1_8080. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:1234 + idleTimeout: 7200s + statPrefix: localhost_1234 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: loopback + name: inbound:127.0.0.1:8080 + trafficDirection: INBOUND +- name: inbound:192.168.0.1:443 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 192.168.0.1 + portValue: 443 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_192_168_0_1_443. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:8443 + idleTimeout: 7200s + statPrefix: localhost_8443 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend2 + name: inbound:192.168.0.1:443 + trafficDirection: INBOUND +- name: inbound:192.168.0.1:80 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 192.168.0.1 + portValue: 80 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: + policies: + tp-1: + permissions: + - any: true + principals: + - andIds: + ids: + - authenticated: + principalName: + exact: kuma://version/1.0 + - authenticated: + principalName: + exact: spiffe://default/web1 + statPrefix: inbound_192_168_0_1_80. + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + idleTimeout: 7200s + forwardClientCertDetails: SANITIZE_SET + httpFilters: + - name: envoy.filters.http.fault + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + delay: + fixedDelay: 5s + percentage: + numerator: 50 + headers: + - name: x-kuma-tags + safeRegexMatch: + regex: .*&kuma.io/service=[^&]*frontend[,&].* + - name: envoy.filters.http.local_ratelimit + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + statPrefix: rate_limit + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + routeConfig: + name: inbound:backend1 + requestHeadersToRemove: + - x-kuma-tags + validateClusters: false + virtualHosts: + - domains: + - '*' + name: backend1 + routes: + - match: + headers: + - name: x-kuma-tags + safeRegexMatch: + regex: .*&kuma.io/service=[^&]*frontend[,&].* + prefix: / + route: + cluster: localhost:8080 + timeout: 0s + typedPerFilterConfig: + envoy.filters.http.local_ratelimit: + '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + filterEnabled: + defaultValue: + numerator: 100 + runtimeKey: local_rate_limit_enabled + filterEnforced: + defaultValue: + numerator: 100 + runtimeKey: local_rate_limit_enforced + statPrefix: rate_limit + tokenBucket: + fillInterval: 10s + maxTokens: 200 + tokensPerFill: 200 + - match: + headers: + - name: x-kuma-tags + safeRegexMatch: + regex: .*&kuma.io/service=.* + prefix: / + route: + cluster: localhost:8080 + timeout: 0s + typedPerFilterConfig: + envoy.filters.http.local_ratelimit: + '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + filterEnabled: + defaultValue: + numerator: 100 + runtimeKey: local_rate_limit_enabled + filterEnforced: + defaultValue: + numerator: 100 + runtimeKey: local_rate_limit_enforced + responseHeadersToAdd: + - appendAction: OVERWRITE_IF_EXISTS_OR_ADD + header: + key: x-rate-limited + value: "true" + statPrefix: rate_limit + status: + code: NotFound + tokenBucket: + fillInterval: 2s + maxTokens: 100 + tokensPerFill: 100 + - match: + prefix: / + route: + cluster: localhost:8080 + timeout: 0s + setCurrentClientCertDetails: + uri: true + statPrefix: localhost_8080 + streamIdleTimeout: 3600s + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/protocol: http + kuma.io/service: backend1 + name: inbound:192.168.0.1:80 + trafficDirection: INBOUND +- name: inbound:192.168.0.2:443 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 192.168.0.2 + portValue: 443 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_192_168_0_2_443. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:8443 + idleTimeout: 7200s + statPrefix: localhost_8443 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend4 + name: inbound:192.168.0.2:443 + trafficDirection: INBOUND +- name: inbound:192.168.0.2:80 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 192.168.0.2 + portValue: 80 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound_192_168_0_2_80. + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + idleTimeout: 7200s + forwardClientCertDetails: SANITIZE_SET + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + routeConfig: + name: inbound:backend3 + requestHeadersToRemove: + - x-kuma-tags + validateClusters: false + virtualHosts: + - domains: + - '*' + name: backend3 + routes: + - match: + prefix: / + route: + cluster: localhost:8080 + timeout: 0s + setCurrentClientCertDetails: + uri: true + statPrefix: localhost_8080 + streamIdleTimeout: 3600s + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/protocol: http + kuma.io/service: backend3 + name: inbound:192.168.0.2:80 + trafficDirection: INBOUND +- name: inbound:[::1]:8081 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: ::1 + portValue: 8081 + bindToPort: false + enableReusePort: false + filterChains: + - filters: + - name: envoy.filters.network.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC + rules: {} + statPrefix: inbound____1__8081. + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: localhost:12345 + idleTimeout: 7200s + statPrefix: localhost_12345 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + prefix: spiffe://default/ + sanType: URI + validationContextSdsSecretConfig: + name: mesh_ca:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + tlsCertificateSdsSecretConfigs: + - name: identity_cert:secret:default + sdsConfig: + ads: {} + resourceApiVersion: V3 + requireClientCertificate: true + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: loopbackv6 + name: inbound:[::1]:8081 + trafficDirection: INBOUND diff --git a/pkg/xds/topology/outbound.go b/pkg/xds/topology/outbound.go index 5c85bab192d9..1896b79a3c6f 100644 --- a/pkg/xds/topology/outbound.go +++ b/pkg/xds/topology/outbound.go @@ -699,10 +699,10 @@ func createMeshExternalServiceEndpoint( } if tls.Version != nil { if tls.Version.Min != nil { - es.MinTlsVersion = pointer.To(toTlsVersion(tls.Version.Min)) + es.MinTlsVersion = pointer.To(common_tls.ToTlsVersion(tls.Version.Min)) } if tls.Version.Max != nil { - es.MaxTlsVersion = pointer.To(toTlsVersion(tls.Version.Max)) + es.MaxTlsVersion = pointer.To(common_tls.ToTlsVersion(tls.Version.Max)) } } // Server name and SNI we need to add @@ -758,23 +758,6 @@ func createMeshExternalServiceEndpoint( return nil } -func toTlsVersion(version *common_tls.TlsVersion) core_xds.TlsVersion { - switch *version { - case common_tls.TLSVersion13: - return core_xds.TLSVersion13 - case common_tls.TLSVersion12: - return core_xds.TLSVersion12 - case common_tls.TLSVersion11: - return core_xds.TLSVersion11 - case common_tls.TLSVersion10: - return core_xds.TLSVersion10 - case common_tls.TLSVersionAuto: - fallthrough - default: - return core_xds.TLSVersionAuto - } -} - func createExternalServiceEndpoint( ctx context.Context, outbound core_xds.EndpointMap, diff --git a/test/e2e_env/multizone/meshtls/meshtls.go b/test/e2e_env/multizone/meshtls/meshtls.go new file mode 100644 index 000000000000..3724643e400b --- /dev/null +++ b/test/e2e_env/multizone/meshtls/meshtls.go @@ -0,0 +1,107 @@ +package meshtls + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + meshtls_api "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/api/v1alpha1" + . "github.com/kumahq/kuma/test/framework" + framework_client "github.com/kumahq/kuma/test/framework/client" + "github.com/kumahq/kuma/test/framework/deployments/democlient" + "github.com/kumahq/kuma/test/framework/deployments/testserver" + "github.com/kumahq/kuma/test/framework/envs/multizone" +) + +func MeshTLS() { + const meshName = "multizone-meshtls" + const k8sZoneNamespace = "multizone-meshtls" + + BeforeAll(func() { + // Global + Expect(NewClusterSetup(). + Install(MTLSMeshUniversal(meshName)). + Install(MeshTrafficPermissionAllowAllUniversal(meshName)). + Setup(multizone.Global)).To(Succeed()) + Expect(WaitForMesh(meshName, multizone.Zones())).To(Succeed()) + + // Kube Zone 1 + Expect(NewClusterSetup(). + Install(NamespaceWithSidecarInjection(k8sZoneNamespace)). + Install(testserver.Install( + testserver.WithName("test-server"), + testserver.WithMesh(meshName), + testserver.WithNamespace(k8sZoneNamespace), + testserver.WithEchoArgs("echo", "--instance", "kube-test-server-1"), + )). + Setup(multizone.KubeZone1), + ).To(Succeed()) + + Expect(NewClusterSetup(). + Install(NamespaceWithSidecarInjection(k8sZoneNamespace)). + Install(democlient.Install( + democlient.WithName("demo-client"), + democlient.WithMesh(meshName), + democlient.WithNamespace(k8sZoneNamespace), + )). + Setup(multizone.KubeZone2), + ).To(Succeed()) + }) + + AfterEachFailure(func() { + DebugUniversal(multizone.Global, meshName) + DebugKube(multizone.KubeZone1, meshName, k8sZoneNamespace) + DebugKube(multizone.KubeZone2, meshName, k8sZoneNamespace) + }) + + E2EAfterEach(func() { + Expect(DeleteMeshResources(multizone.Global, meshName, meshtls_api.MeshTLSResourceTypeDescriptor)).To(Succeed()) + }) + + E2EAfterAll(func() { + Expect(multizone.KubeZone1.TriggerDeleteNamespace(k8sZoneNamespace)).To(Succeed()) + Expect(multizone.KubeZone2.TriggerDeleteNamespace(k8sZoneNamespace)).To(Succeed()) + Expect(multizone.Global.DeleteMesh(meshName)).To(Succeed()) + }) + + It("should define TLS version and traffic should works", func() { + policy := fmt.Sprintf(` +type: MeshTLS +mesh: %s +name: mesh-tls-policy +spec: + targetRef: + kind: Mesh + from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS13 + max: TLS13`, meshName) + + Eventually(func(g Gomega) { + resp, err := framework_client.CollectEchoResponse( + multizone.KubeZone2, "demo-client", "test-server_multizone-meshtls_svc_80.mesh", + framework_client.FromKubernetesPod(k8sZoneNamespace, "demo-client"), + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp.Instance).To(Equal("kube-test-server-1")) + }, "30s", "1s").MustPassRepeatedly(5).Should(Succeed()) + + // when + Expect(multizone.Global.Install(YamlUniversal(policy))).To(Succeed()) + + // then + // traffic should still works + Eventually(func(g Gomega) { + resp, err := framework_client.CollectEchoResponse( + multizone.KubeZone2, "demo-client", "test-server_multizone-meshtls_svc_80.mesh", + framework_client.FromKubernetesPod(k8sZoneNamespace, "demo-client"), + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp.Instance).To(Equal("kube-test-server-1")) + }, "30s", "1s").MustPassRepeatedly(5).Should(Succeed()) + }) +} diff --git a/test/e2e_env/multizone/multizone_suite_test.go b/test/e2e_env/multizone/multizone_suite_test.go index 4b935541d779..d0a5e9e48401 100644 --- a/test/e2e_env/multizone/multizone_suite_test.go +++ b/test/e2e_env/multizone/multizone_suite_test.go @@ -19,6 +19,7 @@ import ( "github.com/kumahq/kuma/test/e2e_env/multizone/meshservice" "github.com/kumahq/kuma/test/e2e_env/multizone/meshtcproute" "github.com/kumahq/kuma/test/e2e_env/multizone/meshtimeout" + "github.com/kumahq/kuma/test/e2e_env/multizone/meshtls" "github.com/kumahq/kuma/test/e2e_env/multizone/meshtrafficpermission" "github.com/kumahq/kuma/test/e2e_env/multizone/ownership" "github.com/kumahq/kuma/test/e2e_env/multizone/reachablebackends" @@ -80,4 +81,5 @@ var ( _ = Describe("Available services", connectivity.AvailableServices, Ordered) _ = Describe("ReachableBackends", reachablebackends.ReachableBackends, Ordered) _ = Describe("MeshServiceReachableBackends", reachablebackends.MeshServicesWithReachableBackendsOption, Ordered) + _ = Describe("MeshTLS", meshtls.MeshTLS, Ordered) ) diff --git a/test/e2e_env/universal/meshtls/policy.go b/test/e2e_env/universal/meshtls/policy.go new file mode 100644 index 000000000000..dd9811e23084 --- /dev/null +++ b/test/e2e_env/universal/meshtls/policy.go @@ -0,0 +1,416 @@ +package meshtls + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/kumahq/kuma/pkg/test/resources/samples" + . "github.com/kumahq/kuma/test/framework" + "github.com/kumahq/kuma/test/framework/client" + "github.com/kumahq/kuma/test/framework/envoy_admin/stats" + "github.com/kumahq/kuma/test/framework/envs/universal" +) + +func Policy() { + var testServerContainerName string + var testServer2ContainerName string + meshName := "mesh-tls" + testServerName := "mesh-tls-test-server" + testServer2Name := "mesh-tls-test-server-2" + + BeforeAll(func() { + testServerContainerName = fmt.Sprintf("%s_%s", universal.Cluster.Name(), testServerName) + testServer2ContainerName = fmt.Sprintf("%s_%s", universal.Cluster.Name(), testServer2Name) + Expect(NewClusterSetup(). + Install(ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build())). + Install(MeshTrafficPermissionAllowAllUniversal(meshName)). + Install(TestServerUniversal( + testServerName, meshName, + WithArgs([]string{"echo", "--instance", "test-server"}), + WithServiceName("mesh-tls-test-server"), + WithDockerContainerName(testServerContainerName), + )). + Install(TestServerUniversal( + testServer2Name, meshName, + WithArgs([]string{"echo", "--instance", "test-server-2"}), + WithServiceName("mesh-tls-test-server-2"), + WithDockerContainerName(testServer2ContainerName), + )). + Install(DemoClientUniversal("mesh-tls-demo-client", meshName, WithTransparentProxy(true))). + Install(DemoClientUniversal("mesh-tls-demo-client-no-mesh", "", WithoutDataplane())). + Setup(universal.Cluster)).To(Succeed()) + }) + + AfterEachFailure(func() { + DebugUniversal(universal.Cluster, meshName) + }) + + E2EAfterAll(func() { + Expect(universal.Cluster.DeleteMeshApps(meshName)).To(Succeed()) + Expect(universal.Cluster.DeleteMesh(meshName)).To(Succeed()) + }) + + It("should change single dataplane to Permissive", func() { + policy := fmt.Sprintf(` +type: MeshTLS +mesh: %s +name: mesh-tls-policy +spec: + targetRef: + kind: MeshSubset + tags: + kuma.io/service: %s + from: + - targetRef: + kind: Mesh + default: + mode: Permissive`, meshName, testServerName) + // when + // default strict mode on mesh + Expect(universal.Cluster.Install( + ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build()), + )).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server-2 from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server-2")) + }, "30s", "500ms").Should(Succeed()) + + // and + // cannot access test-server from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectFailure( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Exitcode).To(Equal(52)) + }, "30s", "500ms").Should(Succeed()) + + // and + // cannot access test-server-2 from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectFailure( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Exitcode).To(Equal(52)) + }, "30s", "500ms").Should(Succeed()) + + // when + // applied MeshTLS policy to set Permissive mode on test-server + Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server-2 from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server-2")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "500ms").Should(Succeed()) + + // and + // cannot access test-server-2 from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectFailure( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Exitcode).To(Equal(52)) + }, "30s", "500ms").Should(Succeed()) + }) + + It("should change single dataplane to Strict", func() { + policy := fmt.Sprintf(` +type: MeshTLS +mesh: %s +name: mesh-tls-policy +spec: + targetRef: + kind: MeshSubset + tags: + kuma.io/service: %s + from: + - targetRef: + kind: Mesh + default: + mode: Strict`, meshName, testServerName) + // when + // default strict mode on mesh + Expect(universal.Cluster.Install( + ResourceUniversal(samples.MeshMTLSBuilder().WithPermissiveMTLSBackends().WithName(meshName).Build()), + )).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server-2 from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server-2")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server-2 from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server-2")) + }, "30s", "500ms").Should(Succeed()) + + // when + // applied MeshTLS policy to set Strict mode on test-server + Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server-2 from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server-2")) + }, "30s", "500ms").Should(Succeed()) + + // and + // can access test-server from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectFailure( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Exitcode).To(Equal(52)) + }, "30s", "500ms").Should(Succeed()) + + // and + // cannot access test-server-2 from service outside of the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server-2")) + }, "30s", "500ms").Should(Succeed()) + }) + + It("should tls version for 1.3", func() { + // given + admin, err := universal.Cluster.GetApp(testServerName).GetEnvoyAdminTunnel() + Expect(err).ToNot(HaveOccurred()) + + policy := fmt.Sprintf(` +type: MeshTLS +mesh: %s +name: mesh-tls-policy +spec: + targetRef: + kind: Mesh + from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS13 + max: TLS13`, meshName) + // when + // default strict mode on mesh + Expect(universal.Cluster.Install( + ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build()), + )).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "1s").Should(Succeed()) + + // and + // uses tls version 1.2 + Eventually(func(g Gomega) { + s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.2") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(s).To(stats.BeGreaterThanZero()) + }, "30s", "1s").Should(Succeed()) + + // when + // applied MeshTLS policy to set 1.3 version on test-server + Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "1s").MustPassRepeatedly(5).Should(Succeed()) + + // and + // uses tls version 1.3 + Eventually(func(g Gomega) { + s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.3") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(s).To(stats.BeGreaterThanZero()) + }, "30s", "1s").Should(Succeed()) + }) + + It("should set cypher and version", func() { + // given + admin, err := universal.Cluster.GetApp(testServerName).GetEnvoyAdminTunnel() + Expect(err).ToNot(HaveOccurred()) + + policy := fmt.Sprintf(` +type: MeshTLS +mesh: %s +name: mesh-tls-policy +spec: + targetRef: + kind: Mesh + from: + - targetRef: + kind: Mesh + default: + tlsVersion: + min: TLS12 + max: TLS12 + tlsCiphers: + - "ECDHE-RSA-AES256-GCM-SHA384"`, meshName) + // when + // default strict mode on mesh + Expect(universal.Cluster.Install( + ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build()), + )).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "1s").Should(Succeed()) + + // and + // uses tls version 1.2 + Eventually(func(g Gomega) { + s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.2") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(s).To(stats.BeGreaterThanZero()) + }, "30s", "1s").Should(Succeed()) + + // and + // doesn't use specified cypher + Eventually(func(g Gomega) { + s, err := admin.GetStats("listener.(.*)_80.ssl.ciphers.ECDHE-RSA-AES256-GCM-SHA384") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(s.Stats).To(BeEmpty()) + }, "30s", "1s").Should(Succeed()) + + // when + // applied MeshTLS policy to set cypher on test-server + Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed()) + + // then + // can access test-server from service in the mesh + Eventually(func(g Gomega) { + responses, err := client.CollectEchoResponse( + universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh", + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(responses.Instance).To(Equal("test-server")) + }, "30s", "1s").MustPassRepeatedly(5).Should(Succeed()) + + // and + // uses tls version 1.2 + Eventually(func(g Gomega) { + s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.2") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(s).To(stats.BeGreaterThanZero()) + }, "30s", "1s").Should(Succeed()) + + // and + // doesn't uses specific cypher + Eventually(func(g Gomega) { + s, err := admin.GetStats("listener.(.*)_80.ssl.ciphers.ECDHE-RSA-AES256-GCM-SHA384") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(s).To(stats.BeGreaterThanZero()) + }, "30s", "1s").Should(Succeed()) + }) +} diff --git a/test/e2e_env/universal/universal_suite_test.go b/test/e2e_env/universal/universal_suite_test.go index 92ae5f3f756a..bc5f21e690a7 100644 --- a/test/e2e_env/universal/universal_suite_test.go +++ b/test/e2e_env/universal/universal_suite_test.go @@ -26,6 +26,7 @@ import ( "github.com/kumahq/kuma/test/e2e_env/universal/meshratelimit" "github.com/kumahq/kuma/test/e2e_env/universal/meshretry" "github.com/kumahq/kuma/test/e2e_env/universal/meshservice" + "github.com/kumahq/kuma/test/e2e_env/universal/meshtls" "github.com/kumahq/kuma/test/e2e_env/universal/meshtrafficpermission" "github.com/kumahq/kuma/test/e2e_env/universal/mtls" "github.com/kumahq/kuma/test/e2e_env/universal/observability" @@ -106,4 +107,5 @@ var ( _ = Describe("MeshLoadBalancingStrategy", meshloadbalancingstrategy.Policy, Ordered) _ = Describe("InterCP Server", intercp.InterCP, Ordered) _ = Describe("Prometheus Metrics", observability.PrometheusMetrics, Ordered) + _ = Describe("MeshTLS", meshtls.Policy, Ordered) )