@@ -42,6 +42,74 @@ def run_command(cmd):
4242
4343
4444class KubeconfigGenerator (object ):
45+ def _load_permission_data (self , permissionfile ):
46+ """Load permissions definition from JSON or YAML file."""
47+ with open (permissionfile , "r" , encoding = "utf-8" ) as fp :
48+ contents = fp .read ()
49+ try :
50+ perms_data = json .loads (contents )
51+ except json .JSONDecodeError :
52+ perms_data = yaml .safe_load (contents )
53+ if not isinstance (perms_data , dict ) or "perms" not in perms_data :
54+ raise ValueError ("Permission file must define a top-level 'perms' object." )
55+ if not isinstance (perms_data ["perms" ], dict ):
56+ raise ValueError ("'perms' must be a mapping of apiGroup to permission rules." )
57+ return perms_data ["perms" ]
58+
59+ def _parse_permission_rules (self , perms ):
60+ """Convert permission mapping to k8s rule list and tracked resources list."""
61+ rule_list = []
62+ resources = []
63+ for api_group , res_actions in perms .items ():
64+ for res in res_actions :
65+ for resource , verbs in res .items ():
66+ if resource not in resources :
67+ resources .append (resource .strip ())
68+ rule_group = {}
69+ if api_group == "non-apigroup" :
70+ if "nonResourceURL" in resource :
71+ parts = resource .split ("nonResourceURL::" )
72+ non_res = parts [1 ].strip () if len (parts ) > 1 else parts [0 ].strip ()
73+ rule_group ["nonResourceURLs" ] = [non_res ]
74+ rule_group ["verbs" ] = verbs
75+ else :
76+ rule_group ["apiGroups" ] = [api_group ]
77+ rule_group ["verbs" ] = verbs
78+ if "resourceName" in resource :
79+ parts = resource .split ("/resourceName::" )
80+ rule_group ["resources" ] = [parts [0 ].strip ()]
81+ rule_group ["resourceNames" ] = [parts [1 ].strip ()]
82+ else :
83+ rule_group ["resources" ] = [resource ]
84+ if rule_group :
85+ rule_list .append (rule_group )
86+ return rule_list , resources
87+
88+ def _read_perm_configmap_resources (self , sa , namespace , kubeconfig ):
89+ cfg_map_name = sa + "-perms"
90+ cfg_map_filename = sa + "-perms.txt"
91+ out , _ = run_command ("kubectl get configmap " + cfg_map_name + " -o json -n " + namespace + kubeconfig )
92+ kubeplus_perms = []
93+ if out :
94+ json_op = json .loads (out )
95+ perms_str = json_op .get ("data" , {}).get (cfg_map_filename , "" )
96+ for p in perms_str .replace ("'" , "" ).replace ("[" , "" ).replace ("]" , "" ).split ("," ):
97+ p = p .strip ()
98+ if p :
99+ kubeplus_perms .append (p )
100+ return kubeplus_perms
101+
102+ def _write_perm_configmap_resources (self , sa , namespace , kubeconfig , resources ):
103+ cfg_map_name = sa + "-perms"
104+ cfg_map_filename = sa + "-perms.txt"
105+ run_command ("kubectl delete configmap " + cfg_map_name + " -n " + namespace + kubeconfig )
106+ with open (cfg_map_filename , "w" , encoding = "utf-8" ) as fp :
107+ fp .write (str (sorted (set (resources ))))
108+ run_command (
109+ "kubectl create configmap " + cfg_map_name + " -n " + namespace
110+ + " --from-file=" + cfg_map_filename + kubeconfig
111+ )
112+
45113
46114
47115 def _create_kubecfg_file (self , sa , namespace , filename , token , ca , server , kubeconfig , cluster_name = None ):
@@ -581,35 +649,9 @@ def _apply_provider_rbac(self, sa, namespace, kubeconfig):
581649 )
582650
583651 def _update_rbac (self , permissionfile , sa , namespace , kubeconfig ):
584- """Add permissions from JSON file to provider (update command)."""
585- with open (permissionfile , "r" , encoding = "utf-8" ) as fp :
586- perms_data = json .load (fp )
587- perms = perms_data ["perms" ]
588- rule_list = []
589- new_resources = []
590-
591- for api_group , res_actions in perms .items ():
592- for res in res_actions :
593- for resource , verbs in res .items ():
594- if resource not in new_resources :
595- new_resources .append (resource .strip ())
596- rule_group = {}
597- if api_group == "non-apigroup" :
598- if "nonResourceURL" in resource :
599- parts = resource .split ("nonResourceURL::" )
600- non_res = parts [1 ].strip () if len (parts ) > 1 else parts [0 ].strip ()
601- rule_group ["nonResourceURLs" ] = [non_res ]
602- rule_group ["verbs" ] = verbs
603- else :
604- rule_group ["apiGroups" ] = [api_group ]
605- rule_group ["verbs" ] = verbs
606- if "resourceName" in resource :
607- parts = resource .split ("/resourceName::" )
608- rule_group ["resources" ] = [parts [0 ].strip ()]
609- rule_group ["resourceNames" ] = [parts [1 ].strip ()]
610- else :
611- rule_group ["resources" ] = [resource ]
612- rule_list .append (rule_group )
652+ """Add permissions from JSON/YAML file to provider (update command)."""
653+ perms = self ._load_permission_data (permissionfile )
654+ rule_list , new_resources = self ._parse_permission_rules (perms )
613655
614656 role = {
615657 "apiVersion" : "rbac.authorization.k8s.io/v1" ,
@@ -628,27 +670,37 @@ def _update_rbac(self, permissionfile, sa, namespace, kubeconfig):
628670 }
629671 create_role_rolebinding (role_binding , sa + "-update-rolebinding.yaml" , kubeconfig )
630672
631- cfg_map_name = sa + "-perms"
632- cfg_map_filename = sa + "-perms.txt"
633- out , _ = run_command ("kubectl get configmap " + cfg_map_name + " -o json -n " + namespace + kubeconfig )
634- kubeplus_perms = []
635- if out :
636- json_op = json .loads (out )
637- perms_str = json_op .get ("data" , {}).get (cfg_map_filename , "" )
638- for p in perms_str .replace ("'" , "" ).replace ("[" , "" ).replace ("]" , "" ).split ("," ):
639- p = p .strip ()
640- if p :
641- kubeplus_perms .append (p )
673+ kubeplus_perms = self ._read_perm_configmap_resources (sa , namespace , kubeconfig )
642674 new_resources .extend (kubeplus_perms )
675+ self ._write_perm_configmap_resources (sa , namespace , kubeconfig , new_resources )
643676
644- run_command ("kubectl delete configmap " + cfg_map_name + " -n " + namespace + kubeconfig )
645- new_resources = sorted (set (new_resources ))
646- with open (cfg_map_filename , "w" , encoding = "utf-8" ) as fp :
647- fp .write (str (new_resources ))
648- run_command (
649- "kubectl create configmap " + cfg_map_name + " -n " + namespace
650- + " --from-file=" + cfg_map_filename + kubeconfig
651- )
677+ def _revoke_rbac (self , permissionfile , sa , namespace , kubeconfig ):
678+ """Revoke permissions from JSON/YAML file for provider/consumer update role."""
679+ perms = self ._load_permission_data (permissionfile )
680+ revoke_rule_list , revoke_resources = self ._parse_permission_rules (perms )
681+ revoke_norm = set (tuple (sorted (r .items ())) for r in self ._normalize_rule_list (revoke_rule_list ))
682+
683+ role_name = sa + "-update"
684+ out , _ = run_command ("kubectl get clusterrole " + role_name + " -o json" + kubeconfig )
685+ if out :
686+ role_obj = json .loads (out )
687+ existing_rules = role_obj .get ("rules" , [])
688+ remaining_rules = []
689+ for rule in existing_rules :
690+ norm = self ._normalize_rule (rule )
691+ norm_key = tuple (sorted (norm .items ()))
692+ if norm_key not in revoke_norm :
693+ remaining_rules .append (rule )
694+ if remaining_rules :
695+ role_obj ["rules" ] = remaining_rules
696+ create_role_rolebinding (role_obj , sa + "-update-role.yaml" , kubeconfig )
697+ else :
698+ run_command ("kubectl delete clusterrole " + role_name + kubeconfig )
699+ run_command ("kubectl delete clusterrolebinding " + role_name + kubeconfig )
700+
701+ current_resources = self ._read_perm_configmap_resources (sa , namespace , kubeconfig )
702+ remaining_resources = [res for res in current_resources if res not in set (revoke_resources )]
703+ self ._write_perm_configmap_resources (sa , namespace , kubeconfig , remaining_resources )
652704
653705
654706 def _apply_rbac (self , sa , namespace , entity = '' , kubeconfig = '' ):
@@ -764,7 +816,7 @@ def _generate_kubeconfig(self, sa, namespace, filename, api_server_ip="", kubeco
764816 "The generated kubeconfig includes the namespace in the context so kubectl defaults to it; "
765817 "consumer RBAC restricts what operations are allowed." ,
766818 )
767- parser .add_argument ("action" , help = "command" , choices = ['create' , 'delete' , 'update' , 'extract' ])
819+ parser .add_argument ("action" , help = "command" , choices = ['create' , 'delete' , 'update' , 'revoke' , ' extract' ])
768820 parser .add_argument (
769821 "namespace" ,
770822 help = "Namespace where the ServiceAccount is created. "
@@ -788,7 +840,7 @@ def _generate_kubeconfig(self, sa, namespace, filename, api_server_ip="", kubeco
788840 "-x" , "--clustername" ,
789841 help = "Cluster name for context and cluster in the generated kubeconfig file." ,
790842 )
791- permission_help = "Permissions file - use with update command. "
843+ permission_help = "Permissions file - use with update or revoke command. "
792844 permission_help += "JSON structure: {perms:{<apiGroup>:[{resource|resource/resourceName::<name>:[verbs]},...]}}"
793845 parser .add_argument ("-p" , "--permissionfile" , help = permission_help )
794846 parser .add_argument (
@@ -804,7 +856,7 @@ def _generate_kubeconfig(self, sa, namespace, filename, api_server_ip="", kubeco
804856 permission_file = pargs .permissionfile or ""
805857 cluster_name = pargs .clustername or ""
806858
807- if action == 'update' and permission_file == '' :
859+ if action in [ 'update' , 'revoke' ] and permission_file == '' :
808860 print ("Permission file missing. Please provide -p/--permissionfile." )
809861 sys .exit (1 )
810862
@@ -849,6 +901,10 @@ def _generate_kubeconfig(self, sa, namespace, filename, api_server_ip="", kubeco
849901 kubeconfigGenerator ._update_rbac (permission_file , sa , namespace , kubeconfigString )
850902 print ("kubeconfig permissions updated: " + filename )
851903
904+ if action == "revoke" :
905+ kubeconfigGenerator ._revoke_rbac (permission_file , sa , namespace , kubeconfigString )
906+ print ("kubeconfig permissions revoked: " + filename )
907+
852908
853909 if action == "delete" :
854910 run_command ("kubectl delete sa " + sa + " -n " + namespace + kubeconfigString )
0 commit comments