Skip to content

Commit 6190905

Browse files
authored
Consolidate RBAC for Guardian to handle backend component requests from the management cluster (#3969)
* Consolidate RBAC rules needed by Guardian to handle management cluster requests * Replace const with actual resource names
1 parent a6edd41 commit 6190905

File tree

11 files changed

+406
-80
lines changed

11 files changed

+406
-80
lines changed

pkg/render/compliance.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2019-2024 Tigera, Inc. All rights reserved.
1+
// Copyright (c) 2019-2025 Tigera, Inc. All rights reserved.
22

33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -725,8 +725,8 @@ func (c *complianceComponent) externalLinseedRoleBinding() *rbacv1.RoleBinding {
725725
Subjects: []rbacv1.Subject{
726726
{
727727
Kind: "ServiceAccount",
728-
Name: linseed,
729-
Namespace: ElasticsearchNamespace,
728+
Name: GuardianServiceAccountName,
729+
Namespace: GuardianNamespace,
730730
},
731731
},
732732
}

pkg/render/fluentd.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,8 @@ func (c *fluentdComponent) externalLinseedRoleBinding() *rbacv1.RoleBinding {
382382
Subjects: []rbacv1.Subject{
383383
{
384384
Kind: "ServiceAccount",
385-
Name: "tigera-linseed",
386-
Namespace: ElasticsearchNamespace,
385+
Name: GuardianServiceAccountName,
386+
Namespace: GuardianNamespace,
387387
},
388388
},
389389
}

pkg/render/fluentd_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,8 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() {
382382
Expect(linseedRoleBinding.Subjects).To(ConsistOf([]rbacv1.Subject{
383383
{
384384
Kind: "ServiceAccount",
385-
Name: render.LinseedServiceName,
386-
Namespace: render.ElasticsearchNamespace,
385+
Name: render.GuardianServiceAccountName,
386+
Namespace: render.GuardianNamespace,
387387
},
388388
}))
389389

@@ -493,8 +493,8 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() {
493493
Expect(linseedRoleBinding.Subjects).To(ConsistOf([]rbacv1.Subject{
494494
{
495495
Kind: "ServiceAccount",
496-
Name: render.LinseedServiceName,
497-
Namespace: render.ElasticsearchNamespace,
496+
Name: render.GuardianServiceAccountName,
497+
Namespace: render.GuardianNamespace,
498498
},
499499
}))
500500

pkg/render/guardian.go

Lines changed: 306 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,6 @@ func (c *GuardianComponent) Objects() ([]client.Object, []client.Object) {
149149

150150
if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise {
151151
objs = append(objs,
152-
// Add tigera-manager service account for impersonation. In managed clusters, the tigera-manager
153-
// service account is always within the tigera-manager namespace - regardless of (multi)tenancy mode.
154-
CreateNamespace(ManagerNamespace, c.cfg.Installation.KubernetesProvider, PSSRestricted, c.cfg.Installation.Azure),
155-
managerServiceAccount(ManagerNamespace),
156-
managerClusterRole(true, c.cfg.Installation.KubernetesProvider, nil),
157-
managerClusterRoleBinding(nil, []string{ManagerNamespace}, []string{}),
158-
159152
// Install default UI settings for this managed cluster.
160153
managerClusterWideSettingsGroup(),
161154
managerUserSpecificSettingsGroup(),
@@ -239,6 +232,8 @@ func (c *GuardianComponent) clusterRole() *rbacv1.ClusterRole {
239232
Verbs: []string{"impersonate"},
240233
})
241234

235+
policyRules = append(policyRules, rulesForManagementClusterRequests(c.cfg.OpenShift)...)
236+
242237
if c.cfg.OpenShift {
243238
policyRules = append(policyRules, rbacv1.PolicyRule{
244239
APIGroups: []string{"security.openshift.io"},
@@ -690,6 +685,292 @@ func GuardianService(clusterDomain string) string {
690685
return fmt.Sprintf("https://%s.%s.svc.%s:%d", GuardianServiceName, GuardianNamespace, clusterDomain, 443)
691686
}
692687

688+
// rulesForManagementClusterRequests returns the set of RBAC rules needed by Guardian in order to
689+
// satisfy requests from the management cluster over the tunnel.
690+
func rulesForManagementClusterRequests(isOpenShift bool) []rbacv1.PolicyRule {
691+
692+
rules := []rbacv1.PolicyRule{
693+
// Common rules required to handle requests from multiple components in the management cluster.
694+
{
695+
// ID uses read-only permissions and KubeController uses both read and write verbs.
696+
APIGroups: []string{""},
697+
Resources: []string{"configmaps"},
698+
Verbs: []string{"create", "delete", "get", "list", "update", "watch"},
699+
},
700+
{
701+
// Allows Linseed to watch namespaces before copying its token.
702+
// Also enables PolicyRecommendation to watch namespaces,
703+
// and Manager/KubeController to list them.
704+
APIGroups: []string{""},
705+
Resources: []string{"namespaces"},
706+
Verbs: []string{"get", "list", "watch"},
707+
},
708+
{
709+
// KubeController watches Nodes to monitor for deletions.
710+
// Manager performs a list operation on Nodes.
711+
APIGroups: []string{""},
712+
Resources: []string{"nodes"},
713+
Verbs: []string{"get", "list", "watch"},
714+
},
715+
{
716+
// KubeController watches Pods to verify existence for IPAM garbage collection.
717+
// Manager performs get operations on Pods.
718+
APIGroups: []string{""},
719+
Resources: []string{"pods"},
720+
Verbs: []string{"get", "list", "watch"},
721+
},
722+
{
723+
// The Federated Services Controller needs access to the remote kubeconfig secret
724+
// in order to create a remote syncer.
725+
APIGroups: []string{""},
726+
Resources: []string{"secrets"},
727+
Verbs: []string{"get", "list", "watch"},
728+
},
729+
{
730+
// Manager uses list; KubeController uses 'get', 'list', 'watch', 'update'.
731+
APIGroups: []string{""},
732+
Resources: []string{"services"},
733+
Verbs: []string{"get", "list", "update", "watch"},
734+
},
735+
{
736+
// Needed by KubeController to validate licenses; also used by ID.
737+
APIGroups: []string{"crd.projectcalico.org"},
738+
Resources: []string{"licensekeys"},
739+
Verbs: []string{"get", "watch"},
740+
},
741+
{
742+
// Manager uses list; PolicyRecommendation & ID uses all verbs.
743+
APIGroups: []string{"projectcalico.org"},
744+
Resources: []string{
745+
"globalnetworksets",
746+
"networkpolicies",
747+
"tier.networkpolicies",
748+
"stagednetworkpolicies",
749+
"tier.stagednetworkpolicies",
750+
},
751+
Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"},
752+
},
753+
{
754+
// Manager uses list; PolicyRecommendation uses all verbs.
755+
APIGroups: []string{"projectcalico.org"},
756+
Resources: []string{"tiers"},
757+
Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"},
758+
},
759+
760+
// Rules needed by guardian to handle manager requests.
761+
{
762+
APIGroups: []string{""},
763+
Resources: []string{"events"},
764+
Verbs: []string{"list"},
765+
},
766+
{
767+
APIGroups: []string{""},
768+
Resources: []string{"serviceaccounts"},
769+
Verbs: []string{"impersonate", "list"},
770+
},
771+
{
772+
// When a request is made in the manager UI, they are proxied through the Voltron backend server. If the
773+
// request is targeting a k8s api or when it is targeting a managed cluster, Voltron will authenticate the
774+
// user based on the auth header and then impersonate the user.
775+
APIGroups: []string{""},
776+
Resources: []string{"groups", "users"},
777+
Verbs: []string{"impersonate"},
778+
},
779+
{
780+
// Allow query server talk to Prometheus via the manager user.
781+
APIGroups: []string{""},
782+
Resources: []string{"services/proxy"},
783+
ResourceNames: []string{
784+
"calico-node-prometheus:9090",
785+
"https:tigera-api:8080",
786+
},
787+
Verbs: []string{"create", "get"},
788+
},
789+
{
790+
APIGroups: []string{"apps"},
791+
Resources: []string{"daemonsets", "replicasets", "statefulsets"},
792+
Verbs: []string{"list"},
793+
},
794+
{
795+
APIGroups: []string{"authentication.k8s.io"},
796+
Resources: []string{"tokenreviews"},
797+
Verbs: []string{"create"},
798+
},
799+
{
800+
APIGroups: []string{"authorization.k8s.io"},
801+
Resources: []string{"subjectaccessreviews"},
802+
Verbs: []string{"create"},
803+
},
804+
{
805+
APIGroups: []string{"networking.k8s.io"},
806+
Resources: []string{"networkpolicies"},
807+
Verbs: []string{"get", "list"},
808+
},
809+
{
810+
APIGroups: []string{"policy.networking.k8s.io"},
811+
Resources: []string{"adminnetworkpolicies", "baselineadminnetworkpolicies"},
812+
Verbs: []string{"list"},
813+
},
814+
{
815+
APIGroups: []string{"projectcalico.org"},
816+
Resources: []string{"alertexceptions"},
817+
Verbs: []string{"get", "list", "update"},
818+
},
819+
{
820+
APIGroups: []string{"projectcalico.org"},
821+
Resources: []string{"felixconfigurations"},
822+
ResourceNames: []string{"default"},
823+
Verbs: []string{"get"},
824+
},
825+
{
826+
APIGroups: []string{"projectcalico.org"},
827+
Resources: []string{
828+
"globalnetworkpolicies",
829+
"networksets",
830+
"stagedglobalnetworkpolicies",
831+
"stagedkubernetesnetworkpolicies",
832+
"tier.globalnetworkpolicies",
833+
"tier.stagedglobalnetworkpolicies",
834+
},
835+
Verbs: []string{"list"},
836+
},
837+
{
838+
APIGroups: []string{"projectcalico.org"},
839+
Resources: []string{"hostendpoints"},
840+
Verbs: []string{"list"},
841+
},
842+
843+
// Rules needed by guardian to handle policy recommendation requests.
844+
{
845+
APIGroups: []string{"projectcalico.org"},
846+
Resources: []string{
847+
"policyrecommendationscopes",
848+
"policyrecommendationscopes/status",
849+
},
850+
Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"},
851+
},
852+
853+
// Rules needed by guardian to handle calico-kube-controller requests.
854+
{
855+
// Nodes are watched to monitor for deletions.
856+
APIGroups: []string{""},
857+
Resources: []string{"endpoints"},
858+
Verbs: []string{"create", "delete", "get", "list", "update", "watch"},
859+
},
860+
{
861+
APIGroups: []string{""},
862+
Resources: []string{"services/status"},
863+
Verbs: []string{"get", "list", "update", "watch"},
864+
},
865+
{
866+
// Needs to manage hostendpoints.
867+
APIGroups: []string{"crd.projectcalico.org"},
868+
Resources: []string{"hostendpoints"},
869+
Verbs: []string{"create", "delete", "get", "list", "update", "watch"},
870+
},
871+
{
872+
// Needs access to update clusterinformations.
873+
APIGroups: []string{"crd.projectcalico.org"},
874+
Resources: []string{"clusterinformations"},
875+
Verbs: []string{"create", "get", "list", "update", "watch"},
876+
},
877+
{
878+
// Needs to manipulate kubecontrollersconfiguration, which contains its config.
879+
// It creates a default if none exists, and updates status as well.
880+
APIGroups: []string{"crd.projectcalico.org"},
881+
Resources: []string{"kubecontrollersconfigurations"},
882+
Verbs: []string{"create", "get", "list", "update", "watch"},
883+
},
884+
{
885+
APIGroups: []string{"crd.projectcalico.org"},
886+
Resources: []string{"tiers"},
887+
Verbs: []string{"create"},
888+
},
889+
{
890+
APIGroups: []string{"crd.projectcalico.org", "projectcalico.org"},
891+
Resources: []string{"deeppacketinspections"},
892+
Verbs: []string{"get", "list", "watch"},
893+
},
894+
{
895+
APIGroups: []string{"crd.projectcalico.org"},
896+
Resources: []string{"deeppacketinspections/status"},
897+
Verbs: []string{"update"},
898+
},
899+
{
900+
APIGroups: []string{"crd.projectcalico.org"},
901+
Resources: []string{"packetcaptures"},
902+
Verbs: []string{"get", "list", "update"},
903+
},
904+
{
905+
APIGroups: []string{"crd.projectcalico.org"},
906+
Resources: []string{"remoteclusterconfigurations"},
907+
Verbs: []string{"get", "list", "watch"},
908+
},
909+
{
910+
APIGroups: []string{"projectcalico.org"},
911+
Resources: []string{"licensekeys"},
912+
Verbs: []string{"create", "get", "list", "update", "watch"},
913+
},
914+
{
915+
// Grant permissions to access ClusterInformation resources in managed clusters.
916+
APIGroups: []string{"projectcalico.org"},
917+
Resources: []string{"clusterinformations"},
918+
Verbs: []string{"get", "list", "watch"},
919+
},
920+
{
921+
APIGroups: []string{"usage.tigera.io"},
922+
Resources: []string{"licenseusagereports"},
923+
Verbs: []string{"create", "delete", "get", "list", "update", "watch"},
924+
},
925+
926+
// Rules needed by guardian to handle Intrusion detection requests.
927+
{
928+
APIGroups: []string{""},
929+
Resources: []string{"podtemplates"},
930+
Verbs: []string{"get"},
931+
},
932+
{
933+
APIGroups: []string{"apps"},
934+
Resources: []string{"deployments"},
935+
Verbs: []string{"get"},
936+
},
937+
{
938+
APIGroups: []string{"crd.projectcalico.org"},
939+
Resources: []string{"alertexceptions"},
940+
Verbs: []string{"get", "list"},
941+
},
942+
{
943+
APIGroups: []string{"crd.projectcalico.org"},
944+
Resources: []string{"securityeventwebhooks"},
945+
Verbs: []string{"get", "list", "update", "watch"},
946+
},
947+
{
948+
APIGroups: []string{"projectcalico.org"},
949+
Resources: []string{
950+
"globalalerts",
951+
"globalalerts/status",
952+
"globalthreatfeeds",
953+
"globalthreatfeeds/status",
954+
},
955+
Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"},
956+
},
957+
}
958+
959+
// Rules needed by policy recommendation in openshift.
960+
if isOpenShift {
961+
rules = append(rules,
962+
rbacv1.PolicyRule{
963+
APIGroups: []string{"security.openshift.io"},
964+
Resources: []string{"securitycontextconstraints"},
965+
Verbs: []string{"use"},
966+
ResourceNames: []string{securitycontextconstraints.HostNetworkV2},
967+
},
968+
)
969+
}
970+
971+
return rules
972+
}
973+
693974
func deprecatedObjects() []client.Object {
694975
return []client.Object{
695976
// All the Guardian objects were moved to "calico-system" circa Calico v3.30, and so the legacy tigera-guardian
@@ -708,5 +989,23 @@ func deprecatedObjects() []client.Object {
708989
TypeMeta: metav1.TypeMeta{Kind: "ClusterRoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"},
709990
ObjectMeta: metav1.ObjectMeta{Name: "tigera-guardian"},
710991
},
992+
993+
// Remove manager namespace objects since the guardian identity is responsible for handling manager requests
994+
&corev1.ServiceAccount{
995+
TypeMeta: metav1.TypeMeta{Kind: "ServiceAccount", APIVersion: "v1"},
996+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-manager", Namespace: "tigera-manager"},
997+
},
998+
&corev1.Namespace{
999+
TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"},
1000+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-manager"},
1001+
},
1002+
&rbacv1.ClusterRole{
1003+
TypeMeta: metav1.TypeMeta{Kind: "ClusterRole", APIVersion: "rbac.authorization.k8s.io/v1"},
1004+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-manager-role"},
1005+
},
1006+
&rbacv1.ClusterRoleBinding{
1007+
TypeMeta: metav1.TypeMeta{Kind: "ClusterRoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"},
1008+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-manager-binding"},
1009+
},
7111010
}
7121011
}

0 commit comments

Comments
 (0)