From fe5a490fa4eb723ed42fa5473749a065010f3e50 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 18 Jun 2026 12:14:40 -0700 Subject: [PATCH 01/30] api(manager): add RBAC management UI field Add Manager.spec.rbac.ui (Enabled/Disabled enum, defaults to Disabled), the RBAC/RBACUI types, and the nil-safe Manager.RBACManagementEnabled() helper. Regenerate deepcopy methods and the Manager CRD manifest. This is the opt-in toggle for the RBAC management UI feature; the render and controller wiring that consume it follow in subsequent commits. Co-Authored-By: Claude Opus 4.8 (1M context) --- api/v1/manager_types.go | 31 +++++++++++++++++++ api/v1/zz_generated.deepcopy.go | 20 ++++++++++++ .../operator/operator.tigera.io_managers.yaml | 12 +++++++ 3 files changed, 63 insertions(+) diff --git a/api/v1/manager_types.go b/api/v1/manager_types.go index d6e7223844..3bf76c2500 100644 --- a/api/v1/manager_types.go +++ b/api/v1/manager_types.go @@ -26,6 +26,37 @@ type ManagerSpec struct { // ManagerDeployment configures the Manager Deployment. // +optional ManagerDeployment *ManagerDeployment `json:"managerDeployment,omitempty"` + + // RBAC configures the RBAC management UI feature. + // +optional + RBAC *RBAC `json:"rbac,omitempty"` +} + +// RBAC controls the RBAC management UI feature surface. +type RBAC struct { + // UI controls whether the RBAC management UI is enabled. Defaults to + // Disabled. + // +optional + // +kubebuilder:validation:Enum=Enabled;Disabled + UI RBACUI `json:"ui,omitempty"` +} + +// RBACUI toggles the RBAC management UI feature. +type RBACUI string + +const ( + RBACUIEnabled RBACUI = "Enabled" + RBACUIDisabled RBACUI = "Disabled" +) + +// RBACManagementEnabled returns true when the Manager CR opts the cluster +// into the RBAC management UI. Safe to call on a nil receiver; returns false +// for either a nil Manager or any UI value other than Enabled. +func (m *Manager) RBACManagementEnabled() bool { + if m == nil || m.Spec.RBAC == nil { + return false + } + return m.Spec.RBAC.UI == RBACUIEnabled } // ManagerDeployment is the configuration for the Manager Deployment. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index f010edf7ee..34c3b08087 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -7917,6 +7917,11 @@ func (in *ManagerSpec) DeepCopyInto(out *ManagerSpec) { *out = new(ManagerDeployment) (*in).DeepCopyInto(*out) } + if in.RBAC != nil { + in, out := &in.RBAC, &out.RBAC + *out = new(RBAC) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagerSpec. @@ -9011,6 +9016,21 @@ func (in *QueryServerLogging) DeepCopy() *QueryServerLogging { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RBAC) DeepCopyInto(out *RBAC) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBAC. +func (in *RBAC) DeepCopy() *RBAC { + if in == nil { + return nil + } + out := new(RBAC) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Retention) DeepCopyInto(out *Retention) { *out = *in diff --git a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml index 5e77333404..7e352ef27f 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml @@ -298,6 +298,18 @@ spec: type: object type: object type: object + rbac: + description: RBAC configures the RBAC management UI feature. + properties: + ui: + description: |- + UI controls whether the RBAC management UI is enabled. Defaults to + Disabled. + enum: + - Enabled + - Disabled + type: string + type: object type: object status: description: Most recently observed state for the Calico Enterprise manager. From 7316a17ddf076aff3329398cd8e9d3fc7ebaa909 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 18 Jun 2026 12:15:40 -0700 Subject: [PATCH 02/30] render: gate RBAC management UI permissions on spec.rbac.ui Add rbac_management.go with the escalation-prevention rule set shared by the calico-manager and calico-kube-controllers roles, and gate the feature's RBAC on Manager.spec.rbac.ui == Enabled: - manager: extend calico-manager-role with the cluster-scoped UI rules and add a calico-system Role+RoleBinding scoping Secret/ConfigMap access to the IdP resources; both gated on !MultiTenant() (the feature is zero-tenant-only). Set RBAC_UI_ENABLED on ui-apis, and allow LDAP egress when OIDC authentication is configured. - apiserver: extend tigera-network-admin with the manage-roles rule. - kube-controllers: enable the rbacsync controller, grant its rules, and add a calico-system Role+RoleBinding reading tigera-idp-groups. The Manager and Authentication CRs are surfaced through the render configs here; the controllers that populate them follow. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/manager/manager_controller.go | 1 + pkg/render/apiserver.go | 15 +++ pkg/render/apiserver_test.go | 27 +++++ .../kubecontrollers/kube-controllers.go | 84 ++++++++++++++++ .../kubecontrollers/kube-controllers_test.go | 54 ++++++++++ pkg/render/manager.go | 99 ++++++++++++++++++- pkg/render/manager_test.go | 97 ++++++++++++++++++ pkg/render/rbac_management.go | 91 +++++++++++++++++ 8 files changed, 464 insertions(+), 4 deletions(-) create mode 100644 pkg/render/rbac_management.go diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index a60537cc5e..6765a75252 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -719,6 +719,7 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ BindingNamespaces: namespaces, OSSTenantNamespaces: ossTenantNamespaces, Manager: instance, + Authentication: authenticationCR, KibanaEnabled: kibanaEnabled, CACertCommonName: certificateManager.CACertCommonName(), } diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 5be75e7ebd..ef347aeac9 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -143,6 +143,10 @@ type APIServerConfiguration struct { KubernetesVersion *common.VersionInfo ClusterDomain string + // RBACManagementEnabled gates the RBAC management UI permissions on + // tigera-network-admin. + RBACManagementEnabled bool + // Whether or not we should run the aggregation API server for projectcalico.org/v3 APIs // as part of this component. RequiresAggregationServer bool @@ -2056,6 +2060,17 @@ func (c *apiServerComponent) tigeraNetworkAdminClusterRole() *rbacv1.ClusterRole }, } + // Grant network-admin the role/binding access the RBAC management UI needs. + // bind+escalate let it assign managed roles without holding their rules, so + // gate on the feature to keep these verbs off non-RBAC-UI clusters. + if c.cfg.RBACManagementEnabled { + rules = append(rules, rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "roles", "clusterrolebindings", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete", "bind", "escalate"}, + }) + } + // Privileges for lma.tigera.io have no effect on managed clusters. if c.cfg.ManagementClusterConnection == nil { // Access to flow logs, audit logs, and statistics. diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index 9878c7ed1b..9358e5c0f5 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -384,6 +384,25 @@ var _ = Describe("API server rendering tests (Calico Enterprise)", func() { Entry("custom cluster domain", "custom-domain.internal"), ) + It("should gate the RBAC management UI rule on RBACManagementEnabled", func() { + // Disabled (default): tigera-network-admin must not carry the + // escalation-capable RBAC management rule. + component, err := render.APIServer(cfg) + Expect(err).NotTo(HaveOccurred()) + resources, _ := component.Objects() + clusterRole := rtest.GetResource(resources, "tigera-network-admin", "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) + Expect(clusterRole.Rules).NotTo(ContainElement(rbacManagementNetworkAdminRule)) + + // Enabled: the rule is appended. + cfg.RBACManagementEnabled = true + component, err = render.APIServer(cfg) + Expect(err).NotTo(HaveOccurred()) + resources, _ = component.Objects() + clusterRole = rtest.GetResource(resources, "tigera-network-admin", "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) + Expect(clusterRole.Rules).To(ContainElement(rbacManagementNetworkAdminRule)) + Expect(clusterRole.Rules).To(ConsistOf(append(networkAdminPolicyRules, rbacManagementNetworkAdminRule))) + }) + It("should render resources without an aggregation server", func() { cfg.RequiresAggregationServer = false @@ -1914,6 +1933,14 @@ var ( Verbs: []string{"patch"}, }, } + + // rbacManagementNetworkAdminRule is the extra tigera-network-admin rule + // rendered only when Manager.spec.rbac.ui is Enabled. + rbacManagementNetworkAdminRule = rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "roles", "clusterrolebindings", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete", "bind", "escalate"}, + } ) var _ = Describe("API server rendering tests (Calico)", func() { diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 1f592f8358..f2bb25ed9c 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -150,6 +150,10 @@ type KubeControllersConfiguration struct { // caBundle so the apiserver can verify the in-process webhook endpoint. // Only consulted when WAFGatewayExtensionEnabled is true. WAFWebhookCABundle []byte + + // RBACManagementEnabled mirrors Manager.spec.rbac.ui == Enabled and gates + // the rbacsync controller in calico-kube-controllers. + RBACManagementEnabled bool } func NewCalicoKubeControllersPolicy(cfg *KubeControllersConfiguration, defaultDeny *v3.NetworkPolicy) render.Component { @@ -200,6 +204,13 @@ func NewCalicoKubeControllers(cfg *KubeControllersConfiguration) *kubeController if cfg.WAFGatewayExtensionEnabled { enabledControllers = append(enabledControllers, "applicationlayer") } + + // Runs the rbacsync controller to reconcile managed ClusterRoles and + // bindings against the tigera-idp-groups ConfigMap. + if cfg.RBACManagementEnabled { + enabledControllers = append(enabledControllers, "rbacsync") + kubeControllerRolePolicyRules = append(kubeControllerRolePolicyRules, rbacSyncControllerRules()...) + } } return &kubeControllersComponent{ @@ -330,6 +341,9 @@ func (c *kubeControllersComponent) Objects() ([]client.Object, []client.Object) c.controllersClusterRoleBinding(), ) objectsToCreate = append(objectsToCreate, c.managedClusterRoleBindings()...) + if c.cfg.RBACManagementEnabled { + objectsToCreate = append(objectsToCreate, c.rbacSyncIDPGroupsRole()...) + } if len(c.enabledControllers) > 0 { // There's something to run, so create the deployment. @@ -658,6 +672,76 @@ func kubeControllersRoleEnterpriseCommonRules(cfg *KubeControllersConfiguration) return rules } +// rbacSyncIDPGroupsRole returns the Role + RoleBinding that grants rbacsync +// read on the tigera-idp-groups ConfigMap in calico-system. +func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { + name := "calico-kube-controllers-rbac-sync" + return []client.Object{ + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{Kind: "Role", APIVersion: "rbac.authorization.k8s.io/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: common.CalicoNamespace}, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + ResourceNames: []string{"tigera-idp-groups"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: common.CalicoNamespace}, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: name, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: c.kubeControllerServiceAccountName, + Namespace: c.cfg.Namespace, + }, + }, + }, + } +} + +// rbacSyncControllerRules returns the extra rules the rbacsync controller +// needs on top of render.RBACManagementEscalationRules. +func rbacSyncControllerRules() []rbacv1.PolicyRule { + rules := render.RBACManagementEscalationRules() + return append(rules, + rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "clusterrolebindings"}, + Verbs: []string{"escalate", "bind"}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + ResourceNames: []string{"tigera-known-oidc-users"}, + Verbs: []string{"get", "list", "watch"}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{"operator.tigera.io"}, + Resources: []string{"compliances"}, + Verbs: []string{"get", "list"}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "create", "patch"}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"list"}, + }, + ) +} + func (c *kubeControllersComponent) controllersServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ TypeMeta: metav1.TypeMeta{Kind: "ServiceAccount", APIVersion: "v1"}, diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index 9f30802d99..ed945b7fb8 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -428,6 +428,60 @@ var _ = Describe("kube-controllers rendering tests", func() { } }) + Context("RBAC management UI gate", func() { + BeforeEach(func() { + instance.Variant = operatorv1.CalicoEnterprise + }) + + // rbacSyncControllerRules emits exactly this escalate/bind rule — + // uniquely added by the rbacsync gate, so it's a reliable presence + // check that won't drift if other unrelated rules grow new verbs. + escalateBindRule := rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "clusterrolebindings"}, + Verbs: []string{"escalate", "bind"}, + } + + It("does not enable rbacsync when RBACManagementEnabled is false", func() { + component := kubecontrollers.NewCalicoKubeControllers(&cfg) + Expect(component.ResolveImages(nil)).To(BeNil()) + resources, _ := component.Objects() + + dp := rtest.GetResource(resources, kubecontrollers.KubeController, common.CalicoNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + envs := dp.Spec.Template.Spec.Containers[0].Env + Expect(envs).To(ContainElement(corev1.EnvVar{ + Name: "ENABLED_CONTROLLERS", Value: "node,loadbalancer,service,federatedservices,usage", + })) + + role := rtest.GetResource(resources, kubecontrollers.KubeControllerRole, "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) + Expect(role.Rules).NotTo(ContainElement(escalateBindRule), "rbacsync rules should be absent when RBACManagementEnabled is false") + }) + + It("enables rbacsync and adds the controller's RBAC when RBACManagementEnabled is true", func() { + cfg.RBACManagementEnabled = true + component := kubecontrollers.NewCalicoKubeControllers(&cfg) + Expect(component.ResolveImages(nil)).To(BeNil()) + resources, _ := component.Objects() + + dp := rtest.GetResource(resources, kubecontrollers.KubeController, common.CalicoNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + envs := dp.Spec.Template.Spec.Containers[0].Env + Expect(envs).To(ContainElement(corev1.EnvVar{ + Name: "ENABLED_CONTROLLERS", Value: "node,loadbalancer,service,federatedservices,usage,rbacsync", + })) + + role := rtest.GetResource(resources, kubecontrollers.KubeControllerRole, "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) + Expect(role.Rules).To(ContainElement(escalateBindRule), "expected escalate/bind rule in calico-kube-controllers role") + + nsRole := rtest.GetResource(resources, "calico-kube-controllers-rbac-sync", common.CalicoNamespace, "rbac.authorization.k8s.io", "v1", "Role").(*rbacv1.Role) + Expect(nsRole.Rules).To(ContainElement(rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + ResourceNames: []string{"tigera-idp-groups"}, + Verbs: []string{"get", "list", "watch"}, + }), "expected read-only access to tigera-idp-groups in calico-system") + }) + }) + It("should render all es-calico-kube-controllers resources for a default configuration (standalone) using CalicoEnterprise when logstorage and secrets exist", func() { expectedResources := []struct { name string diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 765918beb3..26a18664a3 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -211,8 +211,9 @@ type ManagerConfiguration struct { Tenant *operatorv1.Tenant ExternalElastic bool - Manager *operatorv1.Manager - KibanaEnabled bool + Manager *operatorv1.Manager + Authentication *operatorv1.Authentication + KibanaEnabled bool // CACertCommonName is the CommonName from the CA certificate used for operator-managed certificates. // Passed to Voltron so it can identify the correct CA issuer public key. @@ -277,10 +278,13 @@ func (c *managerComponent) Objects() ([]client.Object, []client.Object) { objsToCreate = append(objsToCreate, managerClusterRoleBinding(c.cfg.Tenant, c.cfg.BindingNamespaces, c.cfg.OSSTenantNamespaces), - managerClusterRole(false, c.cfg.Installation.KubernetesProvider, c.cfg.Tenant), + managerClusterRole(false, c.cfg.Installation.KubernetesProvider, c.cfg.Tenant, c.cfg.Manager.RBACManagementEnabled()), c.managedClustersWatchRoleBinding(), ) objsToCreate = append(objsToCreate, c.managedClustersUpdateRBAC()...) + if c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant() { + objsToCreate = append(objsToCreate, c.rbacManagementUINamespacedRole()...) + } if c.cfg.Tenant.MultiTenant() { objsToCreate = append(objsToCreate, c.multiTenantManagedClustersAccess()...) } @@ -746,6 +750,7 @@ func (c *managerComponent) managerUIAPIsContainer() corev1.Container { {Name: "LINSEED_CLIENT_KEY", Value: keyPath}, {Name: "ELASTIC_KIBANA_DISABLED", Value: strconv.FormatBool(c.cfg.Tenant.MultiTenant())}, {Name: "VOLTRON_URL", Value: ManagerService(c.cfg.Tenant)}, + {Name: "RBAC_UI_ENABLED", Value: strconv.FormatBool(c.cfg.Manager.RBACManagementEnabled())}, } // Determine the Linseed location. Use code default unless in multi-tenant mode, @@ -955,7 +960,8 @@ func (c *managerComponent) managedClustersUpdateRBAC() []client.Object { } // managerClusterRole returns a clusterrole that allows authn/authz review requests. -func managerClusterRole(managedCluster bool, kubernetesProvider operatorv1.Provider, tenant *operatorv1.Tenant) *rbacv1.ClusterRole { +// When rbacManagementEnabled is true it also carries the RBAC management UI rules. +func managerClusterRole(managedCluster bool, kubernetesProvider operatorv1.Provider, tenant *operatorv1.Tenant, rbacManagementEnabled bool) *rbacv1.ClusterRole { // Different tenant types use different permission sets. name := ManagerClusterRole if tenant.ManagedClusterIsCalico() { @@ -1161,6 +1167,12 @@ func managerClusterRole(managedCluster bool, kubernetesProvider operatorv1.Provi }, } + // Zero-tenant-only. Must match the rbacManagementUINamespacedRole gate so + // the SA never gets these cluster rules without the namespaced grant. + if rbacManagementEnabled && !tenant.MultiTenant() { + cr.Rules = append(cr.Rules, rbacManagementUIRules()...) + } + if tenant.MultiTenant() { cr.Rules = append(cr.Rules, rbacv1.PolicyRule{ @@ -1195,6 +1207,72 @@ func managerClusterRole(managedCluster bool, kubernetesProvider operatorv1.Provi return cr } +// rbacManagementUIRules returns the cluster-scoped rules the RBAC management +// UI adds to calico-manager-role. Named-resource access is scoped separately +// on rbacManagementUINamespacedRole. +func rbacManagementUIRules() []rbacv1.PolicyRule { + rules := RBACManagementEscalationRules() + return append(rules, + rbacv1.PolicyRule{ + APIGroups: []string{"operator.tigera.io"}, + Resources: []string{"compliances"}, + Verbs: []string{"get"}, + }, + ) +} + +// rbacManagementUINamespacedRole returns the Role + RoleBinding that scopes +// the RBAC management UI's Secret/ConfigMap access to calico-system, where +// tigera-idp-groups and tigera-idp-ldap-config live. +func (c *managerComponent) rbacManagementUINamespacedRole() []client.Object { + return []client.Object{ + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{Kind: "Role", APIVersion: "rbac.authorization.k8s.io/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: ManagerClusterRole, Namespace: common.CalicoNamespace}, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"configmaps", "secrets"}, + Verbs: []string{"create"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"patch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{"tigera-idp-ldap-config"}, + Verbs: []string{"get", "list", "watch", "update", "delete"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + ResourceNames: []string{"tigera-idp-groups"}, + Verbs: []string{"get", "list", "watch", "update", "delete"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: ManagerClusterRole, Namespace: common.CalicoNamespace}, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: ManagerClusterRole, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: ManagerServiceAccount, + Namespace: c.cfg.Namespace, + }, + }, + }, + } +} + func (c *managerComponent) getTLSObjects() []client.Object { objs := []client.Object{} for _, s := range c.tlsSecrets { @@ -1265,6 +1343,19 @@ func (c *managerComponent) managerCalicoSystemNetworkPolicy() *v3.NetworkPolicy }) } + if c.cfg.Manager.RBACManagementEnabled() && c.cfg.Authentication != nil && c.cfg.Authentication.Spec.OIDC != nil { + // LDAP/AD egress (389, 636) for OIDC group lookup. Destination is + // unscoped since the directory address is customer-configured; + // narrowing it is tracked in EV-6665. + egressRules = append(egressRules, v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Ports: networkpolicy.Ports(389, 636), + }, + }) + } + egressRules = networkpolicy.AppendDNSEgressRules(egressRules, c.cfg.OpenShift) egressRules = append(egressRules, v3.Rule{ Action: v3.Allow, diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index 618b815ca7..9ce85c9038 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -161,6 +161,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { {Name: "LINSEED_CLIENT_KEY", Value: "/internal-manager-tls/tls.key"}, {Name: "ELASTIC_KIBANA_DISABLED", Value: "false"}, {Name: "VOLTRON_URL", Value: render.ManagerService(nil)}, + {Name: "RBAC_UI_ENABLED", Value: "false"}, } Expect(uiAPIs.Env).To(Equal(uiAPIsExpectedEnvVars)) @@ -1730,6 +1731,102 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(rtest.GetContainer(deployment.Spec.Template.Spec.Containers, render.DashboardAPIName)).To(BeNil()) }) }) + + Context("RBAC management UI", func() { + var installation *operatorv1.InstallationSpec + BeforeEach(func() { + replicas := int32(1) + installation = &operatorv1.InstallationSpec{ + ControlPlaneReplicas: &replicas, + Variant: operatorv1.CalicoEnterprise, + Registry: "testregistry.com/", + } + }) + + rbacUIEnabledEnv := func(d *appsv1.Deployment) corev1.EnvVar { + for _, c := range d.Spec.Template.Spec.Containers { + if c.Name == render.UIAPIsName { + for _, e := range c.Env { + if e.Name == "RBAC_UI_ENABLED" { + return e + } + } + } + } + return corev1.EnvVar{} + } + + // Namespaced Role carries the create rule on ConfigMaps/Secrets — this + // is the stable presence check for the RBAC management UI gate. + nsCreateRule := rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"configmaps", "secrets"}, + Verbs: []string{"create"}, + } + + It("renders RBAC_UI_ENABLED=false and no namespaced RBAC UI role when rbac is unset", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, + ns: render.ManagerNamespace, + }) + d := rtest.GetResource(resources, render.ManagerDeploymentName, render.ManagerNamespace, appsv1.GroupName, "v1", "Deployment").(*appsv1.Deployment) + Expect(rbacUIEnabledEnv(d)).To(Equal(corev1.EnvVar{Name: "RBAC_UI_ENABLED", Value: "false"})) + + Expect(rtest.GetResource(resources, render.ManagerClusterRole, render.ManagerNamespace, rbacv1.GroupName, "v1", "Role")).To(BeNil()) + }) + + It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbac.ui is Enabled", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, + ns: render.ManagerNamespace, + manager: &operatorv1.Manager{ + Spec: operatorv1.ManagerSpec{ + RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIEnabled}, + }, + }, + }) + d := rtest.GetResource(resources, render.ManagerDeploymentName, render.ManagerNamespace, appsv1.GroupName, "v1", "Deployment").(*appsv1.Deployment) + Expect(rbacUIEnabledEnv(d)).To(Equal(corev1.EnvVar{Name: "RBAC_UI_ENABLED", Value: "true"})) + + role := rtest.GetResource(resources, render.ManagerClusterRole, render.ManagerNamespace, rbacv1.GroupName, "v1", "Role").(*rbacv1.Role) + Expect(role.Rules).To(ContainElement(nsCreateRule)) + }) + + It("renders RBAC_UI_ENABLED=false when rbac.ui is Disabled", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, + ns: render.ManagerNamespace, + manager: &operatorv1.Manager{ + Spec: operatorv1.ManagerSpec{ + RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIDisabled}, + }, + }, + }) + d := rtest.GetResource(resources, render.ManagerDeploymentName, render.ManagerNamespace, appsv1.GroupName, "v1", "Deployment").(*appsv1.Deployment) + Expect(rbacUIEnabledEnv(d)).To(Equal(corev1.EnvVar{Name: "RBAC_UI_ENABLED", Value: "false"})) + }) + + It("does not add the manager-side RBAC rules in multi-tenant mode", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, + ns: "tenant-a", + bindingNamespaces: []string{"tenant-a"}, + tenant: &operatorv1.Tenant{ + ObjectMeta: metav1.ObjectMeta{Name: "tenantA", Namespace: "tenant-a"}, + Spec: operatorv1.TenantSpec{ + ID: "tenant-a", + ManagedClusterVariant: &operatorv1.Calico, + }, + }, + manager: &operatorv1.Manager{ + Spec: operatorv1.ManagerSpec{ + RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIEnabled}, + }, + }, + }) + Expect(rtest.GetResource(resources, render.ManagerClusterRole, "tenant-a", rbacv1.GroupName, "v1", "Role")).To(BeNil()) + }) + }) }) type renderConfig struct { diff --git a/pkg/render/rbac_management.go b/pkg/render/rbac_management.go new file mode 100644 index 0000000000..7ed3831087 --- /dev/null +++ b/pkg/render/rbac_management.go @@ -0,0 +1,91 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package render + +import rbacv1 "k8s.io/api/rbac/v1" + +// RBACManagementEscalationRules returns the rules shared by the calico-manager +// and calico-kube-controllers roles for the RBAC management UI. The SA writing +// a managed role must already hold every permission it grants, else K8s rejects +// the write as privilege escalation; keep this aligned with the managed roles. +func RBACManagementEscalationRules() []rbacv1.PolicyRule { + return []rbacv1.PolicyRule{ + // Full CRUD on the RBAC objects the feature writes. + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "clusterrolebindings", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // Tier/policy resources covered by generated tier roles. + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{ + "tiers", + "tier.networkpolicies", + "tier.stagednetworkpolicies", + "tier.globalnetworkpolicies", + "tier.stagedglobalnetworkpolicies", + "stagedkubernetesnetworkpolicies", + "networkpolicies", + "globalnetworkpolicies", + "stagednetworkpolicies", + "stagedglobalnetworkpolicies", + }, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + { + APIGroups: []string{"networking.k8s.io"}, + Resources: []string{"networkpolicies"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // Resource-role payload set (the feature-level ClusterRoles maintained + // by rbacsync). + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{ + "uisettings", + "uisettingsgroups", + "globalnetworksets", + "networksets", + "managedclusters", + "policyrecommendationscopes", + "policyrecommendationscopes/status", + "deeppacketinspections", + "deeppacketinspections/status", + "egressgatewaypolicies", + "externalnetworks", + "globalalerts", + "globalalerts/status", + "globalalerttemplates", + "alertexceptions", + "globalthreatfeeds", + "globalthreatfeeds/status", + "globalreports", + "globalreports/status", + "globalreporttypes", + "packetcaptures", + "packetcaptures/files", + "securityeventwebhooks", + }, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // LMA log roles' scope verb (per-cluster log access roles). + { + APIGroups: []string{"lma.tigera.io"}, + Resources: []string{"cluster"}, + Verbs: []string{"get"}, + }, + } +} From 0d99f55f142cf0ce1d9a357fe2b34a36ea89eebb Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 18 Jun 2026 12:16:34 -0700 Subject: [PATCH 03/30] utils: move GetManager into utils Move GetManager from the manager controller into pkg/controller/utils so the apiserver and installation controllers can read the Manager CR without importing the manager controller package. Behaviour is unchanged: it keys on tigera-secure, namespacing the lookup only in multi-tenant mode, and returns the raw client error so callers decide whether absence is fatal. Update the manager controller and its tests to the new location. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/manager/manager_controller.go | 19 +------------------ .../manager/manager_controller_test.go | 16 ++++++++-------- pkg/controller/utils/utils.go | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 6765a75252..04330584c6 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -233,23 +233,6 @@ type ReconcileManager struct { opts options.ControllerOptions } -// GetManager returns the default manager instance with defaults populated. -func GetManager(ctx context.Context, cli client.Client, mt bool, ns string) (*operatorv1.Manager, error) { - key := client.ObjectKey{Name: "tigera-secure"} - if mt { - key.Namespace = ns - } - - // Fetch the manager instance. We only support a single instance named "tigera-secure". - instance := &operatorv1.Manager{} - err := cli.Get(ctx, key, instance) - if err != nil { - return nil, err - } - - return instance, nil -} - // Reconcile reads that state of the cluster for a Manager object and makes changes based on the state read // and what is in the Manager.Spec // The Controller will requeue the Request to be processed again if the returned error is non-nil or @@ -276,7 +259,7 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ } // Fetch the Manager instance that corresponds with this reconcile trigger. - instance, err := GetManager(ctx, r.client, r.opts.MultiTenant, request.Namespace) + instance, err := utils.GetManager(ctx, r.client, r.opts.MultiTenant, request.Namespace) if err != nil { if errors.IsNotFound(err) { logc.Info("Manager object not found") diff --git a/pkg/controller/manager/manager_controller_test.go b/pkg/controller/manager/manager_controller_test.go index a825885f4a..7f747ee372 100644 --- a/pkg/controller/manager/manager_controller_test.go +++ b/pkg/controller/manager/manager_controller_test.go @@ -88,7 +88,7 @@ var _ = Describe("Manager controller tests", func() { } err := c.Create(ctx, instance) Expect(err).NotTo(HaveOccurred()) - instance, err = GetManager(ctx, c, false, "") + instance, err = utils.GetManager(ctx, c, false, "") Expect(err).NotTo(HaveOccurred()) }) @@ -102,7 +102,7 @@ var _ = Describe("Manager controller tests", func() { } err := c.Create(ctx, instanceA) Expect(err).NotTo(HaveOccurred()) - instance, err = GetManager(ctx, c, true, tenantANamespace) + instance, err = utils.GetManager(ctx, c, true, tenantANamespace) Expect(err).NotTo(HaveOccurred()) tenantBNamespace := "tenant-b" @@ -112,13 +112,13 @@ var _ = Describe("Manager controller tests", func() { } err = c.Create(ctx, instanceB) Expect(err).NotTo(HaveOccurred()) - instance, err = GetManager(ctx, c, true, tenantBNamespace) + instance, err = utils.GetManager(ctx, c, true, tenantBNamespace) Expect(err).NotTo(HaveOccurred()) }) It("should return expected error when querying namespace that does not contain a manager instance", func() { nsWithoutManager := "non-manager-ns" - instance, err := GetManager(ctx, c, true, nsWithoutManager) + instance, err := utils.GetManager(ctx, c, true, nsWithoutManager) Expect(kerror.IsNotFound(err)).To(BeTrue()) Expect(instance).To(BeNil()) }) @@ -819,7 +819,7 @@ var _ = Describe("Manager controller tests", func() { Namespace: "", }}) Expect(err).ShouldNot(HaveOccurred()) - instance, err := GetManager(ctx, r.client, false, "") + instance, err := utils.GetManager(ctx, r.client, false, "") Expect(err).ShouldNot(HaveOccurred()) Expect(instance.Status.Conditions).To(HaveLen(1)) @@ -843,7 +843,7 @@ var _ = Describe("Manager controller tests", func() { Namespace: "", }}) Expect(err).ShouldNot(HaveOccurred()) - instance, err := GetManager(ctx, r.client, false, "") + instance, err := utils.GetManager(ctx, r.client, false, "") Expect(err).ShouldNot(HaveOccurred()) Expect(instance.Status.Conditions).To(HaveLen(0)) @@ -887,7 +887,7 @@ var _ = Describe("Manager controller tests", func() { Namespace: "", }}) Expect(err).ShouldNot(HaveOccurred()) - instance, err := GetManager(ctx, r.client, false, "") + instance, err := utils.GetManager(ctx, r.client, false, "") Expect(err).ShouldNot(HaveOccurred()) Expect(instance.Status.Conditions).To(HaveLen(3)) @@ -948,7 +948,7 @@ var _ = Describe("Manager controller tests", func() { Namespace: "", }}) Expect(err).ShouldNot(HaveOccurred()) - instance, err := GetManager(ctx, r.client, false, "") + instance, err := utils.GetManager(ctx, r.client, false, "") Expect(err).ShouldNot(HaveOccurred()) Expect(instance.Status.Conditions).To(HaveLen(3)) diff --git a/pkg/controller/utils/utils.go b/pkg/controller/utils/utils.go index f67fb94c12..3f3b295c1e 100644 --- a/pkg/controller/utils/utils.go +++ b/pkg/controller/utils/utils.go @@ -487,6 +487,22 @@ func GetIstio(ctx context.Context, c client.Client) (*operatorv1.Istio, error) { return istio, nil } +// GetManager returns the Manager CR. When multiTenant is true the +// tenant-scoped instance is read from ns; otherwise the cluster-scoped +// instance is read and ns is ignored. Returns the underlying client error +// (including IsNotFound) — callers decide whether absence is fatal. +func GetManager(ctx context.Context, cli client.Client, multiTenant bool, ns string) (*operatorv1.Manager, error) { + key := DefaultEnterpriseInstanceKey + if multiTenant { + key.Namespace = ns + } + instance := &operatorv1.Manager{} + if err := cli.Get(ctx, key, instance); err != nil { + return nil, err + } + return instance, nil +} + // Return the ManagementCluster CR if present. No error is returned if it was not found. func GetManagementCluster(ctx context.Context, c client.Client) (*operatorv1.ManagementCluster, error) { managementCluster := &operatorv1.ManagementCluster{} From 971bf87b6ff93b88c3bc1063adff78a3a1a5aba5 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 18 Jun 2026 12:16:44 -0700 Subject: [PATCH 04/30] controller(installation,apiserver): wire Manager CR into render config Read the Manager CR in the installation and apiserver controllers and pass RBACManagementEnabled into their render configs, and watch the Manager CR so toggling spec.rbac re-runs each reconcile. The installation controller only reads it for the enterprise variant; absence is treated as RBAC management disabled. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../apiserver/apiserver_controller.go | 18 +++++++++++++++ .../installation/core_controller.go | 23 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pkg/controller/apiserver/apiserver_controller.go b/pkg/controller/apiserver/apiserver_controller.go index 668756cc10..4379a9b60d 100644 --- a/pkg/controller/apiserver/apiserver_controller.go +++ b/pkg/controller/apiserver/apiserver_controller.go @@ -122,6 +122,13 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { return fmt.Errorf("apiserver-controller failed to watch primary resource: %v", err) } + // Watch the Manager CR so toggling spec.rbac re-runs the apiserver + // reconcile (the tigera-network-admin RBAC is gated on it). + err = c.WatchObject(&operatorv1.Manager{}, &handler.EnqueueRequestForObject{}) + if err != nil { + return fmt.Errorf("apiserver-controller failed to watch Manager: %v", err) + } + for _, namespace := range []string{common.OperatorNamespace(), render.APIServerNamespace} { for _, secretName := range []string{render.VoltronTunnelSecretName, render.ManagerTLSSecretName} { if err = utils.AddSecretsWatch(c, secretName, namespace); err != nil { @@ -342,6 +349,7 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re var managementCluster *operatorv1.ManagementCluster var managementClusterConnection *operatorv1.ManagementClusterConnection var keyValidatorConfig authentication.KeyValidatorConfig + var managerCR *operatorv1.Manager includeV3NetworkPolicy := false if installationSpec.Variant.IsEnterprise() { @@ -370,6 +378,15 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re return reconcile.Result{}, err } + managerCR, err = utils.GetManager(ctx, r.client, false, "") + if err != nil && !errors.IsNotFound(err) { + r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) + return reconcile.Result{}, err + } + if errors.IsNotFound(err) { + managerCR = nil + } + if managementClusterConnection != nil && managementCluster != nil { err = fmt.Errorf("having both a ManagementCluster and a ManagementClusterConnection is not supported") r.status.SetDegraded(operatorv1.ResourceValidationError, "", err, reqLogger) @@ -497,6 +514,7 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re KubernetesVersion: r.opts.KubernetesVersion, ClusterDomain: r.opts.ClusterDomain, RequiresAggregationServer: !r.opts.UseV3CRDs, + RBACManagementEnabled: managerCR.RBACManagementEnabled(), QueryServerTLSKeyPairCertificateManagementOnly: queryServerTLSSecretCertificateManagementOnly, } diff --git a/pkg/controller/installation/core_controller.go b/pkg/controller/installation/core_controller.go index c4dcdd6aaa..5591489f6f 100644 --- a/pkg/controller/installation/core_controller.go +++ b/pkg/controller/installation/core_controller.go @@ -252,6 +252,14 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { return fmt.Errorf("tigera-installation-controller failed to watch primary resource: %v", err) } + // Watch the Manager CR so changes to spec.rbac re-run the installation + // reconcile (the rbacsync controller in calico-kube-controllers is + // gated on it). + err = c.WatchObject(&operatorv1.Manager{}, &handler.EnqueueRequestForObject{}) + if err != nil { + return fmt.Errorf("tigera-installation-controller failed to watch Manager: %v", err) + } + // watch for change to primary resource LogCollector err = c.WatchObject(&operatorv1.LogCollector{}, &handler.EnqueueRequestForObject{}) if err != nil { @@ -1049,6 +1057,7 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile var managementCluster *operatorv1.ManagementCluster var managementClusterConnection *operatorv1.ManagementClusterConnection + var managerCR *operatorv1.Manager var logCollector *operatorv1.LogCollector if r.enterpriseCRDsExist { logCollector, err = utils.GetLogCollector(ctx, r.client) @@ -1071,6 +1080,17 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile return reconcile.Result{}, err } + if instance.Spec.Variant.IsEnterprise() { + managerCR, err = utils.GetManager(ctx, r.client, false, "") + if err != nil && !apierrors.IsNotFound(err) { + r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) + return reconcile.Result{}, err + } + if apierrors.IsNotFound(err) { + managerCR = nil + } + } + if managementClusterConnection != nil && managementCluster != nil { err = fmt.Errorf("having both a managementCluster and a managementClusterConnection is not supported") r.status.SetDegraded(operatorv1.ResourceValidationError, "", err, reqLogger) @@ -1705,7 +1725,8 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile // the kube-controllers component (and deleted when the WAF extension is // disabled); the caBundle is the operator CA that issued the serving // cert above. - WAFWebhookCABundle: certificateManager.KeyPair().GetCertificatePEM(), + WAFWebhookCABundle: certificateManager.KeyPair().GetCertificatePEM(), + RBACManagementEnabled: managerCR.RBACManagementEnabled(), } components = append(components, kubecontrollers.NewCalicoKubeControllers(&kubeControllersCfg)) From 73d1c97b38c3db3454643ba852f119894e40f129 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 25 Jun 2026 12:19:45 -0700 Subject: [PATCH 05/30] render(apiserver): limit network-admin RBAC UI to the user's own privileges (PR #4865 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Pasan's review comment on the tigera-network-admin escalate/bind grant. The RBAC management UI must never let an admin grant more than they already hold, so this grants NO escalate and NO bind. ui-apis performs the /team/* RBAC writes by impersonating the calling user, so the kube-apiserver runs its privilege-escalation check against the admin's own permissions. Without escalate/bind that means: an admin can assign a calico-ui- role to a group only if they personally hold every rule in it, and can edit a role only if they already hold its rules — otherwise the apiserver returns 403. The UI is a convenience layer over the user's own privileges, not an amplifier; deciding which admins may delegate which capabilities is then a matter of what those admins are granted, under the operator's control. The grant is also narrowed to what the handlers actually do (verified against the /team/* implementation): roles are read-only (the UI lists/inspects the catalog, never authors roles); clusterrolebindings get create/update/delete for group and member writes; rolebindings are never created (only cluster-scoped groups are), so they get update/delete only. Gated behind the RBAC-UI feature flag. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/apiserver.go | 24 ++++++++++++++++-------- pkg/render/apiserver_test.go | 32 ++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index ef347aeac9..1d7e6ff4b6 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -2060,15 +2060,23 @@ func (c *apiServerComponent) tigeraNetworkAdminClusterRole() *rbacv1.ClusterRole }, } - // Grant network-admin the role/binding access the RBAC management UI needs. - // bind+escalate let it assign managed roles without holding their rules, so - // gate on the feature to keep these verbs off non-RBAC-UI clusters. + // Role/binding access for the RBAC management UI. ui-apis writes these + // impersonating the caller, so the apiserver enforces escalation against the + // user's own permissions. The UI reads the role catalogue and manages group + // membership through both cluster- and namespace-scoped bindings. if c.cfg.RBACManagementEnabled { - rules = append(rules, rbacv1.PolicyRule{ - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles", "roles", "clusterrolebindings", "rolebindings"}, - Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete", "bind", "escalate"}, - }) + rules = append(rules, + rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "roles"}, + Verbs: []string{"get", "list", "watch"}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "delete"}, + }, + ) } // Privileges for lma.tigera.io have no effect on managed clusters. diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index 9358e5c0f5..d53aba7cfd 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -391,16 +391,20 @@ var _ = Describe("API server rendering tests (Calico Enterprise)", func() { Expect(err).NotTo(HaveOccurred()) resources, _ := component.Objects() clusterRole := rtest.GetResource(resources, "tigera-network-admin", "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) - Expect(clusterRole.Rules).NotTo(ContainElement(rbacManagementNetworkAdminRule)) + for _, rule := range rbacManagementNetworkAdminRules { + Expect(clusterRole.Rules).NotTo(ContainElement(rule)) + } - // Enabled: the rule is appended. + // Enabled: the rules are appended. cfg.RBACManagementEnabled = true component, err = render.APIServer(cfg) Expect(err).NotTo(HaveOccurred()) resources, _ = component.Objects() clusterRole = rtest.GetResource(resources, "tigera-network-admin", "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) - Expect(clusterRole.Rules).To(ContainElement(rbacManagementNetworkAdminRule)) - Expect(clusterRole.Rules).To(ConsistOf(append(networkAdminPolicyRules, rbacManagementNetworkAdminRule))) + for _, rule := range rbacManagementNetworkAdminRules { + Expect(clusterRole.Rules).To(ContainElement(rule)) + } + Expect(clusterRole.Rules).To(ConsistOf(append(networkAdminPolicyRules, rbacManagementNetworkAdminRules...))) }) It("should render resources without an aggregation server", func() { @@ -1934,12 +1938,20 @@ var ( }, } - // rbacManagementNetworkAdminRule is the extra tigera-network-admin rule - // rendered only when Manager.spec.rbac.ui is Enabled. - rbacManagementNetworkAdminRule = rbacv1.PolicyRule{ - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles", "roles", "clusterrolebindings", "rolebindings"}, - Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete", "bind", "escalate"}, + // rbacManagementNetworkAdminRules are the extra tigera-network-admin rules + // added when rbac.ui is Enabled. See tigeraNetworkAdminClusterRole for the + // rationale behind the verb set. + rbacManagementNetworkAdminRules = []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "roles"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "delete"}, + }, } ) From 5051f2afa5f3161a329a816ab8ec7ff689429f0f Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 25 Jun 2026 12:20:00 -0700 Subject: [PATCH 06/30] render(rbac): drop escalate/bind from rbacsync, hold the managed permissions instead (PR #4865 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Pasan's review comment that the rbacsync controller can become a superuser if compromised, via cluster-wide escalate/bind on RBAC objects. The verbs aren't actually needed: rbacSyncControllerRules already grants the SA the full permission set the calico-ui- roles contain (RBACManagementEscalationRules), so it satisfies Kubernetes' privilege-escalation check by *holding* the rules it writes, rather than by escalate/bind. Removing the verbs means a compromised SA can only assign permissions it already has — it cannot mint an arbitrary cluster-admin role. This relies on RBACManagementEscalationRules staying a superset of the managed roles' contents (which live in calico/kube-controllers). That drift risk is accepted here and is better caught by E2Es than guarded by a standing escalate grant. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/kubecontrollers/kube-controllers.go | 5 ----- .../kubecontrollers/kube-controllers_test.go | 15 +-------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index f2bb25ed9c..16508186da 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -713,11 +713,6 @@ func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { func rbacSyncControllerRules() []rbacv1.PolicyRule { rules := render.RBACManagementEscalationRules() return append(rules, - rbacv1.PolicyRule{ - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles", "clusterrolebindings"}, - Verbs: []string{"escalate", "bind"}, - }, rbacv1.PolicyRule{ APIGroups: []string{""}, Resources: []string{"configmaps"}, diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index ed945b7fb8..0d6bab72d9 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -433,15 +433,6 @@ var _ = Describe("kube-controllers rendering tests", func() { instance.Variant = operatorv1.CalicoEnterprise }) - // rbacSyncControllerRules emits exactly this escalate/bind rule — - // uniquely added by the rbacsync gate, so it's a reliable presence - // check that won't drift if other unrelated rules grow new verbs. - escalateBindRule := rbacv1.PolicyRule{ - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles", "clusterrolebindings"}, - Verbs: []string{"escalate", "bind"}, - } - It("does not enable rbacsync when RBACManagementEnabled is false", func() { component := kubecontrollers.NewCalicoKubeControllers(&cfg) Expect(component.ResolveImages(nil)).To(BeNil()) @@ -453,8 +444,7 @@ var _ = Describe("kube-controllers rendering tests", func() { Name: "ENABLED_CONTROLLERS", Value: "node,loadbalancer,service,federatedservices,usage", })) - role := rtest.GetResource(resources, kubecontrollers.KubeControllerRole, "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) - Expect(role.Rules).NotTo(ContainElement(escalateBindRule), "rbacsync rules should be absent when RBACManagementEnabled is false") + Expect(rtest.GetResource(resources, "calico-kube-controllers-rbac-sync", common.CalicoNamespace, "rbac.authorization.k8s.io", "v1", "Role")).To(BeNil()) }) It("enables rbacsync and adds the controller's RBAC when RBACManagementEnabled is true", func() { @@ -469,9 +459,6 @@ var _ = Describe("kube-controllers rendering tests", func() { Name: "ENABLED_CONTROLLERS", Value: "node,loadbalancer,service,federatedservices,usage,rbacsync", })) - role := rtest.GetResource(resources, kubecontrollers.KubeControllerRole, "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) - Expect(role.Rules).To(ContainElement(escalateBindRule), "expected escalate/bind rule in calico-kube-controllers role") - nsRole := rtest.GetResource(resources, "calico-kube-controllers-rbac-sync", common.CalicoNamespace, "rbac.authorization.k8s.io", "v1", "Role").(*rbacv1.Role) Expect(nsRole.Rules).To(ContainElement(rbacv1.PolicyRule{ APIGroups: []string{""}, From b655d8f5325b37ce0298a9e28b950d3290378123 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 25 Jun 2026 13:02:41 -0700 Subject: [PATCH 07/30] controller(installation): tolerate missing Manager CRD instead of gating on variant (PR #4865 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Pasan's review comment asking whether the variant check before the GetManager read is necessary. It was — but it was the wrong tool. The Manager type is registered in the scheme unconditionally, so on a Calico (OSS) cluster the cached client returns a NoMatchError (not NotFound) when the Manager CRD is absent; the previous error check only tolerated IsNotFound, so without the variant guard the installation controller would degrade on OSS. Drop the redundant variant check (the block already sits under r.enterpriseCRDsExist) and instead tolerate meta.IsNoMatchError alongside IsNotFound, mirroring how this same function handles the calico-system Tier read a few lines below. This is more robust than the variant guard: it also covers the case where enterpriseCRDsExist is true but the Manager CRD has not yet registered. managerCR stays nil on those paths and RBACManagementEnabled() is nil-safe, so behavior is unchanged on OSS. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/installation/core_controller.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pkg/controller/installation/core_controller.go b/pkg/controller/installation/core_controller.go index 5591489f6f..2a00499c86 100644 --- a/pkg/controller/installation/core_controller.go +++ b/pkg/controller/installation/core_controller.go @@ -1080,15 +1080,14 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile return reconcile.Result{}, err } - if instance.Spec.Variant.IsEnterprise() { - managerCR, err = utils.GetManager(ctx, r.client, false, "") - if err != nil && !apierrors.IsNotFound(err) { - r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) - return reconcile.Result{}, err - } - if apierrors.IsNotFound(err) { - managerCR = nil - } + // On Calico/OSS the Manager CRD is absent, so the read returns NoMatchError. + managerCR, err = utils.GetManager(ctx, r.client, false, "") + if err != nil && !apierrors.IsNotFound(err) && !meta.IsNoMatchError(err) { + r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) + return reconcile.Result{}, err + } + if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) { + managerCR = nil } if managementClusterConnection != nil && managementCluster != nil { From 34e17ae62304b6a308f69f25ae8a9a4dcae3cb03 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 25 Jun 2026 13:21:56 -0700 Subject: [PATCH 08/30] render(rbac): drop unused tigera-known-oidc-users grant from rbacsync SA (PR #4865 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Pasan's review comment asking whether the kube-controllers (rbacsync) controller actually accesses the tigera-known-oidc-users ConfigMap — he believed only the ui-apis endpoint consults it. Verified against the RBAC management UI feature branches (calico-private kube-controllers PR #12077 and ui-apis PR #12088): the rbacsync controller never reads this ConfigMap (no direct CoreV1().ConfigMaps() access and no rbaccache path to it; the only ConfigMap it reads is tigera-idp-groups, covered by its own namespaced Role). The actual consumers are in ui-apis — the user-handler middleware writes it and idp/reader.go reads it to surface idp-group members at /team/groups time — using ui-apis's own identity in the tigera-elasticsearch namespace, not the kube-controllers SA. The legitimate kube-controllers/authorization access is already granted separately by logstorage.go. So this rule was dead RBAC on the kube-controllers SA, also gated on the unrelated RBACManagementEnabled flag. Remove it. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/kubecontrollers/kube-controllers.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 16508186da..d5255715b2 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -713,12 +713,6 @@ func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { func rbacSyncControllerRules() []rbacv1.PolicyRule { rules := render.RBACManagementEscalationRules() return append(rules, - rbacv1.PolicyRule{ - APIGroups: []string{""}, - Resources: []string{"configmaps"}, - ResourceNames: []string{"tigera-known-oidc-users"}, - Verbs: []string{"get", "list", "watch"}, - }, rbacv1.PolicyRule{ APIGroups: []string{"operator.tigera.io"}, Resources: []string{"compliances"}, From 4aee36e2caea950c0922f5d3f749f3bd12672489 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 25 Jun 2026 13:40:23 -0700 Subject: [PATCH 09/30] render(manager): gate LDAP egress on the RBAC-UI LDAP config secret, not OIDC (PR #4865 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Pasan's review comment: the manager's LDAP/AD egress (389/636) was gated on Authentication.Spec.OIDC != nil, which is the wrong signal. Verified against the RBAC management UI feature branches (ui-apis PR #12088): the LDAP dial is performed by ui-apis's rbacmanagement/idp client for the /team/idp-groups directory sync, gated solely on the presence of the tigera-idp-ldap-config Secret (in calico-system) — it does not consult the Authentication CR's OIDC connector at all (that path never dials LDAP from this pod). So the old gate opened the port for pure-OIDC setups that never use it and, worse, withheld it for LDAP-sync setups that need it, leaving the feature broken. Gate the egress on the secret instead: - manager controller discovers the tigera-idp-ldap-config Secret in calico-system and watches it (so the policy re-renders on add/remove), passing presence into the render config as RBACManagementLDAPConfigured. - the egress now renders iff RBACManagementEnabled && RBACManagementLDAPConfigured. Added focused tests including a regression guard that OIDC-configured + no LDAP secret produces no egress. Scoping the destination to the configured LDAP host remains tracked in EV-6665 (it needs the same secret read). Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/manager/manager_controller.go | 73 +++++++++------ pkg/render/manager.go | 20 +++- pkg/render/manager_test.go | 98 +++++++++++++++----- 3 files changed, 135 insertions(+), 56 deletions(-) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 04330584c6..471b488782 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -95,6 +95,12 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { return err } + // Re-render when the RBAC-UI LDAP config Secret appears/disappears: it gates + // the manager's LDAP egress policy. + if err := utils.AddSecretsWatch(c, render.RBACManagementLDAPConfigSecretName, common.CalicoNamespace); err != nil { + return err + } + go utils.WaitToAddLicenseKeyWatch(c, opts.K8sClientset, log, licenseAPIReady) go utils.WaitToAddTierWatch(networkpolicy.CalicoTierName, c, opts.K8sClientset, log, tierWatchReady) policiesToWatch := []types.NamespacedName{ @@ -675,36 +681,45 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ } } + // Presence of the LDAP config Secret (always calico-system; the feature is + // zero-tenant) gates the manager's LDAP egress policy below. + ldapConfigSecret, err := utils.GetSecret(ctx, r.client, render.RBACManagementLDAPConfigSecretName, common.CalicoNamespace) + if err != nil { + r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading RBAC management LDAP config secret", err, logc) + return reconcile.Result{}, err + } + managerCfg := &render.ManagerConfiguration{ - VoltronRouteConfig: routeConfig, - KeyValidatorConfig: keyValidatorConfig, - TrustedCertBundle: trustedBundle, - TLSKeyPair: tlsSecret, - VoltronLinseedKeyPair: linseedVoltronServerCert, - PullSecrets: pullSecrets, - OpenShift: r.opts.DetectedProvider.IsOpenShift(), - Installation: installationSpec, - ManagementCluster: managementCluster, - NonClusterHost: nonclusterhost, - TunnelServerCert: tunnelServerCert, - AdditionalTunnelServerCert: additionalTunnelServerCert, - InternalTLSKeyPair: internalTrafficSecret, - ClusterDomain: r.opts.ClusterDomain, - ESLicenseType: elasticLicenseType, - Replicas: replicas, - Compliance: complianceCR, - ComplianceLicenseActive: complianceLicenseFeatureActive, - ComplianceNamespace: utils.NewNamespaceHelper(r.opts.MultiTenant, render.ComplianceNamespace, request.Namespace).InstallNamespace(), - Namespace: helper.InstallNamespace(), - TruthNamespace: helper.TruthNamespace(), - Tenant: tenant, - ExternalElastic: r.opts.ElasticExternal, - BindingNamespaces: namespaces, - OSSTenantNamespaces: ossTenantNamespaces, - Manager: instance, - Authentication: authenticationCR, - KibanaEnabled: kibanaEnabled, - CACertCommonName: certificateManager.CACertCommonName(), + VoltronRouteConfig: routeConfig, + KeyValidatorConfig: keyValidatorConfig, + TrustedCertBundle: trustedBundle, + TLSKeyPair: tlsSecret, + VoltronLinseedKeyPair: linseedVoltronServerCert, + PullSecrets: pullSecrets, + OpenShift: r.opts.DetectedProvider.IsOpenShift(), + Installation: installationSpec, + ManagementCluster: managementCluster, + NonClusterHost: nonclusterhost, + TunnelServerCert: tunnelServerCert, + AdditionalTunnelServerCert: additionalTunnelServerCert, + InternalTLSKeyPair: internalTrafficSecret, + ClusterDomain: r.opts.ClusterDomain, + ESLicenseType: elasticLicenseType, + Replicas: replicas, + Compliance: complianceCR, + ComplianceLicenseActive: complianceLicenseFeatureActive, + ComplianceNamespace: utils.NewNamespaceHelper(r.opts.MultiTenant, render.ComplianceNamespace, request.Namespace).InstallNamespace(), + Namespace: helper.InstallNamespace(), + TruthNamespace: helper.TruthNamespace(), + Tenant: tenant, + ExternalElastic: r.opts.ElasticExternal, + BindingNamespaces: namespaces, + OSSTenantNamespaces: ossTenantNamespaces, + Manager: instance, + Authentication: authenticationCR, + KibanaEnabled: kibanaEnabled, + RBACManagementLDAPConfigured: ldapConfigSecret != nil, + CACertCommonName: certificateManager.CACertCommonName(), } // Render the desired objects from the CRD and create or update them. diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 26a18664a3..864593c8e6 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -77,6 +77,11 @@ const ( ManagerPolicyName = networkpolicy.CalicoComponentPolicyPrefix + "manager-access" ManagerPortName = "https" + // RBACManagementLDAPConfigSecretName is the RBAC-UI LDAP directory-sync config + // Secret (calico-system); its presence gates the manager's LDAP egress policy. + // Keep in sync with ui-apis rbacmanagement/idp LDAPConfigSecretName. + RBACManagementLDAPConfigSecretName = "tigera-idp-ldap-config" + // The name of the TLS certificate used by Voltron to authenticate connections from managed // cluster clients talking to Linseed. VoltronLinseedTLS = "calico-voltron-linseed-tls" @@ -215,6 +220,10 @@ type ManagerConfiguration struct { Authentication *operatorv1.Authentication KibanaEnabled bool + // RBACManagementLDAPConfigured reports whether the RBAC-UI LDAP config Secret + // (RBACManagementLDAPConfigSecretName) is present. It gates the LDAP egress policy. + RBACManagementLDAPConfigured bool + // CACertCommonName is the CommonName from the CA certificate used for operator-managed certificates. // Passed to Voltron so it can identify the correct CA issuer public key. CACertCommonName string @@ -1243,7 +1252,7 @@ func (c *managerComponent) rbacManagementUINamespacedRole() []client.Object { { APIGroups: []string{""}, Resources: []string{"secrets"}, - ResourceNames: []string{"tigera-idp-ldap-config"}, + ResourceNames: []string{RBACManagementLDAPConfigSecretName}, Verbs: []string{"get", "list", "watch", "update", "delete"}, }, { @@ -1343,10 +1352,11 @@ func (c *managerComponent) managerCalicoSystemNetworkPolicy() *v3.NetworkPolicy }) } - if c.cfg.Manager.RBACManagementEnabled() && c.cfg.Authentication != nil && c.cfg.Authentication.Spec.OIDC != nil { - // LDAP/AD egress (389, 636) for OIDC group lookup. Destination is - // unscoped since the directory address is customer-configured; - // narrowing it is tracked in EV-6665. + if c.cfg.Manager.RBACManagementEnabled() && c.cfg.RBACManagementLDAPConfigured { + // LDAP/AD egress (389, 636) for the RBAC-UI directory sync, which dials the + // directory from this pod. Gated on the LDAP config Secret, whose presence + // marks the sync as configured. Destination unscoped (customer-configured); + // scoping to the LDAP host is tracked in EV-6665. egressRules = append(egressRules, v3.Rule{ Action: v3.Allow, Protocol: &networkpolicy.TCPProtocol, diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index 9ce85c9038..5017b61221 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1826,6 +1826,58 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }) Expect(rtest.GetResource(resources, render.ManagerClusterRole, "tenant-a", rbacv1.GroupName, "v1", "Role")).To(BeNil()) }) + + Context("LDAP egress network policy gate", func() { + policyName := types.NamespacedName{Name: "calico-system.manager-access", Namespace: render.ManagerNamespace} + ldapEgress := v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Ports: networkpolicy.Ports(389, 636), + }, + } + rbacUIManager := &operatorv1.Manager{ + Spec: operatorv1.ManagerSpec{RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIEnabled}}, + } + + It("adds LDAP egress only when RBAC UI is enabled and the LDAP config secret is present", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + manager: rbacUIManager, rbacManagementLDAP: true, + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).To(ContainElement(ldapEgress)) + }) + + It("omits LDAP egress when the LDAP config secret is absent, even with RBAC UI enabled", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + manager: rbacUIManager, rbacManagementLDAP: false, + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) + + It("omits LDAP egress when OIDC is configured but the LDAP config secret is absent", func() { + // Regression guard: the gate must key on the LDAP config secret, + // not on Authentication.Spec.OIDC (which never drives an LDAP dial). + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + manager: rbacUIManager, oidc: true, rbacManagementLDAP: false, + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) + + It("omits LDAP egress when the LDAP secret is present but RBAC UI is disabled", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + rbacManagementLDAP: true, + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) + }) }) }) @@ -1843,6 +1895,7 @@ type renderConfig struct { tenant *operatorv1.Tenant manager *operatorv1.Manager externalElastic bool + rbacManagementLDAP bool } func renderObjects(roc renderConfig) ([]client.Object, []client.Object) { @@ -1888,28 +1941,29 @@ func renderObjects(roc renderConfig) ([]client.Object, []client.Object) { } cfg := &render.ManagerConfiguration{ - KeyValidatorConfig: dexCfg, - TrustedCertBundle: bundle, - TLSKeyPair: managerTLS, - Installation: roc.installation, - ManagementCluster: roc.managementCluster, - NonClusterHost: roc.nonClusterHost, - TunnelServerCert: tunnelSecret, - VoltronLinseedKeyPair: voltronLinseedKP, - InternalTLSKeyPair: internalTraffic, - ClusterDomain: dns.DefaultClusterDomain, - ESLicenseType: render.ElasticsearchLicenseTypeEnterpriseTrial, - Replicas: roc.installation.ControlPlaneReplicas, - Compliance: roc.compliance, - ComplianceLicenseActive: roc.complianceFeatureActive, - OpenShift: roc.openshift, - Namespace: roc.ns, - BindingNamespaces: roc.bindingNamespaces, - OSSTenantNamespaces: roc.ossBindingNamespaces, - Tenant: roc.tenant, - Manager: roc.manager, - ExternalElastic: roc.externalElastic, - CACertCommonName: certificateManager.CACertCommonName(), + KeyValidatorConfig: dexCfg, + TrustedCertBundle: bundle, + TLSKeyPair: managerTLS, + Installation: roc.installation, + ManagementCluster: roc.managementCluster, + NonClusterHost: roc.nonClusterHost, + TunnelServerCert: tunnelSecret, + VoltronLinseedKeyPair: voltronLinseedKP, + InternalTLSKeyPair: internalTraffic, + ClusterDomain: dns.DefaultClusterDomain, + ESLicenseType: render.ElasticsearchLicenseTypeEnterpriseTrial, + Replicas: roc.installation.ControlPlaneReplicas, + Compliance: roc.compliance, + ComplianceLicenseActive: roc.complianceFeatureActive, + OpenShift: roc.openshift, + Namespace: roc.ns, + BindingNamespaces: roc.bindingNamespaces, + OSSTenantNamespaces: roc.ossBindingNamespaces, + Tenant: roc.tenant, + Manager: roc.manager, + ExternalElastic: roc.externalElastic, + RBACManagementLDAPConfigured: roc.rbacManagementLDAP, + CACertCommonName: certificateManager.CACertCommonName(), } if roc.tenant.MultiTenant() { From 58cef8561e762decca3f87c559fe5e906a9f9849 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 25 Jun 2026 17:41:52 -0700 Subject: [PATCH 10/30] imports(crds): regenerate enterprise felixconfigurations and ipamblocks bundles Refresh the bundled enterprise CRDs after rebasing onto the latest master. make gen-files picks up upstream schema changes: istioDSCPMark becomes int-or-string (anyOf integer/string) on felixconfigurations, and ipamblocks allocations drops a stale controller-gen TODO and reorders nullable/type. No operator code change; satisfies make dirty-check / validate-gen-versions. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../crd.projectcalico.org_felixconfigurations.yaml | 4 +++- .../crd.projectcalico.org_ipamblocks.yaml | 4 +--- .../projectcalico.org_felixconfigurations.yaml | 4 +++- .../v3.projectcalico.org/projectcalico.org_ipamblocks.yaml | 4 +--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml index cc05ab79b7..f88c63949c 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml @@ -1397,12 +1397,14 @@ spec: - Disabled type: string istioDSCPMark: + anyOf: + - type: integer + - type: string description: |- IstioDSCPMark sets the value to use when directing traffic to Istio ZTunnel, when Istio is enabled. The mark is set only on SYN packets at the final hop to avoid interference with other protocols. This value is reserved by Calico and must not be used with other Istio installation. [Default: 23] pattern: ^.* - type: integer x-kubernetes-int-or-string: true kubeMasqueradeBit: description: |- diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml index f6003b241c..d625dc1ab0 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml @@ -54,10 +54,8 @@ spec: For non-nil entries at index i, the index is the ordinal of the allocation within this block and the value is the index of the associated attributes in the Attributes array. items: - type: integer - # TODO: This nullable is manually added in. We should update controller-gen - # to handle []*int properly itself. nullable: true + type: integer type: array attributes: description: |- diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml index 8a759548e1..5aec3c5e2f 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml @@ -1396,12 +1396,14 @@ spec: - Disabled type: string istioDSCPMark: + anyOf: + - type: integer + - type: string description: |- IstioDSCPMark sets the value to use when directing traffic to Istio ZTunnel, when Istio is enabled. The mark is set only on SYN packets at the final hop to avoid interference with other protocols. This value is reserved by Calico and must not be used with other Istio installation. [Default: 23] pattern: ^.* - type: integer x-kubernetes-int-or-string: true kubeMasqueradeBit: description: |- diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml index d4bf6eb7cb..70cfbe995b 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml @@ -69,10 +69,8 @@ spec: For non-nil entries at index i, the index is the ordinal of the allocation within this block and the value is the index of the associated attributes in the Attributes array. items: - type: integer - # TODO: This nullable is manually added in. We should update controller-gen - # to handle []*int properly itself. nullable: true + type: integer type: array x-kubernetes-list-type: atomic attributes: From bb4bfc1325e604efe9f8c0eccb48bdcb898ef009 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Tue, 30 Jun 2026 11:37:19 -0700 Subject: [PATCH 11/30] api(manager): rename spec.rbac to spec.rbacUI.enabled (PR #4865 review) Reviewer noted that a field named `rbac` reads as configuration for the Manager's K8s RBAC rather than the RBAC management UI feature. Rename the top-level field and its type from `RBAC`/`spec.rbac` to `RBACUI`/`spec.rbacUI`, and the inner toggle from `ui` to `enabled` (a named `Enabled`/`Disabled` enum), so the API reads as `spec.rbacUI.enabled: Enabled|Disabled`. Keeping the wrapper struct leaves room for future RBAC-UI sub-configuration. Updates the RBACManagementEnabled() helper, the render unit tests, and the regenerated deepcopy and Manager CRD. Co-Authored-By: Claude Opus 4.8 (1M context) --- api/v1/manager_types.go | 26 +++++++++---------- api/v1/zz_generated.deepcopy.go | 14 +++++----- .../operator/operator.tigera.io_managers.yaml | 8 +++--- pkg/render/manager_test.go | 24 ++++++++++++----- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/api/v1/manager_types.go b/api/v1/manager_types.go index 3bf76c2500..00d8671cdf 100644 --- a/api/v1/manager_types.go +++ b/api/v1/manager_types.go @@ -27,36 +27,36 @@ type ManagerSpec struct { // +optional ManagerDeployment *ManagerDeployment `json:"managerDeployment,omitempty"` - // RBAC configures the RBAC management UI feature. + // RBACUI configures the RBAC management UI feature. // +optional - RBAC *RBAC `json:"rbac,omitempty"` + RBACUI *RBACUI `json:"rbacUI,omitempty"` } -// RBAC controls the RBAC management UI feature surface. -type RBAC struct { - // UI controls whether the RBAC management UI is enabled. Defaults to +// RBACUI controls the RBAC management UI feature surface. +type RBACUI struct { + // Enabled controls whether the RBAC management UI is enabled. Defaults to // Disabled. // +optional // +kubebuilder:validation:Enum=Enabled;Disabled - UI RBACUI `json:"ui,omitempty"` + Enabled RBACUIStatus `json:"enabled,omitempty"` } -// RBACUI toggles the RBAC management UI feature. -type RBACUI string +// RBACUIStatus toggles the RBAC management UI feature. +type RBACUIStatus string const ( - RBACUIEnabled RBACUI = "Enabled" - RBACUIDisabled RBACUI = "Disabled" + RBACUIEnabled RBACUIStatus = "Enabled" + RBACUIDisabled RBACUIStatus = "Disabled" ) // RBACManagementEnabled returns true when the Manager CR opts the cluster // into the RBAC management UI. Safe to call on a nil receiver; returns false -// for either a nil Manager or any UI value other than Enabled. +// for either a nil Manager or any value other than Enabled. func (m *Manager) RBACManagementEnabled() bool { - if m == nil || m.Spec.RBAC == nil { + if m == nil || m.Spec.RBACUI == nil { return false } - return m.Spec.RBAC.UI == RBACUIEnabled + return m.Spec.RBACUI.Enabled == RBACUIEnabled } // ManagerDeployment is the configuration for the Manager Deployment. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 34c3b08087..3c397299af 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -7917,9 +7917,9 @@ func (in *ManagerSpec) DeepCopyInto(out *ManagerSpec) { *out = new(ManagerDeployment) (*in).DeepCopyInto(*out) } - if in.RBAC != nil { - in, out := &in.RBAC, &out.RBAC - *out = new(RBAC) + if in.RBACUI != nil { + in, out := &in.RBACUI, &out.RBACUI + *out = new(RBACUI) **out = **in } } @@ -9017,16 +9017,16 @@ func (in *QueryServerLogging) DeepCopy() *QueryServerLogging { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RBAC) DeepCopyInto(out *RBAC) { +func (in *RBACUI) DeepCopyInto(out *RBACUI) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBAC. -func (in *RBAC) DeepCopy() *RBAC { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBACUI. +func (in *RBACUI) DeepCopy() *RBACUI { if in == nil { return nil } - out := new(RBAC) + out := new(RBACUI) in.DeepCopyInto(out) return out } diff --git a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml index 7e352ef27f..dd4f2608a8 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml @@ -298,12 +298,12 @@ spec: type: object type: object type: object - rbac: - description: RBAC configures the RBAC management UI feature. + rbacUI: + description: RBACUI configures the RBAC management UI feature. properties: - ui: + enabled: description: |- - UI controls whether the RBAC management UI is enabled. Defaults to + Enabled controls whether the RBAC management UI is enabled. Defaults to Disabled. enum: - Enabled diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index 5017b61221..b665db1341 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1775,13 +1775,25 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(rtest.GetResource(resources, render.ManagerClusterRole, render.ManagerNamespace, rbacv1.GroupName, "v1", "Role")).To(BeNil()) }) - It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbac.ui is Enabled", func() { + It("renders RBAC_UI_ENABLED=false and no namespaced RBAC UI role when the Manager exists but rbacUI is unset", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, + ns: render.ManagerNamespace, + manager: &operatorv1.Manager{Spec: operatorv1.ManagerSpec{}}, + }) + d := rtest.GetResource(resources, render.ManagerDeploymentName, render.ManagerNamespace, appsv1.GroupName, "v1", "Deployment").(*appsv1.Deployment) + Expect(rbacUIEnabledEnv(d)).To(Equal(corev1.EnvVar{Name: "RBAC_UI_ENABLED", Value: "false"})) + + Expect(rtest.GetResource(resources, render.ManagerClusterRole, render.ManagerNamespace, rbacv1.GroupName, "v1", "Role")).To(BeNil()) + }) + + It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbacUI.enabled is Enabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIEnabled}, + RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIEnabled}, }, }, }) @@ -1792,13 +1804,13 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(role.Rules).To(ContainElement(nsCreateRule)) }) - It("renders RBAC_UI_ENABLED=false when rbac.ui is Disabled", func() { + It("renders RBAC_UI_ENABLED=false when rbacUI.enabled is Disabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIDisabled}, + RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIDisabled}, }, }, }) @@ -1820,7 +1832,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIEnabled}, + RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIEnabled}, }, }, }) @@ -1837,7 +1849,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }, } rbacUIManager := &operatorv1.Manager{ - Spec: operatorv1.ManagerSpec{RBAC: &operatorv1.RBAC{UI: operatorv1.RBACUIEnabled}}, + Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIEnabled}}, } It("adds LDAP egress only when RBAC UI is enabled and the LDAP config secret is present", func() { From e4cb0e3a769ee2a6c3fa4f74dca96fd86f4b2f76 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:59:42 -0700 Subject: [PATCH 12/30] controller: GetManager returns nil on NotFound, surfaces NoMatchError as an error (PR #4865 review) Two related review points on how callers read the Manager CR: - GetManager now swallows IsNotFound and returns (nil, nil), matching the sibling GetManagementCluster helper, so callers branch on a nil instance instead of inspecting the error. - A NoMatchError (the Manager CRD is not registered yet) is propagated rather than treated as not-found. In enterprise the CRD is expected; hitting NoMatchError means it has not loaded yet, and we cannot infer the user's intent, so the installation and apiserver controllers now degrade and requeue instead of assuming the RBAC management UI is off. Callers simplified accordingly; the manager controller branches on `instance == nil` for the not-found path, and its unit test now asserts the (nil, nil) contract. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/apiserver/apiserver_controller.go | 9 +++++---- pkg/controller/installation/core_controller.go | 11 ++++++----- pkg/controller/manager/manager_controller.go | 10 +++++----- pkg/controller/manager/manager_controller_test.go | 4 ++-- pkg/controller/utils/utils.go | 13 +++++++++---- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/pkg/controller/apiserver/apiserver_controller.go b/pkg/controller/apiserver/apiserver_controller.go index 4379a9b60d..f339ec93d2 100644 --- a/pkg/controller/apiserver/apiserver_controller.go +++ b/pkg/controller/apiserver/apiserver_controller.go @@ -378,14 +378,15 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re return reconcile.Result{}, err } + // A nil managerCR (no Manager created) means callers fall back to their + // defaults. A NoMatchError (Manager CRD not registered) propagates as an + // error: in enterprise the CRD is expected, so we requeue rather than + // read the Manager as absent. managerCR, err = utils.GetManager(ctx, r.client, false, "") - if err != nil && !errors.IsNotFound(err) { + if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) return reconcile.Result{}, err } - if errors.IsNotFound(err) { - managerCR = nil - } if managementClusterConnection != nil && managementCluster != nil { err = fmt.Errorf("having both a ManagementCluster and a ManagementClusterConnection is not supported") diff --git a/pkg/controller/installation/core_controller.go b/pkg/controller/installation/core_controller.go index 2a00499c86..18a9ae0ff7 100644 --- a/pkg/controller/installation/core_controller.go +++ b/pkg/controller/installation/core_controller.go @@ -1080,15 +1080,16 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile return reconcile.Result{}, err } - // On Calico/OSS the Manager CRD is absent, so the read returns NoMatchError. + // We are in enterprise, so the Manager CRD is expected. A nil managerCR + // (no Manager created) means callers fall back to their defaults. A + // NoMatchError (Manager CRD not registered yet) is treated as an error: + // we don't know the user's intent until the CRD loads, so we requeue + // rather than read the Manager as absent. managerCR, err = utils.GetManager(ctx, r.client, false, "") - if err != nil && !apierrors.IsNotFound(err) && !meta.IsNoMatchError(err) { + if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) return reconcile.Result{}, err } - if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) { - managerCR = nil - } if managementClusterConnection != nil && managementCluster != nil { err = fmt.Errorf("having both a managementCluster and a managementClusterConnection is not supported") diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 471b488782..f55f1ac4ca 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -267,14 +267,14 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ // Fetch the Manager instance that corresponds with this reconcile trigger. instance, err := utils.GetManager(ctx, r.client, r.opts.MultiTenant, request.Namespace) if err != nil { - if errors.IsNotFound(err) { - logc.Info("Manager object not found") - r.status.OnCRNotFound() - return reconcile.Result{}, nil - } r.status.SetDegraded(operatorv1.ResourceReadError, "Error querying Manager", err, logc) return reconcile.Result{}, err } + if instance == nil { + logc.Info("Manager object not found") + r.status.OnCRNotFound() + return reconcile.Result{}, nil + } logc.V(2).Info("Loaded config", "config", instance) r.status.OnCRFound() diff --git a/pkg/controller/manager/manager_controller_test.go b/pkg/controller/manager/manager_controller_test.go index 7f747ee372..b3960fa4c7 100644 --- a/pkg/controller/manager/manager_controller_test.go +++ b/pkg/controller/manager/manager_controller_test.go @@ -116,10 +116,10 @@ var _ = Describe("Manager controller tests", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should return expected error when querying namespace that does not contain a manager instance", func() { + It("should return a nil instance and no error when querying a namespace that does not contain a manager instance", func() { nsWithoutManager := "non-manager-ns" instance, err := utils.GetManager(ctx, c, true, nsWithoutManager) - Expect(kerror.IsNotFound(err)).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) Expect(instance).To(BeNil()) }) diff --git a/pkg/controller/utils/utils.go b/pkg/controller/utils/utils.go index 3f3b295c1e..9405b93206 100644 --- a/pkg/controller/utils/utils.go +++ b/pkg/controller/utils/utils.go @@ -487,10 +487,12 @@ func GetIstio(ctx context.Context, c client.Client) (*operatorv1.Istio, error) { return istio, nil } -// GetManager returns the Manager CR. When multiTenant is true the -// tenant-scoped instance is read from ns; otherwise the cluster-scoped -// instance is read and ns is ignored. Returns the underlying client error -// (including IsNotFound) — callers decide whether absence is fatal. +// GetManager returns the Manager CR, or nil if it is not found. When +// multiTenant is true the tenant-scoped instance is read from ns; otherwise the +// cluster-scoped instance is read and ns is ignored. A NoMatchError (the +// Manager CRD is not registered) is returned to the caller rather than treated +// as not-found: absence of the CRD is distinct from the user not having created +// a Manager, and the caller decides how to handle it. func GetManager(ctx context.Context, cli client.Client, multiTenant bool, ns string) (*operatorv1.Manager, error) { key := DefaultEnterpriseInstanceKey if multiTenant { @@ -498,6 +500,9 @@ func GetManager(ctx context.Context, cli client.Client, multiTenant bool, ns str } instance := &operatorv1.Manager{} if err := cli.Get(ctx, key, instance); err != nil { + if errors.IsNotFound(err) { + return nil, nil + } return nil, err } return instance, nil From 26b06e41e2f9725262c5bc140a30187c45610426 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:59:21 -0700 Subject: [PATCH 13/30] render(rbac): scope rbacsync secret access to calico-system and comment its rules (PR #4865 review) Addresses two review points on the rbacsync controller RBAC: - Scope down the secrets rule: the controller's cluster-scoped `secrets: get/create/patch` is replaced by named/namespaced access in the existing rbacSyncIDPGroupsRole (calico-system) -- named get/patch on the tigera-idp-ldap-config secret, with create kept broad within the namespace since create cannot be name-scoped. - Comment the remaining cluster-scoped rules (compliances read, pods list for leader election) and document why the escalation rules belong on this SA: rbacsync acts as its own identity, unlike the UI which impersonates the user, so it must hold every permission it grants. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../kubecontrollers/kube-controllers.go | 19 ++++++++------ pkg/render/rbac_management.go | 26 ++++++++++++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index d5255715b2..0b3f3154f2 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -673,7 +673,8 @@ func kubeControllersRoleEnterpriseCommonRules(cfg *KubeControllersConfiguration) } // rbacSyncIDPGroupsRole returns the Role + RoleBinding that grants rbacsync -// read on the tigera-idp-groups ConfigMap in calico-system. +// read access to the tigera-idp-groups ConfigMap in calico-system, its only +// namespaced dependency. func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { name := "calico-kube-controllers-rbac-sync" return []client.Object{ @@ -708,22 +709,24 @@ func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { } } -// rbacSyncControllerRules returns the extra rules the rbacsync controller -// needs on top of render.RBACManagementEscalationRules. +// rbacSyncControllerRules returns the cluster-scoped rules the rbacsync +// controller holds. The rbacsync controller writes the managed RBAC roles as +// its own ServiceAccount, and Kubernetes rejects a write to a Role or +// ClusterRole as privilege escalation unless the writer already holds every +// permission that role grants. The shared RBACManagementEscalationRules supply +// most of that coverage; the rules appended here cover the rest of what the +// managed roles grant. func rbacSyncControllerRules() []rbacv1.PolicyRule { rules := render.RBACManagementEscalationRules() return append(rules, rbacv1.PolicyRule{ + // Held to cover the compliances:get the managed roles grant. APIGroups: []string{"operator.tigera.io"}, Resources: []string{"compliances"}, Verbs: []string{"get", "list"}, }, rbacv1.PolicyRule{ - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"get", "create", "patch"}, - }, - rbacv1.PolicyRule{ + // Held to cover the pods:list the managed roles grant. APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, diff --git a/pkg/render/rbac_management.go b/pkg/render/rbac_management.go index 7ed3831087..7895dd4189 100644 --- a/pkg/render/rbac_management.go +++ b/pkg/render/rbac_management.go @@ -16,10 +16,13 @@ package render import rbacv1 "k8s.io/api/rbac/v1" -// RBACManagementEscalationRules returns the rules shared by the calico-manager -// and calico-kube-controllers roles for the RBAC management UI. The SA writing -// a managed role must already hold every permission it grants, else K8s rejects -// the write as privilege escalation; keep this aligned with the managed roles. +// RBACManagementEscalationRules returns the permission set the rbacsync +// controller (in calico-kube-controllers) holds so it can write the RBAC +// management UI's managed roles. The rbacsync controller acts as its own +// ServiceAccount, and Kubernetes rejects a write to a Role or ClusterRole as +// privilege escalation unless the writer already holds every permission that +// role grants. This set is therefore a superset of the rules in those managed +// roles; the managed roles themselves are defined in calico/kube-controllers. func RBACManagementEscalationRules() []rbacv1.PolicyRule { return []rbacv1.PolicyRule{ // Full CRUD on the RBAC objects the feature writes. @@ -81,6 +84,21 @@ func RBACManagementEscalationRules() []rbacv1.PolicyRule { }, Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, }, + // The managed Webhooks role grants creating the webhook backing Secret and + // patching webhooks-secret, so the writer must hold the same to pass the + // escalation check. create cannot be name-scoped (the name is in the + // request body, not the URL path), so it is cluster-wide here. + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{"webhooks-secret"}, + Verbs: []string{"patch"}, + }, // LMA log roles' scope verb (per-cluster log access roles). { APIGroups: []string{"lma.tigera.io"}, From 5e4bcd5726a861a82801e2c8f9af312afd2c8e79 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:58:00 -0700 Subject: [PATCH 14/30] render(rbac): describe the RBAC UI as not-multi-tenant in tenancy comments (PR #4865 review) The RBAC management UI is rendered on every management cluster except multi-tenant ones (the gate is `!Tenant.MultiTenant()`, which covers both the zero-tenant and single-tenant cases). Word the manager cluster-role gate comment and the manager controller LDAP egress comment to match that gate rather than naming a single tenancy mode. Leaves the pre-existing generic tenancy-mode descriptions untouched. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/manager/manager_controller.go | 3 ++- pkg/render/manager.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index f55f1ac4ca..b467d50a7a 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -682,7 +682,8 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ } // Presence of the LDAP config Secret (always calico-system; the feature is - // zero-tenant) gates the manager's LDAP egress policy below. + // not supported on multi-tenant clusters) gates the manager's LDAP egress + // policy. ldapConfigSecret, err := utils.GetSecret(ctx, r.client, render.RBACManagementLDAPConfigSecretName, common.CalicoNamespace) if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading RBAC management LDAP config secret", err, logc) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 864593c8e6..51036ca9f7 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -1176,8 +1176,9 @@ func managerClusterRole(managedCluster bool, kubernetesProvider operatorv1.Provi }, } - // Zero-tenant-only. Must match the rbacManagementUINamespacedRole gate so - // the SA never gets these cluster rules without the namespaced grant. + // Not rendered on multi-tenant management clusters. Keep this condition in + // sync with the rbacManagementUINamespacedRole gate; the cluster rules and + // the namespaced grant are rendered together. if rbacManagementEnabled && !tenant.MultiTenant() { cr.Rules = append(cr.Rules, rbacManagementUIRules()...) } From b5c1b218f37f7059d459c73ea5147f47e4a9f5a1 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:58:55 -0700 Subject: [PATCH 15/30] render(rbac): scope the RBAC-UI namespaced secret patch to named resources (PR #4865 review) The calico-system Role for the RBAC management UI carried a broad `secrets: patch` rule. Reviewer noted patch can take a resource name, so fold patch into the two named-resource rules (tigera-idp-ldap-config and tigera-idp-groups) instead. The create rule stays broad within the namespace, since create cannot be scoped to a not-yet-existing object. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/manager.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 51036ca9f7..fe7f76bfb2 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -1241,26 +1241,24 @@ func (c *managerComponent) rbacManagementUINamespacedRole() []client.Object { ObjectMeta: metav1.ObjectMeta{Name: ManagerClusterRole, Namespace: common.CalicoNamespace}, Rules: []rbacv1.PolicyRule{ { + // create carries the object name in the request body, not the + // URL path, so RBAC cannot restrict it by resource name; it is + // scoped to this namespace instead. APIGroups: []string{""}, Resources: []string{"configmaps", "secrets"}, Verbs: []string{"create"}, }, - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"patch"}, - }, { APIGroups: []string{""}, Resources: []string{"secrets"}, ResourceNames: []string{RBACManagementLDAPConfigSecretName}, - Verbs: []string{"get", "list", "watch", "update", "delete"}, + Verbs: []string{"get", "list", "watch", "update", "patch", "delete"}, }, { APIGroups: []string{""}, Resources: []string{"configmaps"}, ResourceNames: []string{"tigera-idp-groups"}, - Verbs: []string{"get", "list", "watch", "update", "delete"}, + Verbs: []string{"get", "list", "watch", "update", "patch", "delete"}, }, }, }, From b614cbff5e2dc543b4d63df354317c392a1f8359 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:59:10 -0700 Subject: [PATCH 16/30] render(rbac): drop escalation rules from the manager SA, keep only compliances:get (PR #4865 review) rbacManagementUIRules added the shared RBACManagementEscalationRules to the calico-manager cluster role. Reviewer pointed out the RBAC management UI writes managed RBAC objects via user impersonation (Voltron impersonates the caller), so the privilege-escalation check runs against the impersonated user, not the manager SA -- making those rules dead weight on this SA. The escalation rules remain on the rbacsync controller SA, which acts as its own identity. The manager SA keeps only the compliances:get rule that ui-apis itself consumes. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/manager.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index fe7f76bfb2..0eba1f5a0e 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -1221,14 +1221,16 @@ func managerClusterRole(managedCluster bool, kubernetesProvider operatorv1.Provi // UI adds to calico-manager-role. Named-resource access is scoped separately // on rbacManagementUINamespacedRole. func rbacManagementUIRules() []rbacv1.PolicyRule { - rules := RBACManagementEscalationRules() - return append(rules, - rbacv1.PolicyRule{ + return []rbacv1.PolicyRule{ + { + // Lets ui-apis read the Compliance CR (the operator singleton) so the + // UI can tell whether compliance is installed before offering + // compliance-scoped RBAC. APIGroups: []string{"operator.tigera.io"}, Resources: []string{"compliances"}, Verbs: []string{"get"}, }, - ) + } } // rbacManagementUINamespacedRole returns the Role + RoleBinding that scopes From dd12844ea07cec23e6c88163087bc08c19559afd Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:59:53 -0700 Subject: [PATCH 17/30] render(manager): drop redundant OIDC LDAP-egress regression test (PR #4865 review) This case asserted that OIDC being configured does not by itself open LDAP egress. Now that the egress gate keys solely on the LDAP config secret (not on Authentication.Spec.OIDC), the oidc input is irrelevant and the case duplicates the "secret absent" test directly above it. Reviewer noted it is not really a regression case; remove it. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/manager_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index b665db1341..a133457caa 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1870,17 +1870,6 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) }) - It("omits LDAP egress when OIDC is configured but the LDAP config secret is absent", func() { - // Regression guard: the gate must key on the LDAP config secret, - // not on Authentication.Spec.OIDC (which never drives an LDAP dial). - resources, _ := renderObjects(renderConfig{ - installation: installation, ns: render.ManagerNamespace, - manager: rbacUIManager, oidc: true, rbacManagementLDAP: false, - }) - policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) - Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) - }) - It("omits LDAP egress when the LDAP secret is present but RBAC UI is disabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, From 0089629a969715861659e1e951e869d34b4e2a81 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Mon, 29 Jun 2026 17:58:24 -0700 Subject: [PATCH 18/30] render(manager): gate RBAC_UI_ENABLED on non-multi-tenant clusters (PR #4865 review) The ui-apis RBAC_UI_ENABLED env was set from Manager.RBACManagementEnabled() alone, but every resource backing the feature is gated on `enabled && !MultiTenant()`. On a multi-tenant management cluster the container would advertise the feature with no supporting RBAC behind it. Gate the env value the same way so the backing resources exist whenever ui-apis reports the feature on. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/manager.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 0eba1f5a0e..0f5c654a3b 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -759,7 +759,11 @@ func (c *managerComponent) managerUIAPIsContainer() corev1.Container { {Name: "LINSEED_CLIENT_KEY", Value: keyPath}, {Name: "ELASTIC_KIBANA_DISABLED", Value: strconv.FormatBool(c.cfg.Tenant.MultiTenant())}, {Name: "VOLTRON_URL", Value: ManagerService(c.cfg.Tenant)}, - {Name: "RBAC_UI_ENABLED", Value: strconv.FormatBool(c.cfg.Manager.RBACManagementEnabled())}, + // The RBAC management UI is not supported on multi-tenant management + // clusters, matching the gate on its supporting RBAC (see + // managerClusterRole / rbacManagementUINamespacedRole), so the backing + // resources exist whenever ui-apis advertises the feature. + {Name: "RBAC_UI_ENABLED", Value: strconv.FormatBool(c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant())}, } // Determine the Linseed location. Use code default unless in multi-tenant mode, From 7c92e79cc4616a211b9c538ea0cbd20196d7a54e Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Tue, 30 Jun 2026 16:07:02 -0700 Subject: [PATCH 19/30] imports(crds): regenerate calico and enterprise CRD bundles from master Sync the bundled CRDs with the current projectcalico/calico and tigera/calico-private master sources via `make gen-versions`. Upstream dropped the deprecated `preserveUnknownFields: false` field from the CRDs and updated felixconfigurations/ipamblocks schemas; this regenerates the bundle to match so `make validate-gen-versions` / dirty-check passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../crd.projectcalico.org_bgpconfigurations.yaml | 1 - .../crd.projectcalico.org_bgpfilters.yaml | 1 - .../crd.projectcalico.org_bgppeers.yaml | 1 - .../crd.projectcalico.org_blockaffinities.yaml | 1 - .../crd.projectcalico.org_caliconodestatuses.yaml | 1 - .../crd.projectcalico.org_clusterinformations.yaml | 1 - .../crd.projectcalico.org_felixconfigurations.yaml | 3 +-- .../crd.projectcalico.org_globalnetworkpolicies.yaml | 1 - .../crd.projectcalico.org_globalnetworksets.yaml | 1 - .../crd.projectcalico.org_hostendpoints.yaml | 1 - .../crd.projectcalico.org_ipamblocks.yaml | 1 - .../crd.projectcalico.org_ipamconfigs.yaml | 1 - .../crd.projectcalico.org_ipamhandles.yaml | 1 - .../crd.projectcalico.org_ippools.yaml | 1 - .../crd.projectcalico.org_ipreservations.yaml | 1 - ...rojectcalico.org_kubecontrollersconfigurations.yaml | 1 - .../crd.projectcalico.org_networkpolicies.yaml | 1 - .../crd.projectcalico.org_networksets.yaml | 1 - ....projectcalico.org_stagedglobalnetworkpolicies.yaml | 1 - ...jectcalico.org_stagedkubernetesnetworkpolicies.yaml | 1 - .../crd.projectcalico.org_stagednetworkpolicies.yaml | 1 - .../crd.projectcalico.org_tiers.yaml | 1 - .../projectcalico.org_bgpconfigurations.yaml | 1 - .../projectcalico.org_bgpfilters.yaml | 1 - .../projectcalico.org_bgppeers.yaml | 1 - .../projectcalico.org_blockaffinities.yaml | 1 - .../projectcalico.org_caliconodestatuses.yaml | 1 - .../projectcalico.org_clusterinformations.yaml | 1 - .../projectcalico.org_felixconfigurations.yaml | 3 +-- .../projectcalico.org_globalnetworkpolicies.yaml | 1 - .../projectcalico.org_globalnetworksets.yaml | 1 - .../projectcalico.org_hostendpoints.yaml | 1 - .../projectcalico.org_ipamblocks.yaml | 1 - .../projectcalico.org_ipamconfigurations.yaml | 1 - .../projectcalico.org_ipamhandles.yaml | 1 - .../projectcalico.org_ippools.yaml | 1 - .../projectcalico.org_ipreservations.yaml | 1 - ...rojectcalico.org_kubecontrollersconfigurations.yaml | 1 - .../projectcalico.org_networkpolicies.yaml | 1 - .../projectcalico.org_networksets.yaml | 1 - .../projectcalico.org_stagedglobalnetworkpolicies.yaml | 1 - ...jectcalico.org_stagedkubernetesnetworkpolicies.yaml | 1 - .../projectcalico.org_stagednetworkpolicies.yaml | 1 - .../v3.projectcalico.org/projectcalico.org_tiers.yaml | 1 - ...cationlayer.projectcalico.org_globalwafplugins.yaml | 1 - ...ationlayer.projectcalico.org_globalwafpolicies.yaml | 1 - ....projectcalico.org_globalwafvalidationpolicies.yaml | 1 - .../applicationlayer.projectcalico.org_wafplugins.yaml | 1 - ...applicationlayer.projectcalico.org_wafpolicies.yaml | 1 - ...nlayer.projectcalico.org_wafvalidationpolicies.yaml | 1 - .../crd.projectcalico.org_alertexceptions.yaml | 1 - .../crd.projectcalico.org_bfdconfigurations.yaml | 1 - .../crd.projectcalico.org_bgpconfigurations.yaml | 1 - .../crd.projectcalico.org_bgpfilters.yaml | 1 - .../crd.projectcalico.org_bgppeers.yaml | 1 - .../crd.projectcalico.org_blockaffinities.yaml | 1 - .../crd.projectcalico.org_caliconodestatuses.yaml | 1 - .../crd.projectcalico.org_clusterinformations.yaml | 1 - .../crd.projectcalico.org_deeppacketinspections.yaml | 1 - .../crd.projectcalico.org_egressgatewaypolicies.yaml | 1 - .../crd.projectcalico.org_externalnetworks.yaml | 1 - .../crd.projectcalico.org_felixconfigurations.yaml | 1 - .../crd.projectcalico.org_globalalerts.yaml | 1 - .../crd.projectcalico.org_globalalerttemplates.yaml | 1 - .../crd.projectcalico.org_globalnetworkpolicies.yaml | 1 - .../crd.projectcalico.org_globalnetworksets.yaml | 1 - .../crd.projectcalico.org_globalreports.yaml | 1 - .../crd.projectcalico.org_globalreporttypes.yaml | 1 - .../crd.projectcalico.org_globalthreatfeeds.yaml | 1 - .../crd.projectcalico.org_hostendpoints.yaml | 1 - .../crd.projectcalico.org_ipamblocks.yaml | 8 +++++++- .../crd.projectcalico.org_ipamconfigs.yaml | 9 ++++++++- .../crd.projectcalico.org_ipamhandles.yaml | 1 - .../crd.projectcalico.org_ippools.yaml | 1 - .../crd.projectcalico.org_ipreservations.yaml | 1 - ...rojectcalico.org_kubecontrollersconfigurations.yaml | 1 - .../crd.projectcalico.org_licensekeys.yaml | 1 - .../crd.projectcalico.org_managedclusters.yaml | 1 - .../crd.projectcalico.org_networkpolicies.yaml | 1 - .../crd.projectcalico.org_networks.yaml | 1 - .../crd.projectcalico.org_networksets.yaml | 1 - .../crd.projectcalico.org_packetcaptures.yaml | 1 - ...d.projectcalico.org_policyrecommendationscopes.yaml | 1 - ....projectcalico.org_remoteclusterconfigurations.yaml | 1 - .../crd.projectcalico.org_securityeventwebhooks.yaml | 1 - ....projectcalico.org_stagedglobalnetworkpolicies.yaml | 1 - ...jectcalico.org_stagedkubernetesnetworkpolicies.yaml | 1 - .../crd.projectcalico.org_stagednetworkpolicies.yaml | 1 - .../crd.projectcalico.org_tiers.yaml | 1 - .../crd.projectcalico.org_uisettings.yaml | 1 - .../crd.projectcalico.org_uisettingsgroups.yaml | 1 - .../usage.tigera.io_licenseusagereports.yaml | 1 - .../projectcalico.org_alertexceptions.yaml | 1 - .../projectcalico.org_bfdconfigurations.yaml | 1 - .../projectcalico.org_bgpconfigurations.yaml | 1 - .../projectcalico.org_bgpfilters.yaml | 1 - .../projectcalico.org_bgppeers.yaml | 1 - .../projectcalico.org_blockaffinities.yaml | 1 - .../projectcalico.org_caliconodestatuses.yaml | 1 - .../projectcalico.org_clusterinformations.yaml | 1 - .../projectcalico.org_deeppacketinspections.yaml | 1 - .../projectcalico.org_egressgatewaypolicies.yaml | 1 - .../projectcalico.org_externalnetworks.yaml | 1 - .../projectcalico.org_felixconfigurations.yaml | 1 - .../projectcalico.org_globalalerts.yaml | 1 - .../projectcalico.org_globalalerttemplates.yaml | 1 - .../projectcalico.org_globalnetworkpolicies.yaml | 1 - .../projectcalico.org_globalnetworksets.yaml | 1 - .../projectcalico.org_globalreports.yaml | 1 - .../projectcalico.org_globalreporttypes.yaml | 1 - .../projectcalico.org_globalthreatfeeds.yaml | 1 - .../projectcalico.org_hostendpoints.yaml | 1 - .../projectcalico.org_ipamblocks.yaml | 8 +++++++- .../projectcalico.org_ipamconfigurations.yaml | 10 +++++++++- .../projectcalico.org_ipamhandles.yaml | 1 - .../projectcalico.org_ippools.yaml | 1 - .../projectcalico.org_ipreservations.yaml | 1 - ...rojectcalico.org_kubecontrollersconfigurations.yaml | 1 - .../projectcalico.org_licensekeys.yaml | 1 - .../projectcalico.org_managedclusters.yaml | 1 - .../projectcalico.org_networkpolicies.yaml | 1 - .../projectcalico.org_networks.yaml | 1 - .../projectcalico.org_networksets.yaml | 1 - .../projectcalico.org_packetcaptures.yaml | 1 - .../projectcalico.org_policyrecommendationscopes.yaml | 1 - .../projectcalico.org_remoteclusterconfigurations.yaml | 1 - .../projectcalico.org_securityeventwebhooks.yaml | 1 - .../projectcalico.org_stagedglobalnetworkpolicies.yaml | 1 - ...jectcalico.org_stagedkubernetesnetworkpolicies.yaml | 1 - .../projectcalico.org_stagednetworkpolicies.yaml | 1 - .../v3.projectcalico.org/projectcalico.org_tiers.yaml | 1 - .../projectcalico.org_uisettings.yaml | 1 - .../projectcalico.org_uisettingsgroups.yaml | 1 - .../usage.tigera.io_licenseusagereports.yaml | 1 - 134 files changed, 33 insertions(+), 136 deletions(-) diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml index 610ead12b6..0d4ed1fa83 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPConfigurationList plural: bgpconfigurations singular: bgpconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml index 102c9a5aa7..f78c2d13b1 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPFilterList plural: bgpfilters singular: bgpfilter - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml index d022d314cb..734d550ed2 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPPeerList plural: bgppeers singular: bgppeer - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml index e3a3faf635..16a8480670 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml @@ -11,7 +11,6 @@ spec: listKind: BlockAffinityList plural: blockaffinities singular: blockaffinity - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml index 05b627f171..4f3ee58405 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml @@ -11,7 +11,6 @@ spec: listKind: CalicoNodeStatusList plural: caliconodestatuses singular: caliconodestatus - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml index 0f792329e7..d3aabcbab9 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml @@ -11,7 +11,6 @@ spec: listKind: ClusterInformationList plural: clusterinformations singular: clusterinformation - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml index 85623d87e4..7bcd372618 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: FelixConfigurationList plural: felixconfigurations singular: felixconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 @@ -1191,7 +1190,7 @@ spec: prometheusMetricsClientAuth: description: |- PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. - This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + This determines how the server validates client certificates. Default is "NoClientCert". type: string prometheusMetricsEnabled: description: diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml index 1ef49017b1..498e2822cd 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalNetworkPolicyList plural: globalnetworkpolicies singular: globalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml index 7c3cd530d3..71241bcfd5 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalNetworkSetList plural: globalnetworksets singular: globalnetworkset - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml index 375c82431a..83fc3c3a5d 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml @@ -11,7 +11,6 @@ spec: listKind: HostEndpointList plural: hostendpoints singular: hostendpoint - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml index 2e772ee8f6..018e1996ca 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMBlockList plural: ipamblocks singular: ipamblock - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml index 377593615d..09bb2adf29 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMConfigList plural: ipamconfigs singular: ipamconfig - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml index d80a2d2e5d..3816fd731d 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMHandleList plural: ipamhandles singular: ipamhandle - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml index 186575493e..169c0d1b72 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml @@ -11,7 +11,6 @@ spec: listKind: IPPoolList plural: ippools singular: ippool - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml index 22991934d4..1c69e562cb 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml @@ -11,7 +11,6 @@ spec: listKind: IPReservationList plural: ipreservations singular: ipreservation - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml index db0d85b85e..4408ad5bf8 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: KubeControllersConfigurationList plural: kubecontrollersconfigurations singular: kubecontrollersconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml index 21dc5b937f..078ae30237 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkPolicyList plural: networkpolicies singular: networkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml index 173756dd71..e657429082 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkSetList plural: networksets singular: networkset - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml index 259384b753..5f8137a7bc 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: StagedGlobalNetworkPolicyList plural: stagedglobalnetworkpolicies singular: stagedglobalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml index e5851adbd1..e6c0f7132f 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: StagedKubernetesNetworkPolicyList plural: stagedkubernetesnetworkpolicies singular: stagedkubernetesnetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml index 5c37ddac03..c2992279fb 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: StagedNetworkPolicyList plural: stagednetworkpolicies singular: stagednetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml index b2567ece66..deb3fbaa4f 100644 --- a/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml +++ b/pkg/imports/crds/calico/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml @@ -11,7 +11,6 @@ spec: listKind: TierList plural: tiers singular: tier - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml index ebf7a35a8c..5a6063c10f 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml @@ -14,7 +14,6 @@ spec: - bgpconfig - bgpconfigs singular: bgpconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml index f734bf8a5e..d57f10aebb 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPFilterList plural: bgpfilters singular: bgpfilter - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgppeers.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgppeers.yaml index f7695183e4..1141236788 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgppeers.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_bgppeers.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPPeerList plural: bgppeers singular: bgppeer - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml index d95679581a..60e6e5f67e 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml @@ -14,7 +14,6 @@ spec: - affinity - affinities singular: blockaffinity - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml index d52d68e9f8..929ec8a9e4 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml @@ -11,7 +11,6 @@ spec: listKind: CalicoNodeStatusList plural: caliconodestatuses singular: caliconodestatus - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml index 8b83de58ef..46cddc1f92 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml @@ -14,7 +14,6 @@ spec: - clusterinfo - clusterinfos singular: clusterinformation - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml index 77342d58c8..fbea5d30e7 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: FelixConfigurationList plural: felixconfigurations singular: felixconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 @@ -1190,7 +1189,7 @@ spec: prometheusMetricsClientAuth: description: |- PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. - This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + This determines how the server validates client certificates. Default is "NoClientCert". type: string prometheusMetricsEnabled: description: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml index 61714163ce..6db6a1c661 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml @@ -14,7 +14,6 @@ spec: - gnp - cgnp singular: globalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml index 79624494a7..17717dc9b3 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml @@ -13,7 +13,6 @@ spec: shortNames: - gns singular: globalnetworkset - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml index 3f55368288..176a837703 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml @@ -14,7 +14,6 @@ spec: - hep - heps singular: hostendpoint - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml index 927febc51b..e7b4a5b293 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMBlockList plural: ipamblocks singular: ipamblock - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml index c67ca32418..2eac4f5baf 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml @@ -14,7 +14,6 @@ spec: - ipamconfig - ipamconfigs singular: ipamconfiguration - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml index b1b75eae59..0e77395a85 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMHandleList plural: ipamhandles singular: ipamhandle - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ippools.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ippools.yaml index 435dbf1cb5..c082728455 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ippools.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ippools.yaml @@ -11,7 +11,6 @@ spec: listKind: IPPoolList plural: ippools singular: ippool - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipreservations.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipreservations.yaml index dc522cdd8a..e4d2af0a7f 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipreservations.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_ipreservations.yaml @@ -11,7 +11,6 @@ spec: listKind: IPReservationList plural: ipreservations singular: ipreservation - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml index 5cbc88076c..3e8fd4a0b6 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml @@ -14,7 +14,6 @@ spec: - kcc - kccs singular: kubecontrollersconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml index f71d867edf..0ab66e503d 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml @@ -14,7 +14,6 @@ spec: - cnp - caliconetworkpolicy singular: networkpolicy - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networksets.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networksets.yaml index 6ddf977c32..5e561184aa 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networksets.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_networksets.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkSetList plural: networksets singular: networkset - preserveUnknownFields: false scope: Namespaced versions: - name: v3 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml index ef3f9087d5..0744bde5f3 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - sgnp singular: stagedglobalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml index 5381cfa7fd..2b21474b9f 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - sknp singular: stagedkubernetesnetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v3 diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml index a916ccbe36..11eba2f2e5 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml @@ -14,7 +14,6 @@ spec: - scnp - snp singular: stagednetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_tiers.yaml b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_tiers.yaml index 0136cb1c95..abd4aeabc9 100644 --- a/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_tiers.yaml +++ b/pkg/imports/crds/calico/v3.projectcalico.org/projectcalico.org_tiers.yaml @@ -11,7 +11,6 @@ spec: listKind: TierList plural: tiers singular: tier - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafplugins.yaml b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafplugins.yaml index 66ace9e0e7..6d7ce3d75c 100644 --- a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafplugins.yaml +++ b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafplugins.yaml @@ -13,7 +13,6 @@ spec: shortNames: - gwafplugin singular: globalwafplugin - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafpolicies.yaml b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafpolicies.yaml index 966ca50f3e..8298fce200 100644 --- a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafpolicies.yaml +++ b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - gwafp singular: globalwafpolicy - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafvalidationpolicies.yaml b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafvalidationpolicies.yaml index b2007a4b17..40cfd0ac71 100644 --- a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafvalidationpolicies.yaml +++ b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_globalwafvalidationpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - gwafvp singular: globalwafvalidationpolicy - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafplugins.yaml b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafplugins.yaml index d88e6646c9..fa8b562fad 100644 --- a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafplugins.yaml +++ b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafplugins.yaml @@ -13,7 +13,6 @@ spec: shortNames: - wafplugin singular: wafplugin - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafpolicies.yaml b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafpolicies.yaml index 8b476229e6..5537bcbf1d 100644 --- a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafpolicies.yaml +++ b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - wafp singular: wafpolicy - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafvalidationpolicies.yaml b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafvalidationpolicies.yaml index d1f2ec91d9..489f8fa1b2 100644 --- a/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafvalidationpolicies.yaml +++ b/pkg/imports/crds/enterprise/applicationlayer.projectcalico.org/applicationlayer.projectcalico.org_wafvalidationpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - wafvp singular: wafvalidationpolicy - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_alertexceptions.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_alertexceptions.yaml index 9fefbc9722..5a1335ae4d 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_alertexceptions.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_alertexceptions.yaml @@ -11,7 +11,6 @@ spec: listKind: AlertExceptionList plural: alertexceptions singular: alertexception - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bfdconfigurations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bfdconfigurations.yaml index 174ff7d72a..58508c51ad 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bfdconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bfdconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: BFDConfigurationList plural: bfdconfigurations singular: bfdconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml index a555861dbd..51f725b16d 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPConfigurationList plural: bgpconfigurations singular: bgpconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml index 102c9a5aa7..f78c2d13b1 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgpfilters.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPFilterList plural: bgpfilters singular: bgpfilter - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml index 1b70cb0318..af0adc7235 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_bgppeers.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPPeerList plural: bgppeers singular: bgppeer - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml index e3a3faf635..16a8480670 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_blockaffinities.yaml @@ -11,7 +11,6 @@ spec: listKind: BlockAffinityList plural: blockaffinities singular: blockaffinity - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml index 05b627f171..4f3ee58405 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_caliconodestatuses.yaml @@ -11,7 +11,6 @@ spec: listKind: CalicoNodeStatusList plural: caliconodestatuses singular: caliconodestatus - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml index 8ceb6a8bfa..f7bff032a9 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_clusterinformations.yaml @@ -11,7 +11,6 @@ spec: listKind: ClusterInformationList plural: clusterinformations singular: clusterinformation - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_deeppacketinspections.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_deeppacketinspections.yaml index 34b6c300af..f29f99624b 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_deeppacketinspections.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_deeppacketinspections.yaml @@ -11,7 +11,6 @@ spec: listKind: DeepPacketInspectionList plural: deeppacketinspections singular: deeppacketinspection - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_egressgatewaypolicies.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_egressgatewaypolicies.yaml index 1e9e5462c8..eb997b898c 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_egressgatewaypolicies.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_egressgatewaypolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: EgressGatewayPolicyList plural: egressgatewaypolicies singular: egressgatewaypolicy - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_externalnetworks.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_externalnetworks.yaml index 987f82f633..adc155ed0b 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_externalnetworks.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_externalnetworks.yaml @@ -11,7 +11,6 @@ spec: listKind: ExternalNetworkList plural: externalnetworks singular: externalnetwork - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml index f88c63949c..7e9996e066 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_felixconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: FelixConfigurationList plural: felixconfigurations singular: felixconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerts.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerts.yaml index 2bf83b8060..f2a03b345d 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerts.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerts.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalAlertList plural: globalalerts singular: globalalert - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerttemplates.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerttemplates.yaml index 7f24a5b074..4e20d4eb2a 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerttemplates.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalalerttemplates.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalAlertTemplateList plural: globalalerttemplates singular: globalalerttemplate - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml index c4b9129a10..6ed943eaa1 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalNetworkPolicyList plural: globalnetworkpolicies singular: globalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml index 472eaa6c3d..0e4f4bf011 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalnetworksets.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalNetworkSetList plural: globalnetworksets singular: globalnetworkset - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreports.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreports.yaml index 53c7e53e23..7fe813ea75 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreports.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreports.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalReportList plural: globalreports singular: globalreport - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreporttypes.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreporttypes.yaml index 04ecdae290..ba16596e02 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreporttypes.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalreporttypes.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalReportTypeList plural: globalreporttypes singular: globalreporttype - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalthreatfeeds.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalthreatfeeds.yaml index 6522f98f8c..95dd3d2d74 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalthreatfeeds.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_globalthreatfeeds.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalThreatFeedList plural: globalthreatfeeds singular: globalthreatfeed - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml index 375c82431a..83fc3c3a5d 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_hostendpoints.yaml @@ -11,7 +11,6 @@ spec: listKind: HostEndpointList plural: hostendpoints singular: hostendpoint - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml index d625dc1ab0..018e1996ca 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamblocks.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMBlockList plural: ipamblocks singular: ipamblock - preserveUnknownFields: false scope: Cluster versions: - name: v1 @@ -74,6 +73,13 @@ spec: handle_id: description: HandleID is the primary identifier for the allocation. type: string + releasedAt: + description: |- + ReleasedAt is the time this allocation was released, and is set during the allocation's + "cooldown" phase. After `IPCooldownSeconds` have elapsed, the IP is deallocated (moved + from `Allocated` to `Unallocated`). + format: date-time + type: string secondary: additionalProperties: type: string diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml index 34185d9005..09bb2adf29 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamconfigs.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMConfigList plural: ipamconfigs singular: ipamconfig - preserveUnknownFields: false scope: Cluster versions: - name: v1 @@ -42,6 +41,14 @@ spec: properties: autoAllocateBlocks: type: boolean + ipCooldownSeconds: + description: |- + IPCooldownSeconds is the minimum age of a released IP in a block before it is re-used. + If set to zero, IPs can be re-used immediately (but are still handled with a FIFO queue to + minimize immediate reuse). + maximum: 1200 + minimum: 0 + type: integer kubeVirtVMAddressPersistence: description: |- KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml index d80a2d2e5d..3816fd731d 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipamhandles.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMHandleList plural: ipamhandles singular: ipamhandle - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml index df2abcd4da..7e6841bbfe 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ippools.yaml @@ -11,7 +11,6 @@ spec: listKind: IPPoolList plural: ippools singular: ippool - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml index 22991934d4..1c69e562cb 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_ipreservations.yaml @@ -11,7 +11,6 @@ spec: listKind: IPReservationList plural: ipreservations singular: ipreservation - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml index 6da8f9f3b5..a10593fc03 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_kubecontrollersconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: KubeControllersConfigurationList plural: kubecontrollersconfigurations singular: kubecontrollersconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_licensekeys.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_licensekeys.yaml index db528be78a..6c5cd9a674 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_licensekeys.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_licensekeys.yaml @@ -11,7 +11,6 @@ spec: listKind: LicenseKeyList plural: licensekeys singular: licensekey - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_managedclusters.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_managedclusters.yaml index 3d984f14b7..caf465ad75 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_managedclusters.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_managedclusters.yaml @@ -11,7 +11,6 @@ spec: listKind: ManagedClusterList plural: managedclusters singular: managedcluster - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml index b0ef3a86a0..f41d32ce73 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkPolicyList plural: networkpolicies singular: networkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networks.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networks.yaml index ac0aaea2cc..d3955d4fe0 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networks.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networks.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkList plural: networks singular: network - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml index e765f05954..bf6cb1abf6 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_networksets.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkSetList plural: networksets singular: networkset - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_packetcaptures.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_packetcaptures.yaml index 75fcf1bb8a..0d3eb06136 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_packetcaptures.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_packetcaptures.yaml @@ -11,7 +11,6 @@ spec: listKind: PacketCaptureList plural: packetcaptures singular: packetcapture - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_policyrecommendationscopes.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_policyrecommendationscopes.yaml index b33e1d7648..c4b3618296 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_policyrecommendationscopes.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_policyrecommendationscopes.yaml @@ -11,7 +11,6 @@ spec: listKind: PolicyRecommendationScopeList plural: policyrecommendationscopes singular: policyrecommendationscope - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_remoteclusterconfigurations.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_remoteclusterconfigurations.yaml index 46be027547..f9e8d13123 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_remoteclusterconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_remoteclusterconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: RemoteClusterConfigurationList plural: remoteclusterconfigurations singular: remoteclusterconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_securityeventwebhooks.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_securityeventwebhooks.yaml index ba61ad2421..6b95748a3e 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_securityeventwebhooks.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_securityeventwebhooks.yaml @@ -11,7 +11,6 @@ spec: listKind: SecurityEventWebhookList plural: securityeventwebhooks singular: securityeventwebhook - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml index c305396091..7f4f787300 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: StagedGlobalNetworkPolicyList plural: stagedglobalnetworkpolicies singular: stagedglobalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml index e5851adbd1..e6c0f7132f 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: StagedKubernetesNetworkPolicyList plural: stagedkubernetesnetworkpolicies singular: stagedkubernetesnetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml index 2256961697..d054319f89 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_stagednetworkpolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: StagedNetworkPolicyList plural: stagednetworkpolicies singular: stagednetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml index b2567ece66..deb3fbaa4f 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_tiers.yaml @@ -11,7 +11,6 @@ spec: listKind: TierList plural: tiers singular: tier - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettings.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettings.yaml index c4af5edb46..e5f948e0d7 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettings.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettings.yaml @@ -11,7 +11,6 @@ spec: listKind: UISettingsList plural: uisettings singular: uisettings - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettingsgroups.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettingsgroups.yaml index b1d6ce9970..01dabd70a5 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettingsgroups.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/crd.projectcalico.org_uisettingsgroups.yaml @@ -11,7 +11,6 @@ spec: listKind: UISettingsGroupList plural: uisettingsgroups singular: uisettingsgroup - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/usage.tigera.io_licenseusagereports.yaml b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/usage.tigera.io_licenseusagereports.yaml index 59d3c0974b..264b8edec0 100644 --- a/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/usage.tigera.io_licenseusagereports.yaml +++ b/pkg/imports/crds/enterprise/v1.crd.projectcalico.org/usage.tigera.io_licenseusagereports.yaml @@ -11,7 +11,6 @@ spec: listKind: LicenseUsageReportList plural: licenseusagereports singular: licenseusagereport - preserveUnknownFields: false scope: Cluster versions: - name: v1 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_alertexceptions.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_alertexceptions.yaml index 1dcd44c189..f030d268dd 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_alertexceptions.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_alertexceptions.yaml @@ -11,7 +11,6 @@ spec: listKind: AlertExceptionList plural: alertexceptions singular: alertexception - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bfdconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bfdconfigurations.yaml index fb7629540b..f0265265fc 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bfdconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bfdconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: BFDConfigurationList plural: bfdconfigurations singular: bfdconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml index 6a11cd0b79..37c52893a2 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpconfigurations.yaml @@ -14,7 +14,6 @@ spec: - bgpconfig - bgpconfigs singular: bgpconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml index f734bf8a5e..d57f10aebb 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgpfilters.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPFilterList plural: bgpfilters singular: bgpfilter - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgppeers.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgppeers.yaml index 6d6436bb43..bf7c0ced63 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgppeers.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_bgppeers.yaml @@ -11,7 +11,6 @@ spec: listKind: BGPPeerList plural: bgppeers singular: bgppeer - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml index d95679581a..60e6e5f67e 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_blockaffinities.yaml @@ -14,7 +14,6 @@ spec: - affinity - affinities singular: blockaffinity - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml index d52d68e9f8..929ec8a9e4 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_caliconodestatuses.yaml @@ -11,7 +11,6 @@ spec: listKind: CalicoNodeStatusList plural: caliconodestatuses singular: caliconodestatus - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml index 2e47062909..5507328ca0 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_clusterinformations.yaml @@ -14,7 +14,6 @@ spec: - clusterinfo - clusterinfos singular: clusterinformation - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_deeppacketinspections.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_deeppacketinspections.yaml index a9afe9dd3b..ecf060fa00 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_deeppacketinspections.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_deeppacketinspections.yaml @@ -11,7 +11,6 @@ spec: listKind: DeepPacketInspectionList plural: deeppacketinspections singular: deeppacketinspection - preserveUnknownFields: false scope: Namespaced versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_egressgatewaypolicies.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_egressgatewaypolicies.yaml index cae3c4ecbf..132963a214 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_egressgatewaypolicies.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_egressgatewaypolicies.yaml @@ -11,7 +11,6 @@ spec: listKind: EgressGatewayPolicyList plural: egressgatewaypolicies singular: egressgatewaypolicy - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_externalnetworks.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_externalnetworks.yaml index 6ddc032b1d..796d321ccd 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_externalnetworks.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_externalnetworks.yaml @@ -11,7 +11,6 @@ spec: listKind: ExternalNetworkList plural: externalnetworks singular: externalnetwork - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml index 5aec3c5e2f..627134ee57 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_felixconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: FelixConfigurationList plural: felixconfigurations singular: felixconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerts.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerts.yaml index 5583399d28..8b50489c30 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerts.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerts.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalAlertList plural: globalalerts singular: globalalert - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerttemplates.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerttemplates.yaml index a449bbe2ee..578bc0fae3 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerttemplates.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalalerttemplates.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalAlertTemplateList plural: globalalerttemplates singular: globalalerttemplate - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml index b62610e3c8..00e4a609a5 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworkpolicies.yaml @@ -14,7 +14,6 @@ spec: - gnp - cgnp singular: globalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml index b43344cd3b..b7729ad2c4 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalnetworksets.yaml @@ -13,7 +13,6 @@ spec: shortNames: - gns singular: globalnetworkset - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreports.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreports.yaml index 355df8ebc9..24d0992507 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreports.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreports.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalReportList plural: globalreports singular: globalreport - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreporttypes.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreporttypes.yaml index e4bcefbf8a..cc6e66aed6 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreporttypes.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalreporttypes.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalReportTypeList plural: globalreporttypes singular: globalreporttype - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalthreatfeeds.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalthreatfeeds.yaml index 19894b4a2e..0d11a78bda 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalthreatfeeds.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_globalthreatfeeds.yaml @@ -11,7 +11,6 @@ spec: listKind: GlobalThreatFeedList plural: globalthreatfeeds singular: globalthreatfeed - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml index 3f55368288..176a837703 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_hostendpoints.yaml @@ -14,7 +14,6 @@ spec: - hep - heps singular: hostendpoint - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml index 70cfbe995b..e7b4a5b293 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamblocks.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMBlockList plural: ipamblocks singular: ipamblock - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: @@ -95,6 +94,13 @@ spec: HandleID is the ID of the IPAM handle that owns this allocation. type: string + releasedAt: + description: |- + ReleasedAt is the time this allocation was released, and is set during the allocation's + "cooldown" phase. After `IPCooldownSeconds` have elapsed, the IP is deallocated (moved + from `Allocated` to `Unallocated`). + format: date-time + type: string secondary: additionalProperties: type: string diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml index e8505eff59..2eac4f5baf 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamconfigurations.yaml @@ -14,7 +14,6 @@ spec: - ipamconfig - ipamconfigs singular: ipamconfiguration - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: @@ -63,6 +62,15 @@ spec: default: true description: Whether or not to auto allocate blocks to hosts. type: boolean + ipCooldownSeconds: + description: |- + IPCooldownSeconds is the minimum age of a released IP in a block before it is re-used. + If set to zero, IPs can be re-used immediately (but are still handled with a FIFO queue to + minimize immediate reuse). + format: int32 + maximum: 1200 + minimum: 0 + type: integer kubeVirtVMAddressPersistence: description: |- KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml index b1b75eae59..0e77395a85 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipamhandles.yaml @@ -11,7 +11,6 @@ spec: listKind: IPAMHandleList plural: ipamhandles singular: ipamhandle - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ippools.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ippools.yaml index e9bfd125c7..3832d23b80 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ippools.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ippools.yaml @@ -11,7 +11,6 @@ spec: listKind: IPPoolList plural: ippools singular: ippool - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipreservations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipreservations.yaml index dc522cdd8a..e4d2af0a7f 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipreservations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_ipreservations.yaml @@ -11,7 +11,6 @@ spec: listKind: IPReservationList plural: ipreservations singular: ipreservation - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml index e523fb35e3..a589e445cc 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_kubecontrollersconfigurations.yaml @@ -14,7 +14,6 @@ spec: - kcc - kccs singular: kubecontrollersconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_licensekeys.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_licensekeys.yaml index c93478b44a..6bec97f908 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_licensekeys.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_licensekeys.yaml @@ -11,7 +11,6 @@ spec: listKind: LicenseKeyList plural: licensekeys singular: licensekey - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_managedclusters.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_managedclusters.yaml index c6f1590fcd..d6a4f95a28 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_managedclusters.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_managedclusters.yaml @@ -11,7 +11,6 @@ spec: listKind: ManagedClusterList plural: managedclusters singular: managedcluster - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml index e6600678d1..9a594dbde9 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networkpolicies.yaml @@ -14,7 +14,6 @@ spec: - cnp - caliconetworkpolicy singular: networkpolicy - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networks.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networks.yaml index 7e2846d2a5..993faf23ca 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networks.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networks.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkList plural: networks singular: network - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networksets.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networksets.yaml index f5935ce086..5a277a87ef 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networksets.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_networksets.yaml @@ -11,7 +11,6 @@ spec: listKind: NetworkSetList plural: networksets singular: networkset - preserveUnknownFields: false scope: Namespaced versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_packetcaptures.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_packetcaptures.yaml index d36ec49f0b..5752af9de5 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_packetcaptures.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_packetcaptures.yaml @@ -11,7 +11,6 @@ spec: listKind: PacketCaptureList plural: packetcaptures singular: packetcapture - preserveUnknownFields: false scope: Namespaced versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_policyrecommendationscopes.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_policyrecommendationscopes.yaml index cbe38ad4a7..171b407330 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_policyrecommendationscopes.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_policyrecommendationscopes.yaml @@ -11,7 +11,6 @@ spec: listKind: PolicyRecommendationScopeList plural: policyrecommendationscopes singular: policyrecommendationscope - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_remoteclusterconfigurations.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_remoteclusterconfigurations.yaml index 6198011dad..967e852e23 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_remoteclusterconfigurations.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_remoteclusterconfigurations.yaml @@ -11,7 +11,6 @@ spec: listKind: RemoteClusterConfigurationList plural: remoteclusterconfigurations singular: remoteclusterconfiguration - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_securityeventwebhooks.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_securityeventwebhooks.yaml index b701e292b7..b7927f9bf9 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_securityeventwebhooks.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_securityeventwebhooks.yaml @@ -11,7 +11,6 @@ spec: listKind: SecurityEventWebhookList plural: securityeventwebhooks singular: securityeventwebhook - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml index 4df3ca5956..412897d1d0 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedglobalnetworkpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - sgnp singular: stagedglobalnetworkpolicy - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml index 5381cfa7fd..2b21474b9f 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagedkubernetesnetworkpolicies.yaml @@ -13,7 +13,6 @@ spec: shortNames: - sknp singular: stagedkubernetesnetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml index 7147c2d0e9..a879ffb2cf 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_stagednetworkpolicies.yaml @@ -14,7 +14,6 @@ spec: - scnp - snp singular: stagednetworkpolicy - preserveUnknownFields: false scope: Namespaced versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_tiers.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_tiers.yaml index 0136cb1c95..abd4aeabc9 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_tiers.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_tiers.yaml @@ -11,7 +11,6 @@ spec: listKind: TierList plural: tiers singular: tier - preserveUnknownFields: false scope: Cluster versions: - additionalPrinterColumns: diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettings.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettings.yaml index d78c5049a0..405bb43188 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettings.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettings.yaml @@ -11,7 +11,6 @@ spec: listKind: UISettingsList plural: uisettings singular: uisettings - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettingsgroups.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettingsgroups.yaml index 5027068e9b..e9eaaacc6e 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettingsgroups.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/projectcalico.org_uisettingsgroups.yaml @@ -11,7 +11,6 @@ spec: listKind: UISettingsGroupList plural: uisettingsgroups singular: uisettingsgroup - preserveUnknownFields: false scope: Cluster versions: - name: v3 diff --git a/pkg/imports/crds/enterprise/v3.projectcalico.org/usage.tigera.io_licenseusagereports.yaml b/pkg/imports/crds/enterprise/v3.projectcalico.org/usage.tigera.io_licenseusagereports.yaml index 59d3c0974b..264b8edec0 100644 --- a/pkg/imports/crds/enterprise/v3.projectcalico.org/usage.tigera.io_licenseusagereports.yaml +++ b/pkg/imports/crds/enterprise/v3.projectcalico.org/usage.tigera.io_licenseusagereports.yaml @@ -11,7 +11,6 @@ spec: listKind: LicenseUsageReportList plural: licenseusagereports singular: licenseusagereport - preserveUnknownFields: false scope: Cluster versions: - name: v1 From fca30bc195645d50c208d5b4fce8f1ceec032764 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:27:43 -0700 Subject: [PATCH 20/30] api(manager): rename RBACUI.Enabled to Type *RBACUIType (PR #4865 review) Rename the RBAC management UI toggle field from Enabled (RBACUIStatus) to Type (*RBACUIType) per review. Keep the Enabled/Disabled enum idiom; the pointer distinguishes unset from the zero value. --- api/v1/manager_types.go | 28 +++++++------------ api/v1/zz_generated.deepcopy.go | 7 ++++- .../operator/operator.tigera.io_managers.yaml | 11 +++----- .../kubecontrollers/kube-controllers.go | 4 +-- pkg/render/manager_test.go | 13 +++++---- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/api/v1/manager_types.go b/api/v1/manager_types.go index 00d8671cdf..3d8729a475 100644 --- a/api/v1/manager_types.go +++ b/api/v1/manager_types.go @@ -32,31 +32,23 @@ type ManagerSpec struct { RBACUI *RBACUI `json:"rbacUI,omitempty"` } -// RBACUI controls the RBAC management UI feature surface. +// RBACUI configures the RBAC management UI. This is a separate control plane +// for Calico Enterprise RBAC that lives alongside, and does not replace, the +// user's ability to configure RBAC themselves. type RBACUI struct { - // Enabled controls whether the RBAC management UI is enabled. Defaults to - // Disabled. + // Enabled turns the RBAC management UI on or off. Defaults to false. // +optional - // +kubebuilder:validation:Enum=Enabled;Disabled - Enabled RBACUIStatus `json:"enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` } -// RBACUIStatus toggles the RBAC management UI feature. -type RBACUIStatus string - -const ( - RBACUIEnabled RBACUIStatus = "Enabled" - RBACUIDisabled RBACUIStatus = "Disabled" -) - -// RBACManagementEnabled returns true when the Manager CR opts the cluster -// into the RBAC management UI. Safe to call on a nil receiver; returns false -// for either a nil Manager or any value other than Enabled. +// RBACManagementEnabled returns true when the Manager CR enables the RBAC +// management UI. Safe to call on a nil receiver; returns false for a nil +// Manager, an unset RBACUI, or an unset/false toggle. func (m *Manager) RBACManagementEnabled() bool { - if m == nil || m.Spec.RBACUI == nil { + if m == nil || m.Spec.RBACUI == nil || m.Spec.RBACUI.Enabled == nil { return false } - return m.Spec.RBACUI.Enabled == RBACUIEnabled + return *m.Spec.RBACUI.Enabled } // ManagerDeployment is the configuration for the Manager Deployment. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 3c397299af..6f46a215b9 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -7920,7 +7920,7 @@ func (in *ManagerSpec) DeepCopyInto(out *ManagerSpec) { if in.RBACUI != nil { in, out := &in.RBACUI, &out.RBACUI *out = new(RBACUI) - **out = **in + (*in).DeepCopyInto(*out) } } @@ -9019,6 +9019,11 @@ func (in *QueryServerLogging) DeepCopy() *QueryServerLogging { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RBACUI) DeepCopyInto(out *RBACUI) { *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBACUI. diff --git a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml index dd4f2608a8..87c6ac5977 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml @@ -302,13 +302,10 @@ spec: description: RBACUI configures the RBAC management UI feature. properties: enabled: - description: |- - Enabled controls whether the RBAC management UI is enabled. Defaults to - Disabled. - enum: - - Enabled - - Disabled - type: string + description: + Enabled turns the RBAC management UI on or off. Defaults + to false. + type: boolean type: object type: object status: diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 0b3f3154f2..49f0b3b3fd 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -151,8 +151,8 @@ type KubeControllersConfiguration struct { // Only consulted when WAFGatewayExtensionEnabled is true. WAFWebhookCABundle []byte - // RBACManagementEnabled mirrors Manager.spec.rbac.ui == Enabled and gates - // the rbacsync controller in calico-kube-controllers. + // RBACManagementEnabled mirrors Manager.spec.rbacUI.enabled and gates the + // rbacsync controller in calico-kube-controllers. RBACManagementEnabled bool } diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index a133457caa..a8bf94e7d0 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -31,6 +31,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" @@ -1787,13 +1788,13 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(rtest.GetResource(resources, render.ManagerClusterRole, render.ManagerNamespace, rbacv1.GroupName, "v1", "Role")).To(BeNil()) }) - It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbacUI.enabled is Enabled", func() { + It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbacUI.enabled is true", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIEnabled}, + RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}, }, }, }) @@ -1804,13 +1805,13 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(role.Rules).To(ContainElement(nsCreateRule)) }) - It("renders RBAC_UI_ENABLED=false when rbacUI.enabled is Disabled", func() { + It("renders RBAC_UI_ENABLED=false when rbacUI.enabled is false", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIDisabled}, + RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(false)}, }, }, }) @@ -1832,7 +1833,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIEnabled}, + RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}, }, }, }) @@ -1849,7 +1850,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }, } rbacUIManager := &operatorv1.Manager{ - Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{Enabled: operatorv1.RBACUIEnabled}}, + Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}}, } It("adds LDAP egress only when RBAC UI is enabled and the LDAP config secret is present", func() { From 30c4c4eb6033ce9f017da56bb53199688ad0f3cc Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:28:13 -0700 Subject: [PATCH 21/30] apiserver: drop author-history comment on the Manager read (PR #4865 review) The comment stored authoring rationale rather than reader-useful context; remove it per review. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/apiserver/apiserver_controller.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/controller/apiserver/apiserver_controller.go b/pkg/controller/apiserver/apiserver_controller.go index f339ec93d2..c2b34492c0 100644 --- a/pkg/controller/apiserver/apiserver_controller.go +++ b/pkg/controller/apiserver/apiserver_controller.go @@ -378,10 +378,6 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re return reconcile.Result{}, err } - // A nil managerCR (no Manager created) means callers fall back to their - // defaults. A NoMatchError (Manager CRD not registered) propagates as an - // error: in enterprise the CRD is expected, so we requeue rather than - // read the Manager as absent. managerCR, err = utils.GetManager(ctx, r.client, false, "") if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) From f907d5080d5c3a5f865e1a42083cb65bcd91ceb4 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:28:46 -0700 Subject: [PATCH 22/30] installation: drop author-history comment on the Manager read (PR #4865 review) The comment stored authoring rationale rather than reader-useful context; remove it per review. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/controller/installation/core_controller.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/controller/installation/core_controller.go b/pkg/controller/installation/core_controller.go index 18a9ae0ff7..6105249448 100644 --- a/pkg/controller/installation/core_controller.go +++ b/pkg/controller/installation/core_controller.go @@ -1080,11 +1080,6 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile return reconcile.Result{}, err } - // We are in enterprise, so the Manager CRD is expected. A nil managerCR - // (no Manager created) means callers fall back to their defaults. A - // NoMatchError (Manager CRD not registered yet) is treated as an error: - // we don't know the user's intent until the CRD loads, so we requeue - // rather than read the Manager as absent. managerCR, err = utils.GetManager(ctx, r.client, false, "") if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger) From c665375ba01bc0524316300704c75718b87ceb5e Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:30:37 -0700 Subject: [PATCH 23/30] render(kubecontrollers): name the managed role each rbacsync rule covers (PR #4865 review) Explain in the appended rbacSyncControllerRules comments which managed role's grant each escalation rule mirrors: the tigera-network-admin ClusterRole for compliances, and the per-cluster log-access ClusterRoles for pods:list. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/kubecontrollers/kube-controllers.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 49f0b3b3fd..4779ba5e2c 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -720,13 +720,15 @@ func rbacSyncControllerRules() []rbacv1.PolicyRule { rules := render.RBACManagementEscalationRules() return append(rules, rbacv1.PolicyRule{ - // Held to cover the compliances:get the managed roles grant. + // Covers the compliances:get,list granted by the managed + // tigera-network-admin ClusterRole. APIGroups: []string{"operator.tigera.io"}, Resources: []string{"compliances"}, Verbs: []string{"get", "list"}, }, rbacv1.PolicyRule{ - // Held to cover the pods:list the managed roles grant. + // Covers the pods:list granted by the managed per-cluster + // log-access ClusterRoles that rbacsync maintains. APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, From 6ff6508001893e82a9ec0545bea9144beb5d8545 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:32:28 -0700 Subject: [PATCH 24/30] render: inline the RBAC escalation rules into rbacSyncControllerRules (PR #4865 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The escalation rules only apply to the rbacsync controller (not ui-apis), so the shared render.RBACManagementEscalationRules is no longer shared. Fold every rule into rbacSyncControllerRules — where they all belong as escalation coverage — each annotated with the managed role whose grant it mirrors, and delete the now-dead rbac_management.go. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../kubecontrollers/kube-controllers.go | 156 ++++++++++++++++-- pkg/render/rbac_management.go | 109 ------------ 2 files changed, 140 insertions(+), 125 deletions(-) delete mode 100644 pkg/render/rbac_management.go diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 4779ba5e2c..cc34b3e6b8 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -710,30 +710,154 @@ func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { } // rbacSyncControllerRules returns the cluster-scoped rules the rbacsync -// controller holds. The rbacsync controller writes the managed RBAC roles as -// its own ServiceAccount, and Kubernetes rejects a write to a Role or -// ClusterRole as privilege escalation unless the writer already holds every -// permission that role grants. The shared RBACManagementEscalationRules supply -// most of that coverage; the rules appended here cover the rest of what the -// managed roles grant. +// controller holds. The controller reconciles the ClusterRoles that back the +// Manager UI's RBAC management feature, and each rule below lets it manage the +// access one Calico Enterprise UI feature (and its view or modify state) +// requires. The controller runs only when RBAC management is enabled. func rbacSyncControllerRules() []rbacv1.PolicyRule { - rules := render.RBACManagementEscalationRules() - return append(rules, - rbacv1.PolicyRule{ - // Covers the compliances:get,list granted by the managed - // tigera-network-admin ClusterRole. + return []rbacv1.PolicyRule{ + // RBAC management: the ClusterRoles and bindings the controller + // reconciles for the feature. + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "clusterrolebindings", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // Network Policy tiers, view and modify: the per-tier and all-tiers + // Policies and Global Policies roles cover the tiers and tier-scoped + // (tier.*) policy resources. The plain networkpolicies and + // stagednetworkpolicies come from Policy Recommendations, which + // references them directly. + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{ + "tiers", + "tier.networkpolicies", + "tier.stagednetworkpolicies", + "tier.globalnetworkpolicies", + "tier.stagedglobalnetworkpolicies", + "stagedkubernetesnetworkpolicies", + "networkpolicies", + "stagednetworkpolicies", + }, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // Network Policy tiers, view and modify: Kubernetes network policies + // within a tier. + { + APIGroups: []string{"networking.k8s.io"}, + Resources: []string{"networkpolicies"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // The per-feature pages, view and modify: Dashboards, Managed Clusters, + // Global Network Sets, Network Sets, Policy Recommendations, Packet + // Captures, Alerts and Security Events, Threat Feeds, Compliance + // Reports, Webhooks, Deep Packet Inspection, and Egress Gateways. + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{ + "uisettings", + "uisettingsgroups", + "globalnetworksets", + "networksets", + "managedclusters", + "policyrecommendationscopes", + "policyrecommendationscopes/status", + "deeppacketinspections", + "deeppacketinspections/status", + "egressgatewaypolicies", + "externalnetworks", + "globalalerts", + "globalalerts/status", + "globalalerttemplates", + "alertexceptions", + "globalthreatfeeds", + "globalthreatfeeds/status", + "globalreports", + "globalreports/status", + "globalreporttypes", + "packetcaptures", + "packetcaptures/files", + "securityeventwebhooks", + }, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // Dashboards, view and modify: the cluster-settings and user-settings + // dashboard layouts stored on the UISettingsGroups data subresource. + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"uisettingsgroups/data"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + // Manager UI load: the authorization self-check the UI runs on load. + // Packet Captures: authenticating a capture-file download. + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"authorizationreviews", "authenticationreviews"}, + Verbs: []string{"create"}, + }, + // Manager UI load: Felix configuration read for cluster-wide settings. + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"felixconfigurations"}, + Verbs: []string{"get", "list", "watch"}, + }, + // Webhooks, modify: creating and updating the Secret that stores the + // webhook credentials. + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{"webhooks-secret"}, + Verbs: []string{"patch"}, + }, + // Logs, view: Flow, DNS, Audit, L7, and Events log access, per managed + // cluster and for the management cluster. + { + APIGroups: []string{"lma.tigera.io"}, + Resources: []string{"cluster"}, + Verbs: []string{"get"}, + }, + // Compliance Reports, view: reading the Compliance feature state. + { APIGroups: []string{"operator.tigera.io"}, Resources: []string{"compliances"}, - Verbs: []string{"get", "list"}, + Verbs: []string{"get"}, }, - rbacv1.PolicyRule{ - // Covers the pods:list granted by the managed per-cluster - // log-access ClusterRoles that rbacsync maintains. + // Manager UI load: feature-enabled checks for Application Layer / WAF, + // Packet Capture, and Intrusion Detection. + { + APIGroups: []string{"operator.tigera.io"}, + Resources: []string{"applicationlayers", "packetcaptureapis", "intrusiondetections"}, + Verbs: []string{"get"}, + }, + // Global Network Sets and Network Sets, view and modify: listing the + // pods a network set selects. + { APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, }, - ) + // Service Graph: service accounts, one of the Kubernetes resources the + // flow view references. + { + APIGroups: []string{""}, + Resources: []string{"serviceaccounts"}, + Verbs: []string{"get", "list"}, + }, + // Manager UI load: the statistics proxy to the Calico API server and + // the node Prometheus. + { + APIGroups: []string{""}, + Resources: []string{"services/proxy"}, + ResourceNames: []string{"https:calico-api:8080", "calico-node-prometheus:9090"}, + Verbs: []string{"get", "create"}, + }, + } } func (c *kubeControllersComponent) controllersServiceAccount() *corev1.ServiceAccount { diff --git a/pkg/render/rbac_management.go b/pkg/render/rbac_management.go deleted file mode 100644 index 7895dd4189..0000000000 --- a/pkg/render/rbac_management.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2026 Tigera, Inc. All rights reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package render - -import rbacv1 "k8s.io/api/rbac/v1" - -// RBACManagementEscalationRules returns the permission set the rbacsync -// controller (in calico-kube-controllers) holds so it can write the RBAC -// management UI's managed roles. The rbacsync controller acts as its own -// ServiceAccount, and Kubernetes rejects a write to a Role or ClusterRole as -// privilege escalation unless the writer already holds every permission that -// role grants. This set is therefore a superset of the rules in those managed -// roles; the managed roles themselves are defined in calico/kube-controllers. -func RBACManagementEscalationRules() []rbacv1.PolicyRule { - return []rbacv1.PolicyRule{ - // Full CRUD on the RBAC objects the feature writes. - { - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles", "clusterrolebindings", "rolebindings"}, - Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, - }, - // Tier/policy resources covered by generated tier roles. - { - APIGroups: []string{"projectcalico.org"}, - Resources: []string{ - "tiers", - "tier.networkpolicies", - "tier.stagednetworkpolicies", - "tier.globalnetworkpolicies", - "tier.stagedglobalnetworkpolicies", - "stagedkubernetesnetworkpolicies", - "networkpolicies", - "globalnetworkpolicies", - "stagednetworkpolicies", - "stagedglobalnetworkpolicies", - }, - Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, - }, - { - APIGroups: []string{"networking.k8s.io"}, - Resources: []string{"networkpolicies"}, - Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, - }, - // Resource-role payload set (the feature-level ClusterRoles maintained - // by rbacsync). - { - APIGroups: []string{"projectcalico.org"}, - Resources: []string{ - "uisettings", - "uisettingsgroups", - "globalnetworksets", - "networksets", - "managedclusters", - "policyrecommendationscopes", - "policyrecommendationscopes/status", - "deeppacketinspections", - "deeppacketinspections/status", - "egressgatewaypolicies", - "externalnetworks", - "globalalerts", - "globalalerts/status", - "globalalerttemplates", - "alertexceptions", - "globalthreatfeeds", - "globalthreatfeeds/status", - "globalreports", - "globalreports/status", - "globalreporttypes", - "packetcaptures", - "packetcaptures/files", - "securityeventwebhooks", - }, - Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, - }, - // The managed Webhooks role grants creating the webhook backing Secret and - // patching webhooks-secret, so the writer must hold the same to pass the - // escalation check. create cannot be name-scoped (the name is in the - // request body, not the URL path), so it is cluster-wide here. - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"create"}, - }, - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - ResourceNames: []string{"webhooks-secret"}, - Verbs: []string{"patch"}, - }, - // LMA log roles' scope verb (per-cluster log access roles). - { - APIGroups: []string{"lma.tigera.io"}, - Resources: []string{"cluster"}, - Verbs: []string{"get"}, - }, - } -} From 3724a80e1edf825d0699db99827fceafd6776017 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:33:02 -0700 Subject: [PATCH 25/30] render(manager): drop steering comment on RBAC_UI_ENABLED (PR #4865 review) The comment read as authoring context rather than something useful to the eventual reader; remove it per review. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/manager.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 0f5c654a3b..b52bfa6bae 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -759,10 +759,6 @@ func (c *managerComponent) managerUIAPIsContainer() corev1.Container { {Name: "LINSEED_CLIENT_KEY", Value: keyPath}, {Name: "ELASTIC_KIBANA_DISABLED", Value: strconv.FormatBool(c.cfg.Tenant.MultiTenant())}, {Name: "VOLTRON_URL", Value: ManagerService(c.cfg.Tenant)}, - // The RBAC management UI is not supported on multi-tenant management - // clusters, matching the gate on its supporting RBAC (see - // managerClusterRole / rbacManagementUINamespacedRole), so the backing - // resources exist whenever ui-apis advertises the feature. {Name: "RBAC_UI_ENABLED", Value: strconv.FormatBool(c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant())}, } From 6ffec3da98e90df662c3bbb7a638999eb3da9615 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Wed, 1 Jul 2026 16:35:17 -0700 Subject: [PATCH 26/30] render(manager): gate RBAC-UI LDAP egress on non-multi-tenant clusters (PR #4865 review) The RBAC management UI is unsupported on multi-tenant clusters, so its LDAP egress rule now carries the same !MultiTenant() gate as the rest of the feature. Adds a test that the egress is omitted in multi-tenant mode even with the UI enabled and the LDAP config Secret present. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/render/manager.go | 9 +++++---- pkg/render/manager_test.go | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index b52bfa6bae..a990f30af7 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -1353,11 +1353,12 @@ func (c *managerComponent) managerCalicoSystemNetworkPolicy() *v3.NetworkPolicy }) } - if c.cfg.Manager.RBACManagementEnabled() && c.cfg.RBACManagementLDAPConfigured { + if c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant() && c.cfg.RBACManagementLDAPConfigured { // LDAP/AD egress (389, 636) for the RBAC-UI directory sync, which dials the - // directory from this pod. Gated on the LDAP config Secret, whose presence - // marks the sync as configured. Destination unscoped (customer-configured); - // scoping to the LDAP host is tracked in EV-6665. + // directory from this pod. Gated on !MultiTenant() to match the rest of the + // RBAC UI (unsupported on multi-tenant clusters), and on the LDAP config + // Secret, whose presence marks the sync as configured. Destination unscoped + // (customer-configured); scoping to the LDAP host is tracked in EV-6665. egressRules = append(egressRules, v3.Rule{ Action: v3.Allow, Protocol: &networkpolicy.TCPProtocol, diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index a8bf94e7d0..b23fe0f73f 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1879,6 +1879,26 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) }) + + It("omits LDAP egress in multi-tenant mode even with RBAC UI enabled and the LDAP secret present", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, + ns: "tenant-a", + bindingNamespaces: []string{"tenant-a"}, + tenant: &operatorv1.Tenant{ + ObjectMeta: metav1.ObjectMeta{Name: "tenantA", Namespace: "tenant-a"}, + Spec: operatorv1.TenantSpec{ + ID: "tenant-a", + ManagedClusterVariant: &operatorv1.Calico, + }, + }, + manager: rbacUIManager, + rbacManagementLDAP: true, + }) + policy := testutils.GetCalicoSystemPolicyFromResources( + types.NamespacedName{Name: "calico-system.manager-access", Namespace: "tenant-a"}, resources) + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) }) }) }) From a42d5c3a44219a89db897f65175930a1cea46ae3 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 2 Jul 2026 16:08:01 -0700 Subject: [PATCH 27/30] render(kubecontrollers): enhance comments in rbacSyncControllerRules for clarity on role mirroring --- .../kubecontrollers/kube-controllers.go | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index cc34b3e6b8..af8f8f7685 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -714,10 +714,24 @@ func (c *kubeControllersComponent) rbacSyncIDPGroupsRole() []client.Object { // Manager UI's RBAC management feature, and each rule below lets it manage the // access one Calico Enterprise UI feature (and its view or modify state) // requires. The controller runs only when RBAC management is enabled. +// +// Under Kubernetes' privilege-escalation guard the controller can only grant +// permissions it already holds, so each rule mirrors a grant made by one of the +// managed calico-ui-* ClusterRoles the rbacsync controller generates in +// calico-private (kube-controllers/pkg/controllers/rbacsync: resourceroles.go +// defines the calico-ui--{view,mod} and calico-ui-logs-view-* roles, +// tierroles.go the calico-ui-{np,gnp}-{view,mod}- and calico-ui-cluster- +// context roles). The comment on each rule names the managed role(s) it covers. +// +// Only the grants unique to the managed roles live here. Core resources those +// roles also grant (namespaces, nodes, services, pods, clusterinformations, +// hostendpoints, serviceaccounts, tiers) are already held by the common +// kube-controllers rules above, which satisfy the escalation guard for them. func rbacSyncControllerRules() []rbacv1.PolicyRule { return []rbacv1.PolicyRule{ // RBAC management: the ClusterRoles and bindings the controller - // reconciles for the feature. + // reconciles for the feature. Not a mirrored grant — this is the + // controller's own reconcile target for every managed calico-ui-* role. { APIGroups: []string{"rbac.authorization.k8s.io"}, Resources: []string{"clusterroles", "clusterrolebindings", "rolebindings"}, @@ -727,7 +741,9 @@ func rbacSyncControllerRules() []rbacv1.PolicyRule { // Policies and Global Policies roles cover the tiers and tier-scoped // (tier.*) policy resources. The plain networkpolicies and // stagednetworkpolicies come from Policy Recommendations, which - // references them directly. + // references them directly. Mirrors calico-ui-{np,gnp}-{view,mod}- + // (and -all), calico-ui-get-tier-* and calico-ui-policy-recommendations- + // {view,mod}. { APIGroups: []string{"projectcalico.org"}, Resources: []string{ @@ -743,7 +759,7 @@ func rbacSyncControllerRules() []rbacv1.PolicyRule { Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, }, // Network Policy tiers, view and modify: Kubernetes network policies - // within a tier. + // within a tier. Mirrors calico-ui-np-{view,mod}- (and -all). { APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, @@ -753,6 +769,9 @@ func rbacSyncControllerRules() []rbacv1.PolicyRule { // Global Network Sets, Network Sets, Policy Recommendations, Packet // Captures, Alerts and Security Events, Threat Feeds, Compliance // Reports, Webhooks, Deep Packet Inspection, and Egress Gateways. + // Mirrors the matching calico-ui--{view,mod} roles + // (e.g. calico-ui-managed-clusters-{view,mod}, calico-ui-alerts- + // {view,mod}, calico-ui-egress-gateways-{view,mod}). { APIGroups: []string{"projectcalico.org"}, Resources: []string{ @@ -784,26 +803,30 @@ func rbacSyncControllerRules() []rbacv1.PolicyRule { }, // Dashboards, view and modify: the cluster-settings and user-settings // dashboard layouts stored on the UISettingsGroups data subresource. + // Mirrors calico-ui-dashboards-{view,mod}. { APIGroups: []string{"projectcalico.org"}, Resources: []string{"uisettingsgroups/data"}, Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, }, // Manager UI load: the authorization self-check the UI runs on load. - // Packet Captures: authenticating a capture-file download. + // Packet Captures: authenticating a capture-file download. Mirrors the + // authorizationreviews grant on calico-ui-cluster-context and the + // authenticationreviews grant on calico-ui-packet-captures-{view,mod}. { APIGroups: []string{"projectcalico.org"}, Resources: []string{"authorizationreviews", "authenticationreviews"}, Verbs: []string{"create"}, }, // Manager UI load: Felix configuration read for cluster-wide settings. + // Mirrors calico-ui-cluster-context. { APIGroups: []string{"projectcalico.org"}, Resources: []string{"felixconfigurations"}, Verbs: []string{"get", "list", "watch"}, }, // Webhooks, modify: creating and updating the Secret that stores the - // webhook credentials. + // webhook credentials. Mirrors calico-ui-webhooks-mod. { APIGroups: []string{""}, Resources: []string{"secrets"}, @@ -816,41 +839,53 @@ func rbacSyncControllerRules() []rbacv1.PolicyRule { Verbs: []string{"patch"}, }, // Logs, view: Flow, DNS, Audit, L7, and Events log access, per managed - // cluster and for the management cluster. + // cluster and for the management cluster. Mirrors calico-ui-logs-view-* + // (all/audit/dns/events/flows/l7, plus their per-cluster and + // all-clusters variants). { APIGroups: []string{"lma.tigera.io"}, Resources: []string{"cluster"}, Verbs: []string{"get"}, }, - // Compliance Reports, view: reading the Compliance feature state. + // Manager UI load: the Compliance feature-enabled check. Mirrors the + // unscoped compliances grant on calico-ui-cluster-context. (The + // calico-ui-compliance-reports-{view,mod} roles also read compliances + // but scope it to the tigera-secure CR; this rule must stay unscoped to + // cover cluster-context, whose feature check is not resource-scoped.) { APIGroups: []string{"operator.tigera.io"}, Resources: []string{"compliances"}, Verbs: []string{"get"}, }, // Manager UI load: feature-enabled checks for Application Layer / WAF, - // Packet Capture, and Intrusion Detection. + // Packet Capture, and Intrusion Detection. Mirrors calico-ui-cluster- + // context (which bundles the compliances check above into the same rule). { APIGroups: []string{"operator.tigera.io"}, Resources: []string{"applicationlayers", "packetcaptureapis", "intrusiondetections"}, Verbs: []string{"get"}, }, // Global Network Sets and Network Sets, view and modify: listing the - // pods a network set selects. + // pods a network set selects. Mirrors the pods grant on calico-ui- + // {global-network-sets,network-sets}-{view,mod} and calico-ui-service- + // graph-{view,mod}. (The common kube-controllers rules above already + // grant pods get/list/watch for IPAM GC, so this is also covered there.) { APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, }, - // Service Graph: service accounts, one of the Kubernetes resources the - // flow view references. + // Service Graph: the service accounts the flow view references. The only + // managed role granting serviceaccounts, so mirrors calico-ui-service- + // graph-{view,mod}. (That role also grants services, namespaces and + // hostendpoints, all covered by the common kube-controllers rules above.) { APIGroups: []string{""}, Resources: []string{"serviceaccounts"}, Verbs: []string{"get", "list"}, }, // Manager UI load: the statistics proxy to the Calico API server and - // the node Prometheus. + // the node Prometheus. Mirrors calico-ui-cluster-context. { APIGroups: []string{""}, Resources: []string{"services/proxy"}, From 99a6c3ce356141382ddceb80bbaccccb532e698c Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Thu, 2 Jul 2026 16:30:00 -0700 Subject: [PATCH 28/30] render(manager): add LDAP host parsing and scoping for egress policy based on config Secret --- pkg/controller/manager/manager_controller.go | 26 +++++++- .../manager/manager_controller_test.go | 36 +++++++++++ pkg/render/manager.go | 45 ++++++++++--- pkg/render/manager_test.go | 64 ++++++++++++++++++- 4 files changed, 159 insertions(+), 12 deletions(-) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index b467d50a7a..14be79634f 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -17,6 +17,7 @@ package manager import ( "context" "fmt" + neturl "net/url" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -683,12 +684,13 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ // Presence of the LDAP config Secret (always calico-system; the feature is // not supported on multi-tenant clusters) gates the manager's LDAP egress - // policy. + // policy, and its url field scopes that policy to the LDAP host. ldapConfigSecret, err := utils.GetSecret(ctx, r.client, render.RBACManagementLDAPConfigSecretName, common.CalicoNamespace) if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading RBAC management LDAP config secret", err, logc) return reconcile.Result{}, err } + ldapHost := ldapEgressHost(ldapConfigSecret) managerCfg := &render.ManagerConfiguration{ VoltronRouteConfig: routeConfig, @@ -720,6 +722,7 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ Authentication: authenticationCR, KibanaEnabled: kibanaEnabled, RBACManagementLDAPConfigured: ldapConfigSecret != nil, + RBACManagementLDAPHost: ldapHost, CACertCommonName: certificateManager.CACertCommonName(), } @@ -870,3 +873,24 @@ func (r *ReconcileManager) resolveAdditionalTunnelCert( } return certificatemanagement.NewKeyPair(secret, nil, ""), nil } + +// ldapEgressHost returns the host (IP or hostname, no port) parsed from the +// url field of the RBAC-UI LDAP config Secret, used to scope the manager's LDAP +// egress policy. It returns "" when the Secret is absent or its url is missing +// or unparseable; the caller leaves the egress destination unscoped in that +// case rather than failing the reconcile, since a malformed url should not gate +// the rest of the Manager. +func ldapEgressHost(ldapConfigSecret *corev1.Secret) string { + if ldapConfigSecret == nil { + return "" + } + raw := string(ldapConfigSecret.Data[render.RBACManagementLDAPConfigSecretURLKey]) + if raw == "" { + return "" + } + parsed, err := neturl.Parse(raw) + if err != nil { + return "" + } + return parsed.Hostname() +} diff --git a/pkg/controller/manager/manager_controller_test.go b/pkg/controller/manager/manager_controller_test.go index b3960fa4c7..5d8c32d8a6 100644 --- a/pkg/controller/manager/manager_controller_test.go +++ b/pkg/controller/manager/manager_controller_test.go @@ -1496,3 +1496,39 @@ func assertSANs(secret *corev1.Secret, expectedSAN string) { Expect(cert.DNSNames).To(Equal([]string{expectedSAN})) } + +var _ = Describe("ldapEgressHost", func() { + secretWithURL := func(url string) *corev1.Secret { + return &corev1.Secret{ + Data: map[string][]byte{render.RBACManagementLDAPConfigSecretURLKey: []byte(url)}, + } + } + + It("returns \"\" for a nil secret", func() { + Expect(ldapEgressHost(nil)).To(Equal("")) + }) + + DescribeTable("parses the host from the url field", + func(secret *corev1.Secret, expected string) { + Expect(ldapEgressHost(secret)).To(Equal(expected)) + }, + Entry("ldaps host with explicit port", secretWithURL("ldaps://ad.example.com:636"), "ad.example.com"), + Entry("ldap host without a port", secretWithURL("ldap://openldap.test.svc"), "openldap.test.svc"), + Entry("literal IPv4 host", secretWithURL("ldap://10.20.30.40:389"), "10.20.30.40"), + Entry("literal IPv6 host", secretWithURL("ldaps://[2001:db8::1]:636"), "2001:db8::1"), + Entry("strips credentials from the url", secretWithURL("ldap://cn=admin:secret@ad.example.com:389"), "ad.example.com"), + Entry("keeps a path/base-DN after the host", secretWithURL("ldaps://ad.example.com:636/ou=users,dc=example,dc=com"), "ad.example.com"), + Entry("preserves host casing (Domains match is case-insensitive)", secretWithURL("ldaps://AD.Example.COM"), "AD.Example.COM"), + Entry("missing url key falls back to empty", &corev1.Secret{Data: map[string][]byte{}}, ""), + Entry("empty url falls back to empty", secretWithURL(""), ""), + Entry("unparseable url falls back to empty", secretWithURL("://not a url"), ""), + // A url without an ldap:// / ldaps:// scheme is not something ui-apis + // writes, but url.Parse can't recover the host from "host:port" (it reads + // "host" as the scheme) or a bare "host", so both degrade to empty — which + // the caller renders as an unscoped (ports-only) egress rule rather than + // failing. These pin that fallback so a future parser change is a conscious + // one. + Entry("scheme-less host:port degrades to empty (host read as scheme)", secretWithURL("ad.example.com:636"), ""), + Entry("scheme-less bare host degrades to empty", secretWithURL("ad.example.com"), ""), + ) +}) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index a990f30af7..8aadf0b02d 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -17,6 +17,7 @@ package render import ( "crypto/x509" "fmt" + "net" "strconv" "strings" @@ -82,6 +83,12 @@ const ( // Keep in sync with ui-apis rbacmanagement/idp LDAPConfigSecretName. RBACManagementLDAPConfigSecretName = "tigera-idp-ldap-config" + // RBACManagementLDAPConfigSecretURLKey is the key in the LDAP config Secret + // holding the endpoint (e.g. "ldaps://ad.example.com:636"); its host scopes + // the LDAP egress policy. Keep in sync with ui-apis rbacmanagement/idp + // SecretFieldURL. + RBACManagementLDAPConfigSecretURLKey = "url" + // The name of the TLS certificate used by Voltron to authenticate connections from managed // cluster clients talking to Linseed. VoltronLinseedTLS = "calico-voltron-linseed-tls" @@ -224,6 +231,13 @@ type ManagerConfiguration struct { // (RBACManagementLDAPConfigSecretName) is present. It gates the LDAP egress policy. RBACManagementLDAPConfigured bool + // RBACManagementLDAPHost is the host parsed from the RBAC-UI LDAP config + // Secret's url field (an IP or a hostname, without port). When set, the LDAP + // egress policy is scoped to it instead of being left open. Empty when the + // Secret is absent or its url is missing/unparseable, in which case the + // egress rule falls back to an unscoped destination. + RBACManagementLDAPHost string + // CACertCommonName is the CommonName from the CA certificate used for operator-managed certificates. // Passed to Voltron so it can identify the correct CA issuer public key. CACertCommonName string @@ -1354,17 +1368,28 @@ func (c *managerComponent) managerCalicoSystemNetworkPolicy() *v3.NetworkPolicy } if c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant() && c.cfg.RBACManagementLDAPConfigured { - // LDAP/AD egress (389, 636) for the RBAC-UI directory sync, which dials the - // directory from this pod. Gated on !MultiTenant() to match the rest of the - // RBAC UI (unsupported on multi-tenant clusters), and on the LDAP config - // Secret, whose presence marks the sync as configured. Destination unscoped - // (customer-configured); scoping to the LDAP host is tracked in EV-6665. + // LDAP/AD egress (389, 636) for the RBAC-UI directory sync, gated on the + // LDAP config Secret whose presence marks the sync as configured. The + // destination is scoped to the host parsed from that Secret's url — as a + // domain match for a hostname or a /32/128 for a literal IP. Both standard + // ports stay open (the url may dial a non-standard port; scoping the port + // too would risk denying a valid config), so only the host is narrowed. + dest := v3.EntityRule{Ports: networkpolicy.Ports(389, 636)} + if host := c.cfg.RBACManagementLDAPHost; host != "" { + if ip := net.ParseIP(host); ip != nil { + suffix := "/32" + if ip.To4() == nil { + suffix = "/128" + } + dest.Nets = []string{ip.String() + suffix} + } else { + dest.Domains = []string{host} + } + } egressRules = append(egressRules, v3.Rule{ - Action: v3.Allow, - Protocol: &networkpolicy.TCPProtocol, - Destination: v3.EntityRule{ - Ports: networkpolicy.Ports(389, 636), - }, + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: dest, }) } diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index b23fe0f73f..9296856740 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1842,6 +1842,9 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Context("LDAP egress network policy gate", func() { policyName := types.NamespacedName{Name: "calico-system.manager-access", Namespace: render.ManagerNamespace} + // ldapEgress is the fallback rule shape rendered when the LDAP config + // Secret is present but no host could be parsed from its url: ports + // open, destination unscoped. ldapEgress := v3.Rule{ Action: v3.Allow, Protocol: &networkpolicy.TCPProtocol, @@ -1853,7 +1856,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}}, } - It("adds LDAP egress only when RBAC UI is enabled and the LDAP config secret is present", func() { + It("adds an unscoped LDAP egress when RBAC UI and the secret are present but no host is known", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: rbacUIManager, rbacManagementLDAP: true, @@ -1862,6 +1865,63 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(policy.Spec.Egress).To(ContainElement(ldapEgress)) }) + It("scopes LDAP egress to a Domains match when the LDAP host is a hostname", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + manager: rbacUIManager, rbacManagementLDAP: true, + rbacManagementLDAPHost: "ad.example.com", + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).To(ContainElement(v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Domains: []string{"ad.example.com"}, + Ports: networkpolicy.Ports(389, 636), + }, + })) + // The unscoped fallback rule must not also be present. + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) + + It("scopes LDAP egress to a /32 Nets match when the LDAP host is an IPv4 address", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + manager: rbacUIManager, rbacManagementLDAP: true, + rbacManagementLDAPHost: "10.20.30.40", + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).To(ContainElement(v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Nets: []string{"10.20.30.40/32"}, + Ports: networkpolicy.Ports(389, 636), + }, + })) + // The unscoped fallback rule must not also be present. + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) + + It("scopes LDAP egress to a /128 Nets match when the LDAP host is an IPv6 address", func() { + resources, _ := renderObjects(renderConfig{ + installation: installation, ns: render.ManagerNamespace, + manager: rbacUIManager, rbacManagementLDAP: true, + rbacManagementLDAPHost: "2001:db8::1", + }) + policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) + Expect(policy.Spec.Egress).To(ContainElement(v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Nets: []string{"2001:db8::1/128"}, + Ports: networkpolicy.Ports(389, 636), + }, + })) + // The unscoped fallback rule must not also be present. + Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) + }) + It("omits LDAP egress when the LDAP config secret is absent, even with RBAC UI enabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, @@ -1918,6 +1978,7 @@ type renderConfig struct { manager *operatorv1.Manager externalElastic bool rbacManagementLDAP bool + rbacManagementLDAPHost string } func renderObjects(roc renderConfig) ([]client.Object, []client.Object) { @@ -1985,6 +2046,7 @@ func renderObjects(roc renderConfig) ([]client.Object, []client.Object) { Manager: roc.manager, ExternalElastic: roc.externalElastic, RBACManagementLDAPConfigured: roc.rbacManagementLDAP, + RBACManagementLDAPHost: roc.rbacManagementLDAPHost, CACertCommonName: certificateManager.CACertCommonName(), } From 914098403cd9ee24d6ddb1cf0773b1370aad51d6 Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Fri, 3 Jul 2026 14:49:49 -0700 Subject: [PATCH 29/30] render(manager): refactor RBACUI configuration to use State instead of Enabled and update related tests --- api/v1/manager_types.go | 18 +++++-- api/v1/manager_types_test.go | 51 +++++++++++++++++++ api/v1/zz_generated.deepcopy.go | 6 +-- .../operator/operator.tigera.io_managers.yaml | 11 ++-- .../kubecontrollers/kube-controllers.go | 2 +- pkg/render/manager_test.go | 12 ++--- 6 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 api/v1/manager_types_test.go diff --git a/api/v1/manager_types.go b/api/v1/manager_types.go index 3d8729a475..f338ce0d99 100644 --- a/api/v1/manager_types.go +++ b/api/v1/manager_types.go @@ -32,23 +32,31 @@ type ManagerSpec struct { RBACUI *RBACUI `json:"rbacUI,omitempty"` } +// +kubebuilder:validation:Enum=Enabled;Disabled +type RBACUIStatusType string + +const ( + RBACUIDisabled RBACUIStatusType = "Disabled" + RBACUIEnabled RBACUIStatusType = "Enabled" +) + // RBACUI configures the RBAC management UI. This is a separate control plane // for Calico Enterprise RBAC that lives alongside, and does not replace, the // user's ability to configure RBAC themselves. type RBACUI struct { - // Enabled turns the RBAC management UI on or off. Defaults to false. + // State turns the RBAC management UI on or off. Defaults to Disabled. // +optional - Enabled *bool `json:"enabled,omitempty"` + State *RBACUIStatusType `json:"state,omitempty"` } // RBACManagementEnabled returns true when the Manager CR enables the RBAC // management UI. Safe to call on a nil receiver; returns false for a nil -// Manager, an unset RBACUI, or an unset/false toggle. +// Manager, an unset RBACUI, or any state other than Enabled. func (m *Manager) RBACManagementEnabled() bool { - if m == nil || m.Spec.RBACUI == nil || m.Spec.RBACUI.Enabled == nil { + if m == nil || m.Spec.RBACUI == nil || m.Spec.RBACUI.State == nil { return false } - return *m.Spec.RBACUI.Enabled + return *m.Spec.RBACUI.State == RBACUIEnabled } // ManagerDeployment is the configuration for the Manager Deployment. diff --git a/api/v1/manager_types_test.go b/api/v1/manager_types_test.go new file mode 100644 index 0000000000..9a59442dff --- /dev/null +++ b/api/v1/manager_types_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +import "testing" + +func TestRBACManagementEnabled(t *testing.T) { + state := func(s RBACUIStatusType) *RBACUIStatusType { return &s } + + for _, tc := range []struct { + name string + m *Manager + want bool + }{ + // Nil paths: RBACManagementEnabled is called as managerCR.RBACManagementEnabled() + // where managerCR is nil when no Manager CR exists, so a nil receiver (and each + // nil field below) must return false rather than panic. + {name: "nil Manager", m: nil, want: false}, + {name: "nil RBACUI", m: &Manager{}, want: false}, + {name: "nil State", m: &Manager{Spec: ManagerSpec{RBACUI: &RBACUI{}}}, want: false}, + + {name: "State Enabled", m: &Manager{Spec: ManagerSpec{RBACUI: &RBACUI{State: state(RBACUIEnabled)}}}, want: true}, + {name: "State Disabled", m: &Manager{Spec: ManagerSpec{RBACUI: &RBACUI{State: state(RBACUIDisabled)}}}, want: false}, + // Any value other than Enabled is off (the Enum marker rejects this at the + // apiserver, but the helper must not treat a non-empty value as enabled). + {name: "State unrecognized value", m: &Manager{Spec: ManagerSpec{RBACUI: &RBACUI{State: state("SomethingElse")}}}, want: false}, + } { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("RBACManagementEnabled panicked on %s: %v", tc.name, r) + } + }() + if got := tc.m.RBACManagementEnabled(); got != tc.want { + t.Errorf("RBACManagementEnabled() = %v, want %v", got, tc.want) + } + }) + } +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 6f46a215b9..00e122a473 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -9019,9 +9019,9 @@ func (in *QueryServerLogging) DeepCopy() *QueryServerLogging { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RBACUI) DeepCopyInto(out *RBACUI) { *out = *in - if in.Enabled != nil { - in, out := &in.Enabled, &out.Enabled - *out = new(bool) + if in.State != nil { + in, out := &in.State, &out.State + *out = new(RBACUIStatusType) **out = **in } } diff --git a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml index 87c6ac5977..66c1345bf4 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_managers.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_managers.yaml @@ -301,11 +301,14 @@ spec: rbacUI: description: RBACUI configures the RBAC management UI feature. properties: - enabled: + state: description: - Enabled turns the RBAC management UI on or off. Defaults - to false. - type: boolean + State turns the RBAC management UI on or off. Defaults + to Disabled. + enum: + - Enabled + - Disabled + type: string type: object type: object status: diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index af8f8f7685..b8cb885045 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -151,7 +151,7 @@ type KubeControllersConfiguration struct { // Only consulted when WAFGatewayExtensionEnabled is true. WAFWebhookCABundle []byte - // RBACManagementEnabled mirrors Manager.spec.rbacUI.enabled and gates the + // RBACManagementEnabled mirrors Manager.spec.rbacUI.state and gates the // rbacsync controller in calico-kube-controllers. RBACManagementEnabled bool } diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index 9296856740..8d7d35f628 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1788,13 +1788,13 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(rtest.GetResource(resources, render.ManagerClusterRole, render.ManagerNamespace, rbacv1.GroupName, "v1", "Role")).To(BeNil()) }) - It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbacUI.enabled is true", func() { + It("renders RBAC_UI_ENABLED=true and the namespaced RBAC UI role when rbacUI.state is Enabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}, + RBACUI: &operatorv1.RBACUI{State: ptr.To(operatorv1.RBACUIEnabled)}, }, }, }) @@ -1805,13 +1805,13 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(role.Rules).To(ContainElement(nsCreateRule)) }) - It("renders RBAC_UI_ENABLED=false when rbacUI.enabled is false", func() { + It("renders RBAC_UI_ENABLED=false when rbacUI.state is Disabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(false)}, + RBACUI: &operatorv1.RBACUI{State: ptr.To(operatorv1.RBACUIDisabled)}, }, }, }) @@ -1833,7 +1833,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }, manager: &operatorv1.Manager{ Spec: operatorv1.ManagerSpec{ - RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}, + RBACUI: &operatorv1.RBACUI{State: ptr.To(operatorv1.RBACUIEnabled)}, }, }, }) @@ -1853,7 +1853,7 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { }, } rbacUIManager := &operatorv1.Manager{ - Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{Enabled: ptr.To(true)}}, + Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{State: ptr.To(operatorv1.RBACUIEnabled)}}, } It("adds an unscoped LDAP egress when RBAC UI and the secret are present but no host is known", func() { From 99f3939c10101d49cef2f85dc5d410be3752de0a Mon Sep 17 00:00:00 2001 From: Dimitri Nicolopoulos Date: Fri, 3 Jul 2026 15:38:47 -0700 Subject: [PATCH 30/30] render(manager): refactor LDAP egress handling to use Authentication CR and remove deprecated code --- pkg/controller/manager/manager_controller.go | 98 ++++++------------- .../manager/manager_controller_test.go | 36 ------- pkg/render/manager.go | 48 +++++---- pkg/render/manager_test.go | 94 +++++++++--------- 4 files changed, 101 insertions(+), 175 deletions(-) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 14be79634f..5f637b92d7 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -17,7 +17,6 @@ package manager import ( "context" "fmt" - neturl "net/url" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -96,12 +95,6 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { return err } - // Re-render when the RBAC-UI LDAP config Secret appears/disappears: it gates - // the manager's LDAP egress policy. - if err := utils.AddSecretsWatch(c, render.RBACManagementLDAPConfigSecretName, common.CalicoNamespace); err != nil { - return err - } - go utils.WaitToAddLicenseKeyWatch(c, opts.K8sClientset, log, licenseAPIReady) go utils.WaitToAddTierWatch(networkpolicy.CalicoTierName, c, opts.K8sClientset, log, tierWatchReady) policiesToWatch := []types.NamespacedName{ @@ -682,48 +675,36 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ } } - // Presence of the LDAP config Secret (always calico-system; the feature is - // not supported on multi-tenant clusters) gates the manager's LDAP egress - // policy, and its url field scopes that policy to the LDAP host. - ldapConfigSecret, err := utils.GetSecret(ctx, r.client, render.RBACManagementLDAPConfigSecretName, common.CalicoNamespace) - if err != nil { - r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading RBAC management LDAP config secret", err, logc) - return reconcile.Result{}, err - } - ldapHost := ldapEgressHost(ldapConfigSecret) - managerCfg := &render.ManagerConfiguration{ - VoltronRouteConfig: routeConfig, - KeyValidatorConfig: keyValidatorConfig, - TrustedCertBundle: trustedBundle, - TLSKeyPair: tlsSecret, - VoltronLinseedKeyPair: linseedVoltronServerCert, - PullSecrets: pullSecrets, - OpenShift: r.opts.DetectedProvider.IsOpenShift(), - Installation: installationSpec, - ManagementCluster: managementCluster, - NonClusterHost: nonclusterhost, - TunnelServerCert: tunnelServerCert, - AdditionalTunnelServerCert: additionalTunnelServerCert, - InternalTLSKeyPair: internalTrafficSecret, - ClusterDomain: r.opts.ClusterDomain, - ESLicenseType: elasticLicenseType, - Replicas: replicas, - Compliance: complianceCR, - ComplianceLicenseActive: complianceLicenseFeatureActive, - ComplianceNamespace: utils.NewNamespaceHelper(r.opts.MultiTenant, render.ComplianceNamespace, request.Namespace).InstallNamespace(), - Namespace: helper.InstallNamespace(), - TruthNamespace: helper.TruthNamespace(), - Tenant: tenant, - ExternalElastic: r.opts.ElasticExternal, - BindingNamespaces: namespaces, - OSSTenantNamespaces: ossTenantNamespaces, - Manager: instance, - Authentication: authenticationCR, - KibanaEnabled: kibanaEnabled, - RBACManagementLDAPConfigured: ldapConfigSecret != nil, - RBACManagementLDAPHost: ldapHost, - CACertCommonName: certificateManager.CACertCommonName(), + VoltronRouteConfig: routeConfig, + KeyValidatorConfig: keyValidatorConfig, + TrustedCertBundle: trustedBundle, + TLSKeyPair: tlsSecret, + VoltronLinseedKeyPair: linseedVoltronServerCert, + PullSecrets: pullSecrets, + OpenShift: r.opts.DetectedProvider.IsOpenShift(), + Installation: installationSpec, + ManagementCluster: managementCluster, + NonClusterHost: nonclusterhost, + TunnelServerCert: tunnelServerCert, + AdditionalTunnelServerCert: additionalTunnelServerCert, + InternalTLSKeyPair: internalTrafficSecret, + ClusterDomain: r.opts.ClusterDomain, + ESLicenseType: elasticLicenseType, + Replicas: replicas, + Compliance: complianceCR, + ComplianceLicenseActive: complianceLicenseFeatureActive, + ComplianceNamespace: utils.NewNamespaceHelper(r.opts.MultiTenant, render.ComplianceNamespace, request.Namespace).InstallNamespace(), + Namespace: helper.InstallNamespace(), + TruthNamespace: helper.TruthNamespace(), + Tenant: tenant, + ExternalElastic: r.opts.ElasticExternal, + BindingNamespaces: namespaces, + OSSTenantNamespaces: ossTenantNamespaces, + Manager: instance, + Authentication: authenticationCR, + KibanaEnabled: kibanaEnabled, + CACertCommonName: certificateManager.CACertCommonName(), } // Render the desired objects from the CRD and create or update them. @@ -873,24 +854,3 @@ func (r *ReconcileManager) resolveAdditionalTunnelCert( } return certificatemanagement.NewKeyPair(secret, nil, ""), nil } - -// ldapEgressHost returns the host (IP or hostname, no port) parsed from the -// url field of the RBAC-UI LDAP config Secret, used to scope the manager's LDAP -// egress policy. It returns "" when the Secret is absent or its url is missing -// or unparseable; the caller leaves the egress destination unscoped in that -// case rather than failing the reconcile, since a malformed url should not gate -// the rest of the Manager. -func ldapEgressHost(ldapConfigSecret *corev1.Secret) string { - if ldapConfigSecret == nil { - return "" - } - raw := string(ldapConfigSecret.Data[render.RBACManagementLDAPConfigSecretURLKey]) - if raw == "" { - return "" - } - parsed, err := neturl.Parse(raw) - if err != nil { - return "" - } - return parsed.Hostname() -} diff --git a/pkg/controller/manager/manager_controller_test.go b/pkg/controller/manager/manager_controller_test.go index 5d8c32d8a6..b3960fa4c7 100644 --- a/pkg/controller/manager/manager_controller_test.go +++ b/pkg/controller/manager/manager_controller_test.go @@ -1496,39 +1496,3 @@ func assertSANs(secret *corev1.Secret, expectedSAN string) { Expect(cert.DNSNames).To(Equal([]string{expectedSAN})) } - -var _ = Describe("ldapEgressHost", func() { - secretWithURL := func(url string) *corev1.Secret { - return &corev1.Secret{ - Data: map[string][]byte{render.RBACManagementLDAPConfigSecretURLKey: []byte(url)}, - } - } - - It("returns \"\" for a nil secret", func() { - Expect(ldapEgressHost(nil)).To(Equal("")) - }) - - DescribeTable("parses the host from the url field", - func(secret *corev1.Secret, expected string) { - Expect(ldapEgressHost(secret)).To(Equal(expected)) - }, - Entry("ldaps host with explicit port", secretWithURL("ldaps://ad.example.com:636"), "ad.example.com"), - Entry("ldap host without a port", secretWithURL("ldap://openldap.test.svc"), "openldap.test.svc"), - Entry("literal IPv4 host", secretWithURL("ldap://10.20.30.40:389"), "10.20.30.40"), - Entry("literal IPv6 host", secretWithURL("ldaps://[2001:db8::1]:636"), "2001:db8::1"), - Entry("strips credentials from the url", secretWithURL("ldap://cn=admin:secret@ad.example.com:389"), "ad.example.com"), - Entry("keeps a path/base-DN after the host", secretWithURL("ldaps://ad.example.com:636/ou=users,dc=example,dc=com"), "ad.example.com"), - Entry("preserves host casing (Domains match is case-insensitive)", secretWithURL("ldaps://AD.Example.COM"), "AD.Example.COM"), - Entry("missing url key falls back to empty", &corev1.Secret{Data: map[string][]byte{}}, ""), - Entry("empty url falls back to empty", secretWithURL(""), ""), - Entry("unparseable url falls back to empty", secretWithURL("://not a url"), ""), - // A url without an ldap:// / ldaps:// scheme is not something ui-apis - // writes, but url.Parse can't recover the host from "host:port" (it reads - // "host" as the scheme) or a bare "host", so both degrade to empty — which - // the caller renders as an unscoped (ports-only) egress rule rather than - // failing. These pin that fallback so a future parser change is a conscious - // one. - Entry("scheme-less host:port degrades to empty (host read as scheme)", secretWithURL("ad.example.com:636"), ""), - Entry("scheme-less bare host degrades to empty", secretWithURL("ad.example.com"), ""), - ) -}) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 8aadf0b02d..d634e22478 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -79,16 +79,10 @@ const ( ManagerPortName = "https" // RBACManagementLDAPConfigSecretName is the RBAC-UI LDAP directory-sync config - // Secret (calico-system); its presence gates the manager's LDAP egress policy. + // Secret (calico-system) the rbacsync process reads to perform the sync. // Keep in sync with ui-apis rbacmanagement/idp LDAPConfigSecretName. RBACManagementLDAPConfigSecretName = "tigera-idp-ldap-config" - // RBACManagementLDAPConfigSecretURLKey is the key in the LDAP config Secret - // holding the endpoint (e.g. "ldaps://ad.example.com:636"); its host scopes - // the LDAP egress policy. Keep in sync with ui-apis rbacmanagement/idp - // SecretFieldURL. - RBACManagementLDAPConfigSecretURLKey = "url" - // The name of the TLS certificate used by Voltron to authenticate connections from managed // cluster clients talking to Linseed. VoltronLinseedTLS = "calico-voltron-linseed-tls" @@ -227,17 +221,6 @@ type ManagerConfiguration struct { Authentication *operatorv1.Authentication KibanaEnabled bool - // RBACManagementLDAPConfigured reports whether the RBAC-UI LDAP config Secret - // (RBACManagementLDAPConfigSecretName) is present. It gates the LDAP egress policy. - RBACManagementLDAPConfigured bool - - // RBACManagementLDAPHost is the host parsed from the RBAC-UI LDAP config - // Secret's url field (an IP or a hostname, without port). When set, the LDAP - // egress policy is scoped to it instead of being left open. Empty when the - // Secret is absent or its url is missing/unparseable, in which case the - // egress rule falls back to an unscoped destination. - RBACManagementLDAPHost string - // CACertCommonName is the CommonName from the CA certificate used for operator-managed certificates. // Passed to Voltron so it can identify the correct CA issuer public key. CACertCommonName string @@ -1367,15 +1350,16 @@ func (c *managerComponent) managerCalicoSystemNetworkPolicy() *v3.NetworkPolicy }) } - if c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant() && c.cfg.RBACManagementLDAPConfigured { - // LDAP/AD egress (389, 636) for the RBAC-UI directory sync, gated on the - // LDAP config Secret whose presence marks the sync as configured. The - // destination is scoped to the host parsed from that Secret's url — as a - // domain match for a hostname or a /32/128 for a literal IP. Both standard - // ports stay open (the url may dial a non-standard port; scoping the port - // too would risk denying a valid config), so only the host is narrowed. + if c.cfg.Manager.RBACManagementEnabled() && !c.cfg.Tenant.MultiTenant() && + c.cfg.Authentication != nil && c.cfg.Authentication.Spec.LDAP != nil { + // LDAP/AD egress (389, 636) for the RBAC-UI directory sync, gated on LDAP + // being configured on the Authentication CR. The destination is scoped to + // Authentication.spec.ldap.host — a domain match for a hostname or a + // /32/128 for a literal IP. Both standard ports stay open (the host may + // specify a non-standard port; scoping the port too would risk denying a + // valid config), so only the host is narrowed. dest := v3.EntityRule{Ports: networkpolicy.Ports(389, 636)} - if host := c.cfg.RBACManagementLDAPHost; host != "" { + if host := ldapEgressHost(c.cfg.Authentication.Spec.LDAP.Host); host != "" { if ip := net.ParseIP(host); ip != nil { suffix := "/32" if ip.To4() == nil { @@ -1457,6 +1441,18 @@ func (c *managerComponent) managerCalicoSystemNetworkPolicy() *v3.NetworkPolicy } } +// ldapEgressHost returns the host (without port) from an +// Authentication.spec.ldap.host value, used to scope the manager's LDAP egress +// policy. The value carries an optional port (e.g. "ad.example.com:636"); when +// no port is present the value is already the host. IPv6 literals are returned +// unbracketed so the caller can parse them with net.ParseIP. +func ldapEgressHost(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return strings.TrimSuffix(strings.TrimPrefix(host, "["), "]") +} + func (c *managerComponent) multiTenantManagedClustersAccess() []client.Object { var objects []client.Object diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index 8d7d35f628..9c3177e94b 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -1842,9 +1842,9 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Context("LDAP egress network policy gate", func() { policyName := types.NamespacedName{Name: "calico-system.manager-access", Namespace: render.ManagerNamespace} - // ldapEgress is the fallback rule shape rendered when the LDAP config - // Secret is present but no host could be parsed from its url: ports - // open, destination unscoped. + // ldapEgress is the fallback rule shape rendered when LDAP is configured + // on the Authentication CR but its host is empty: ports open, destination + // unscoped. ldapEgress := v3.Rule{ Action: v3.Allow, Protocol: &networkpolicy.TCPProtocol, @@ -1856,10 +1856,10 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Spec: operatorv1.ManagerSpec{RBACUI: &operatorv1.RBACUI{State: ptr.To(operatorv1.RBACUIEnabled)}}, } - It("adds an unscoped LDAP egress when RBAC UI and the secret are present but no host is known", func() { + It("adds an unscoped LDAP egress when RBAC UI and LDAP auth are configured but no host is set", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, - manager: rbacUIManager, rbacManagementLDAP: true, + manager: rbacUIManager, ldapConfigured: true, }) policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).To(ContainElement(ldapEgress)) @@ -1868,8 +1868,8 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { It("scopes LDAP egress to a Domains match when the LDAP host is a hostname", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, - manager: rbacUIManager, rbacManagementLDAP: true, - rbacManagementLDAPHost: "ad.example.com", + manager: rbacUIManager, ldapConfigured: true, + ldapHost: "ad.example.com:636", }) policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).To(ContainElement(v3.Rule{ @@ -1887,8 +1887,8 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { It("scopes LDAP egress to a /32 Nets match when the LDAP host is an IPv4 address", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, - manager: rbacUIManager, rbacManagementLDAP: true, - rbacManagementLDAPHost: "10.20.30.40", + manager: rbacUIManager, ldapConfigured: true, + ldapHost: "10.20.30.40:389", }) policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).To(ContainElement(v3.Rule{ @@ -1906,8 +1906,8 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { It("scopes LDAP egress to a /128 Nets match when the LDAP host is an IPv6 address", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, - manager: rbacUIManager, rbacManagementLDAP: true, - rbacManagementLDAPHost: "2001:db8::1", + manager: rbacUIManager, ldapConfigured: true, + ldapHost: "[2001:db8::1]:636", }) policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).To(ContainElement(v3.Rule{ @@ -1922,25 +1922,25 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) }) - It("omits LDAP egress when the LDAP config secret is absent, even with RBAC UI enabled", func() { + It("omits LDAP egress when LDAP auth is not configured, even with RBAC UI enabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, - manager: rbacUIManager, rbacManagementLDAP: false, + manager: rbacUIManager, ldapConfigured: false, }) policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) }) - It("omits LDAP egress when the LDAP secret is present but RBAC UI is disabled", func() { + It("omits LDAP egress when LDAP auth is configured but RBAC UI is disabled", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: render.ManagerNamespace, - rbacManagementLDAP: true, + ldapConfigured: true, }) policy := testutils.GetCalicoSystemPolicyFromResources(policyName, resources) Expect(policy.Spec.Egress).NotTo(ContainElement(ldapEgress)) }) - It("omits LDAP egress in multi-tenant mode even with RBAC UI enabled and the LDAP secret present", func() { + It("omits LDAP egress in multi-tenant mode even with RBAC UI enabled and LDAP auth configured", func() { resources, _ := renderObjects(renderConfig{ installation: installation, ns: "tenant-a", @@ -1952,8 +1952,8 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { ManagedClusterVariant: &operatorv1.Calico, }, }, - manager: rbacUIManager, - rbacManagementLDAP: true, + manager: rbacUIManager, + ldapConfigured: true, }) policy := testutils.GetCalicoSystemPolicyFromResources( types.NamespacedName{Name: "calico-system.manager-access", Namespace: "tenant-a"}, resources) @@ -1977,8 +1977,10 @@ type renderConfig struct { tenant *operatorv1.Tenant manager *operatorv1.Manager externalElastic bool - rbacManagementLDAP bool - rbacManagementLDAPHost string + // ldapConfigured, when true, sets Authentication.spec.ldap (gating the RBAC-UI + // LDAP egress rule); ldapHost sets Authentication.spec.ldap.host (scoping it). + ldapConfigured bool + ldapHost string } func renderObjects(roc renderConfig) ([]client.Object, []client.Object) { @@ -2024,30 +2026,34 @@ func renderObjects(roc renderConfig) ([]client.Object, []client.Object) { } cfg := &render.ManagerConfiguration{ - KeyValidatorConfig: dexCfg, - TrustedCertBundle: bundle, - TLSKeyPair: managerTLS, - Installation: roc.installation, - ManagementCluster: roc.managementCluster, - NonClusterHost: roc.nonClusterHost, - TunnelServerCert: tunnelSecret, - VoltronLinseedKeyPair: voltronLinseedKP, - InternalTLSKeyPair: internalTraffic, - ClusterDomain: dns.DefaultClusterDomain, - ESLicenseType: render.ElasticsearchLicenseTypeEnterpriseTrial, - Replicas: roc.installation.ControlPlaneReplicas, - Compliance: roc.compliance, - ComplianceLicenseActive: roc.complianceFeatureActive, - OpenShift: roc.openshift, - Namespace: roc.ns, - BindingNamespaces: roc.bindingNamespaces, - OSSTenantNamespaces: roc.ossBindingNamespaces, - Tenant: roc.tenant, - Manager: roc.manager, - ExternalElastic: roc.externalElastic, - RBACManagementLDAPConfigured: roc.rbacManagementLDAP, - RBACManagementLDAPHost: roc.rbacManagementLDAPHost, - CACertCommonName: certificateManager.CACertCommonName(), + KeyValidatorConfig: dexCfg, + TrustedCertBundle: bundle, + TLSKeyPair: managerTLS, + Installation: roc.installation, + ManagementCluster: roc.managementCluster, + NonClusterHost: roc.nonClusterHost, + TunnelServerCert: tunnelSecret, + VoltronLinseedKeyPair: voltronLinseedKP, + InternalTLSKeyPair: internalTraffic, + ClusterDomain: dns.DefaultClusterDomain, + ESLicenseType: render.ElasticsearchLicenseTypeEnterpriseTrial, + Replicas: roc.installation.ControlPlaneReplicas, + Compliance: roc.compliance, + ComplianceLicenseActive: roc.complianceFeatureActive, + OpenShift: roc.openshift, + Namespace: roc.ns, + BindingNamespaces: roc.bindingNamespaces, + OSSTenantNamespaces: roc.ossBindingNamespaces, + Tenant: roc.tenant, + Manager: roc.manager, + ExternalElastic: roc.externalElastic, + CACertCommonName: certificateManager.CACertCommonName(), + } + + if roc.ldapConfigured { + cfg.Authentication = &operatorv1.Authentication{ + Spec: operatorv1.AuthenticationSpec{LDAP: &operatorv1.AuthenticationLDAP{Host: roc.ldapHost}}, + } } if roc.tenant.MultiTenant() {