diff --git a/pkg/controller/intrusiondetection/intrusiondetection_controller.go b/pkg/controller/intrusiondetection/intrusiondetection_controller.go index 9d9f25eadd..396cf66515 100644 --- a/pkg/controller/intrusiondetection/intrusiondetection_controller.go +++ b/pkg/controller/intrusiondetection/intrusiondetection_controller.go @@ -513,8 +513,6 @@ func (r *ReconcileIntrusionDetection) Reconcile(ctx context.Context, request rec } if !r.multiTenant { - // DPI is only supported in single-tenant / zero-tenant clusters. - // FIXME: core controller creates TyphaNodeTLSConfig, this controller should only get it. // But changing the call from GetOrCreateTyphaNodeTLSConfig() to GetTyphaNodeTLSConfig() // makes tests fail, this needs to be looked at. @@ -559,6 +557,18 @@ func (r *ReconcileIntrusionDetection) Reconcile(ctx context.Context, request rec }, TrustedBundle: typhaNodeTLS.TrustedBundle, })) + } else { + dpiComponent := dpi.DPI(&dpi.DPIConfig{ + IntrusionDetection: instance, + Installation: network, + PullSecrets: pullSecrets, + OpenShift: r.provider.IsOpenShift(), + ManagedCluster: isManagedCluster, + ManagementCluster: isManagementCluster, + ClusterDomain: r.clusterDomain, + Tenant: tenant, + }) + components = append(components, dpiComponent) } for _, comp := range components { diff --git a/pkg/controller/intrusiondetection/intrusiondetection_controller_test.go b/pkg/controller/intrusiondetection/intrusiondetection_controller_test.go index 18d6860505..7bd54acefd 100644 --- a/pkg/controller/intrusiondetection/intrusiondetection_controller_test.go +++ b/pkg/controller/intrusiondetection/intrusiondetection_controller_test.go @@ -675,4 +675,66 @@ var _ = Describe("IntrusionDetection controller tests", func() { Expect(*ids.Spec.ComponentResources[0].ResourceRequirements.Limits.Memory()).Should(Equal(resource.MustParse(dpi.DefaultMemoryLimit))) }) }) + + Context("Multi-tenant mode", func() { + var tenantANamespace = "tenantANamespace" + + BeforeEach(func() { + mockStatus = &status.MockStatus{} + mockStatus.On("OnCRFound").Return() + mockStatus.On("SetMetaData", mock.Anything).Return() + + // Update the reconciler to run in external ES mode for these tests. + r.elasticExternal = true + r.multiTenant = true + + // Create the Tenant resources for tenant-a + tenantA := &operatorv1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: tenantANamespace, + }, + Spec: operatorv1.TenantSpec{ID: "tenant-a"}, + } + Expect(c.Create(ctx, tenantA)).NotTo(HaveOccurred()) + + err := c.Create(ctx, &operatorv1.IntrusionDetection{ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure", Namespace: tenantANamespace}}) + Expect(err).NotTo(HaveOccurred()) + + certificateManagerTenantA, err := certificatemanager.Create(c, nil, "", tenantANamespace, certificatemanager.AllowCACreation(), certificatemanager.WithTenant(tenantA)) + Expect(err).NotTo(HaveOccurred()) + Expect(c.Create(ctx, certificateManagerTenantA.KeyPair().Secret(tenantANamespace))) + tenantABundle, err := certificateManagerTenantA.CreateMultiTenantTrustedBundleWithSystemRootCertificates() + Expect(err).NotTo(HaveOccurred()) + Expect(c.Create(ctx, tenantABundle.ConfigMap(tenantANamespace))).NotTo(HaveOccurred()) + + linseedTLSTenantA, err := certificateManagerTenantA.GetOrCreateKeyPair(c, render.TigeraLinseedSecret, tenantANamespace, []string{render.TigeraLinseedSecret}) + Expect(err).NotTo(HaveOccurred()) + Expect(c.Create(ctx, linseedTLSTenantA.Secret(tenantANamespace))).NotTo(HaveOccurred()) + + Expect(c.Delete(ctx, &v3.DeepPacketInspection{ObjectMeta: metav1.ObjectMeta{Name: "test-dpi", Namespace: "test-dpi-ns"}})).ShouldNot(HaveOccurred()) + }) + + It("should Reconcile with default values for DPI", func() { + clusterRole := rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: dpi.DeepPacketInspectionLinseedRBACName, + }, + } + clusterRoleBinding := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: dpi.DeepPacketInspectionLinseedRBACName, + }, + } + + _, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: tenantANamespace}}) + Expect(err).ShouldNot(HaveOccurred()) + + err = test.GetResource(c, &clusterRole) + Expect(err).ShouldNot(HaveOccurred()) + + err = test.GetResource(c, &clusterRoleBinding) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) }) diff --git a/pkg/render/intrusiondetection/dpi/dpi.go b/pkg/render/intrusiondetection/dpi/dpi.go index 1720fa2d5e..9ab95625ad 100644 --- a/pkg/render/intrusiondetection/dpi/dpi.go +++ b/pkg/render/intrusiondetection/dpi/dpi.go @@ -62,6 +62,8 @@ type DPIConfig struct { HasNoDPIResource bool ClusterDomain string DPICertSecret certificatemanagement.KeyPairInterface + + Tenant *operatorv1.Tenant } func DPI(cfg *DPIConfig) render.Component { @@ -89,6 +91,17 @@ func (d *dpiComponent) ResolveImages(is *operatorv1.ImageSet) error { func (d *dpiComponent) Objects() (objsToCreate, objsToDelete []client.Object) { var toCreate, toDelete []client.Object + if d.cfg.Tenant.MultiTenant() { + // We need to create the RBAC needed to allow managed cluster + // to push data via Linseed. Since DPI does not get deployed in the + // multi-tenant management cluster, Linseed token is created to match + // the canonical namespace. The ClusterRoleBinding will use the + // canonical service account. + toCreate = append(toCreate, d.dpiLinseedAccessClusterRole()) + toCreate = append(toCreate, d.dpiLinseedAccessClusterRoleBinding()) + return toCreate, toDelete + } + if d.cfg.HasNoLicense { toDelete = append(toDelete, render.CreateNamespace(DeepPacketInspectionNamespace, d.cfg.Installation.KubernetesProvider, render.PSSPrivileged)) } else { diff --git a/pkg/render/intrusiondetection/dpi/dpi_test.go b/pkg/render/intrusiondetection/dpi/dpi_test.go index 33095f8fc1..b0507e80c6 100644 --- a/pkg/render/intrusiondetection/dpi/dpi_test.go +++ b/pkg/render/intrusiondetection/dpi/dpi_test.go @@ -619,6 +619,45 @@ var _ = Describe("DPI rendering tests", func() { Entry("for managed, openshift-dns", testutils.AllowTigeraScenario{ManagedCluster: true, OpenShift: true}), ) }) + + Context("multi-tenant", func() { + It("should render RBAC to allow Linseed access", func() { + cfg.Tenant = &operatorv1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenantA", + Namespace: "tenantANamespace", + }, + Spec: operatorv1.TenantSpec{ + ID: "tenant-a-id", + }, + } + dpiComponent := dpi.DPI(cfg) + + resources, _ := dpiComponent.Objects() + + cr := rtest.GetResource(resources, dpi.DeepPacketInspectionLinseedRBACName, "", rbacv1.GroupName, "v1", "ClusterRole").(*rbacv1.ClusterRole) + expectedRules := []rbacv1.PolicyRule{ + { + APIGroups: []string{"linseed.tigera.io"}, + Resources: []string{"events"}, + Verbs: []string{ + "create", + }, + }, + } + Expect(cr.Rules).To(ContainElements(expectedRules)) + rb := rtest.GetResource(resources, dpi.DeepPacketInspectionLinseedRBACName, "", rbacv1.GroupName, "v1", "ClusterRoleBinding").(*rbacv1.ClusterRoleBinding) + Expect(rb.RoleRef.Kind).To(Equal("ClusterRole")) + Expect(rb.RoleRef.Name).To(Equal(dpi.DeepPacketInspectionLinseedRBACName)) + Expect(rb.Subjects).To(ContainElements([]rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: dpi.DeepPacketInspectionName, + Namespace: dpi.DeepPacketInspectionNamespace, + }, + })) + }) + }) }) func validateDPIComponents(resources []client.Object, openshift bool) {