Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fe5a490
api(manager): add RBAC management UI field
dimitri-nicolo Jun 18, 2026
7316a17
render: gate RBAC management UI permissions on spec.rbac.ui
dimitri-nicolo Jun 18, 2026
0d99f55
utils: move GetManager into utils
dimitri-nicolo Jun 18, 2026
971bf87
controller(installation,apiserver): wire Manager CR into render config
dimitri-nicolo Jun 18, 2026
73d1c97
render(apiserver): limit network-admin RBAC UI to the user's own priv…
dimitri-nicolo Jun 25, 2026
5051f2a
render(rbac): drop escalate/bind from rbacsync, hold the managed perm…
dimitri-nicolo Jun 25, 2026
b655d8f
controller(installation): tolerate missing Manager CRD instead of gat…
dimitri-nicolo Jun 25, 2026
34e17ae
render(rbac): drop unused tigera-known-oidc-users grant from rbacsync…
dimitri-nicolo Jun 25, 2026
4aee36e
render(manager): gate LDAP egress on the RBAC-UI LDAP config secret, …
dimitri-nicolo Jun 25, 2026
58cef85
imports(crds): regenerate enterprise felixconfigurations and ipambloc…
dimitri-nicolo Jun 26, 2026
bb4bfc1
api(manager): rename spec.rbac to spec.rbacUI.enabled (PR #4865 review)
dimitri-nicolo Jun 30, 2026
e4cb0e3
controller: GetManager returns nil on NotFound, surfaces NoMatchError…
dimitri-nicolo Jun 30, 2026
26b06e4
render(rbac): scope rbacsync secret access to calico-system and comme…
dimitri-nicolo Jun 30, 2026
5e4bcd5
render(rbac): describe the RBAC UI as not-multi-tenant in tenancy com…
dimitri-nicolo Jun 30, 2026
b5c1b21
render(rbac): scope the RBAC-UI namespaced secret patch to named reso…
dimitri-nicolo Jun 30, 2026
b614cbf
render(rbac): drop escalation rules from the manager SA, keep only co…
dimitri-nicolo Jun 30, 2026
dd12844
render(manager): drop redundant OIDC LDAP-egress regression test (PR …
dimitri-nicolo Jun 30, 2026
0089629
render(manager): gate RBAC_UI_ENABLED on non-multi-tenant clusters (P…
dimitri-nicolo Jun 30, 2026
7c92e79
imports(crds): regenerate calico and enterprise CRD bundles from master
dimitri-nicolo Jun 30, 2026
fca30bc
api(manager): rename RBACUI.Enabled to Type *RBACUIType (PR #4865 rev…
dimitri-nicolo Jul 1, 2026
30c4c4e
apiserver: drop author-history comment on the Manager read (PR #4865 …
dimitri-nicolo Jul 1, 2026
f907d50
installation: drop author-history comment on the Manager read (PR #48…
dimitri-nicolo Jul 1, 2026
c665375
render(kubecontrollers): name the managed role each rbacsync rule cov…
dimitri-nicolo Jul 1, 2026
6ff6508
render: inline the RBAC escalation rules into rbacSyncControllerRules…
dimitri-nicolo Jul 1, 2026
3724a80
render(manager): drop steering comment on RBAC_UI_ENABLED (PR #4865 r…
dimitri-nicolo Jul 1, 2026
6ffec3d
render(manager): gate RBAC-UI LDAP egress on non-multi-tenant cluster…
dimitri-nicolo Jul 1, 2026
a42d5c3
render(kubecontrollers): enhance comments in rbacSyncControllerRules …
dimitri-nicolo Jul 2, 2026
99a6c3c
render(manager): add LDAP host parsing and scoping for egress policy …
dimitri-nicolo Jul 2, 2026
9140984
render(manager): refactor RBACUI configuration to use State instead o…
dimitri-nicolo Jul 3, 2026
99f3939
render(manager): refactor LDAP egress handling to use Authentication …
dimitri-nicolo Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions api/v1/manager_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@ type ManagerSpec struct {
// ManagerDeployment configures the Manager Deployment.
// +optional
ManagerDeployment *ManagerDeployment `json:"managerDeployment,omitempty"`

// RBACUI configures the RBAC management UI feature.
// +optional
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 {
// State turns the RBAC management UI on or off. Defaults to Disabled.
// +optional
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 any state other than Enabled.
func (m *Manager) RBACManagementEnabled() bool {
if m == nil || m.Spec.RBACUI == nil || m.Spec.RBACUI.State == nil {
return false
}
return *m.Spec.RBACUI.State == RBACUIEnabled
}

// ManagerDeployment is the configuration for the Manager Deployment.
Expand Down
51 changes: 51 additions & 0 deletions api/v1/manager_types_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
25 changes: 25 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions pkg/controller/apiserver/apiserver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -370,6 +378,12 @@ 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 {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger)
return reconcile.Result{}, err
}

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)
Expand Down Expand Up @@ -497,6 +511,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(),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RBACManagementEnabled: managerCR.RBACManagementEnabled(),
RBACManagementEnabled: managerCR != nil && managerCR.RBACManagementEnabled(),

QueryServerTLSKeyPairCertificateManagementOnly: queryServerTLSSecretCertificateManagementOnly,
}

Expand Down
18 changes: 17 additions & 1 deletion pkg/controller/installation/core_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -1071,6 +1080,12 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
return reconcile.Result{}, err
}

managerCR, err = utils.GetManager(ctx, r.client, false, "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we update GetManager to return nil, nil on NotFound?

if err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error reading Manager", err, reqLogger)
return reconcile.Result{}, err
}

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)
Expand Down Expand Up @@ -1705,7 +1720,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(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since utils.GetManager returns nil, nil on not found won't this now panic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Brian-McM It shouldn't panic, RBACManagementEnabled() guards m == nil before touching the pointer. The latest commit adds a table test (api/v1/manager_types_test.go) covering the nil-managerCR, nil-RBACUI, and nil-State cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RBACManagementEnabled: managerCR.RBACManagementEnabled(),
RBACManagementEnabled: managerCR != nil && managerCR.RBACManagementEnabled(),

}
components = append(components, kubecontrollers.NewCalicoKubeControllers(&kubeControllersCfg))

Expand Down
30 changes: 7 additions & 23 deletions pkg/controller/manager/manager_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -276,16 +259,16 @@ 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")
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()

Expand Down Expand Up @@ -719,6 +702,7 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ
BindingNamespaces: namespaces,
OSSTenantNamespaces: ossTenantNamespaces,
Manager: instance,
Authentication: authenticationCR,
KibanaEnabled: kibanaEnabled,
CACertCommonName: certificateManager.CACertCommonName(),
}
Expand Down
20 changes: 10 additions & 10 deletions pkg/controller/manager/manager_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
})

Expand All @@ -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"
Expand All @@ -112,14 +112,14 @@ 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() {
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 := GetManager(ctx, c, true, nsWithoutManager)
Expect(kerror.IsNotFound(err)).To(BeTrue())
instance, err := utils.GetManager(ctx, c, true, nsWithoutManager)
Expect(err).NotTo(HaveOccurred())
Expect(instance).To(BeNil())
})

Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down
21 changes: 21 additions & 0 deletions pkg/controller/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,27 @@ func GetIstio(ctx context.Context, c client.Client) (*operatorv1.Istio, error) {
return istio, nil
}

// 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 {
key.Namespace = ns
}
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
}

// 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{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ spec:
listKind: BGPConfigurationList
plural: bgpconfigurations
singular: bgpconfiguration
preserveUnknownFields: false
scope: Cluster
versions:
- name: v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ spec:
listKind: BGPFilterList
plural: bgpfilters
singular: bgpfilter
preserveUnknownFields: false
scope: Cluster
versions:
- name: v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ spec:
listKind: BGPPeerList
plural: bgppeers
singular: bgppeer
preserveUnknownFields: false
scope: Cluster
versions:
- name: v1
Expand Down
Loading
Loading