From c24763dd27fc053ae954b56f6c44fa680b079f00 Mon Sep 17 00:00:00 2001 From: Chirayu Kapoor Date: Thu, 4 Jul 2024 17:26:30 +0530 Subject: [PATCH] Refactoring * Use python kube-client * Modularize delete functionality * Add -n, --namespace parameter for namespace * Error handling Signed-off-by: Chirayu Kapoor --- .github/workflows/pr.yaml | 4 +- README.md | 2 +- kubeplus-saas-provider.json | 1 + provider-kubeconfig.py | 1516 +++++++++++++++++------------------ requirements.txt | 3 +- 5 files changed, 729 insertions(+), 797 deletions(-) create mode 100644 kubeplus-saas-provider.json diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index d86e6c37..d4770c18 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -6,7 +6,7 @@ env: KUBEPLUS_TEST_OUTPUT: yes jobs: job1: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 name: Deploy to minikube steps: - uses: actions/checkout@v2 @@ -52,7 +52,7 @@ jobs: pip3 install -r requirements.txt apiserver=`kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'` echo "API_SERVER_URL:$apiserver" - python3 provider-kubeconfig.py -s $apiserver create $KUBEPLUS_NS + python3 provider-kubeconfig.py create -s $apiserver -n $KUBEPLUS_NS deactivate echo "Building mutating-webhook..." diff --git a/README.md b/README.md index d2166f30..a8f1c1a3 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Let’s look at an example of creating a multi-instance WordPress Service using source venv/bin/activate pip3 install -r requirements.txt apiserver=`kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'` - python3 provider-kubeconfig.py -s $apiserver create $KUBEPLUS_NS + python3 provider-kubeconfig.py create -s $apiserver -n $KUBEPLUS_NS deactivate ``` diff --git a/kubeplus-saas-provider.json b/kubeplus-saas-provider.json new file mode 100644 index 00000000..58e6b73a --- /dev/null +++ b/kubeplus-saas-provider.json @@ -0,0 +1 @@ +{"apiVersion": "v1", "kind": "Config", "clusters": [{"name": "kubeplus-saas-provider", "cluster": {"server": "https://127.0.0.1:37325", "insecure-skip-tls-verify": true}}], "users": [{"name": "kubeplus-saas-provider", "user": {"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IlcxbXU1VUU3UDhaUTlLekNMZUVYZ3ZhSC1NUFF5cEpYLVFVTk1fdmt1aFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhYmMiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoia3ViZXBsdXMtc2Fhcy1wcm92aWRlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrdWJlcGx1cy1zYWFzLXByb3ZpZGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNjM1Mjc1NGMtNmY5OS00YmMyLTkzNWQtZjcyZjY2MzM3YjkwIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmFiYzprdWJlcGx1cy1zYWFzLXByb3ZpZGVyIn0.IvI9d_GB9uAHGOpjbo_PBDFF7nadXI_d8Iwe-Vv-48O4bADcNyW12c2I4zdOoPwVIitGc_3j4CYB4qtNdvdVzklS3W_orsFaq2WfU3Hg58E39QYp1yLwgeF7ef2YpMjc-aNtXRNY9wKEKcmGgOIu4rTFEncB-IK1nsFAxIlHj7pHpvMF6aT98MdgggkTlnn98Ku63nnNVm-sem_mFSHq-evkxm3X7Ljb2E683Mc2VxuioGUyR3BKM_LIsEzyPHgLDZg9vVWji6fq_6QGBJWqA_8AqB8tYO2RI_M67R3VwpqW9EbGI9UcUFxgDcgCxShccnhGsAWBBChYbL63Z-3QLg"}}], "contexts": [{"name": "kubeplus-saas-provider", "context": {"cluster": "kubeplus-saas-provider", "user": "kubeplus-saas-provider", "namespace": "abc"}}], "current-context": "kubeplus-saas-provider"} \ No newline at end of file diff --git a/provider-kubeconfig.py b/provider-kubeconfig.py index 0bf1a309..e06c106e 100644 --- a/provider-kubeconfig.py +++ b/provider-kubeconfig.py @@ -1,11 +1,13 @@ -import sys import json import subprocess -import sys import os import yaml -import time import argparse +from kubernetes import client, config, watch +from kubernetes.client.rest import ApiException +from urllib.parse import urlparse, urlunparse +import base64 + from logging.config import dictConfig @@ -29,806 +31,734 @@ } }) - - -def create_role_rolebinding(contents, name, kubeconfig): - filePath = os.getcwd() + "/" + name - fp = open(filePath, "w") - #json_content = json.dumps(contents) - #fp.write(json_content) - yaml_content = yaml.dump(contents) - fp.write(yaml_content) - fp.close() - #print("---") - #print(yaml_content) - #print("---") - cmd = " kubectl apply -f " + filePath + kubeconfig - run_command(cmd) - - -def run_command(cmd): - #print("Inside run_command") - #print(cmd) - cmdOut = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() - out = cmdOut[0].decode('utf-8') - err = cmdOut[1].decode('utf-8') - #print(out) - #print("---") - #print(err) - return out, err - +DEFAULT_KUBECONFIG_PATH = "~/.kube/config" +PROVIDER_KUBECONFIG = "kubeplus-saas-provider.json" class KubeconfigGenerator(object): - def run_command(self, cmd): - #print("Inside run_command") - #print(cmd) - cmdOut = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() - out = cmdOut[0] - err = cmdOut[1] - #print(out) - #print("---") - #print(err) - return out, err - - def _create_kubecfg_file(self, sa, namespace, filename, token, ca, server, kubeconfig): - #print("Creating kubecfg file") - top_level_dict = {} - top_level_dict["apiVersion"] = "v1" - top_level_dict["kind"] = "Config" - - contextName = sa - - usersList = [] - usertoken = {} - usertoken["token"] = token - userInfo = {} - userInfo["name"] = sa - userInfo["user"] = usertoken - usersList.append(userInfo) - top_level_dict["users"] = usersList - - clustersList = [] - cluster_details = {} - cluster_details["server"] = server - - # TODO: Use the certificate authority to perform tls - # cluster_details["certificate-authority-data"] = ca - cluster_details["insecure-skip-tls-verify"] = True - - clusterInfo = {} - clusterInfo["cluster"] = cluster_details - clusterInfo["name"] = sa - clustersList.append(clusterInfo) - top_level_dict["clusters"] = clustersList - - context_details = {} - context_details["cluster"] = sa - context_details["user"] = sa - context_details["namespace"] = namespace - contextInfo = {} - contextInfo["context"] = context_details - contextInfo["name"] = contextName - contextList = [] - contextList.append(contextInfo) - top_level_dict["contexts"] = contextList - - top_level_dict["current-context"] = contextName - - json_file = json.dumps(top_level_dict) - #print("kubecfg file:" + json_file) - - fp = open(os.getcwd() + "/" + filename, "w") - fp.write(json_file) - fp.close() - - configmapName = sa - created = False - while not created: - cmd = "kubectl create configmap " + configmapName + " -n " + namespace + " --from-file=" + os.getcwd() + "/" + filename + kubeconfig - self.run_command(cmd) - get_cmd = "kubectl get configmap " + configmapName + " -n " + namespace + kubeconfig - output, err = self.run_command(get_cmd) - output = output.decode('utf-8') - if 'Error from server (NotFound)' in output: - time.sleep(2) - print("Trying again..") - else: - created = True - - - def _apply_consumer_rbac(self, sa, namespace, kubeconfig): - role = {} - role["apiVersion"] = "rbac.authorization.k8s.io/v1" - role["kind"] = "ClusterRole" - metadata = {} - metadata["name"] = sa - role["metadata"] = metadata - - # Read all resources - ruleGroup1 = {} - apiGroup1 = ["*",""] - resourceGroup1 = ["*"] - verbsGroup1 = ["get","watch","list"] - ruleGroup1["apiGroups"] = apiGroup1 - ruleGroup1["resources"] = resourceGroup1 - ruleGroup1["verbs"] = verbsGroup1 - - # Impersonate users, groups, serviceaccounts - ruleGroup9 = {} - apiGroup9 = [""] - resourceGroup9 = ["users","groups","serviceaccounts"] - verbsGroup9 = ["impersonate"] - ruleGroup9["apiGroups"] = apiGroup9 - ruleGroup9["resources"] = resourceGroup9 - ruleGroup9["verbs"] = verbsGroup9 - - # Pod/portforward to open consumerui - ruleGroup10 = {} - apiGroup10 = [""] - resourceGroup10 = ["pods/portforward"] - verbsGroup10 = ["create","get"] - ruleGroup10["apiGroups"] = apiGroup10 - ruleGroup10["resources"] = resourceGroup10 - ruleGroup10["verbs"] = verbsGroup10 - - ruleList = [] - ruleList.append(ruleGroup1) - ruleList.append(ruleGroup9) - ruleList.append(ruleGroup10) - role["rules"] = ruleList - - roleName = sa + "-role-impersonate.yaml" - create_role_rolebinding(role, roleName, kubeconfig) - - roleBinding = {} - roleBinding["apiVersion"] = "rbac.authorization.k8s.io/v1" - roleBinding["kind"] = "ClusterRoleBinding" - metadata = {} - metadata["name"] = sa - roleBinding["metadata"] = metadata - - subject = {} - subject["kind"] = "ServiceAccount" - subject["name"] = sa - subject["apiGroup"] = "" - subject["namespace"] = namespace - subjectList = [] - subjectList.append(subject) - roleBinding["subjects"] = subjectList - - roleRef = {} - roleRef["kind"] = "ClusterRole" - roleRef["name"] = sa - roleRef["apiGroup"] = "rbac.authorization.k8s.io" - roleBinding["roleRef"] = roleRef - - roleBindingName = sa + "-rolebinding-impersonate.yaml" - create_role_rolebinding(roleBinding, roleBindingName, kubeconfig) - - def _apply_provider_rbac(self, sa, namespace, kubeconfig): - role = {} - role["apiVersion"] = "rbac.authorization.k8s.io/v1" - role["kind"] = "ClusterRole" - metadata = {} - metadata["name"] = sa - role["metadata"] = metadata - - # all resources - all_resources = [] - - # Read all resources - ruleGroup1 = {} - apiGroup1 = ["*",""] - resourceGroup1 = ["*"] - verbsGroup1 = ["get","watch","list"] - ruleGroup1["apiGroups"] = apiGroup1 - ruleGroup1["resources"] = resourceGroup1 - ruleGroup1["verbs"] = verbsGroup1 - - # CRUD on resourcecompositions et. al. - ruleGroup2 = {} - apiGroup2 = ["workflows.kubeplus"] - resourceGroup2 = ["resourcecompositions","resourcemonitors","resourcepolicies","resourceevents"] - verbsGroup2 = ["get","watch","list","create","delete","update","patch"] - ruleGroup2["apiGroups"] = apiGroup2 - ruleGroup2["resources"] = resourceGroup2 - ruleGroup2["verbs"] = verbsGroup2 - all_resources.extend(resourceGroup2) - - # CRUD on clusterroles and clusterrolebindings - ruleGroup3 = {} - apiGroup3 = ["rbac.authorization.k8s.io"] - resourceGroup3 = ["clusterroles","clusterrolebindings","roles","rolebindings"] - verbsGroup3 = ["get","watch","list","create","delete","update","patch","deletecollection"] - ruleGroup3["apiGroups"] = apiGroup3 - ruleGroup3["resources"] = resourceGroup3 - ruleGroup3["verbs"] = verbsGroup3 - all_resources.extend(resourceGroup3) - - # CRUD on Port forward - ruleGroup4 = {} - apiGroup4 = [""] - resourceGroup4 = ["pods/portforward"] - verbsGroup4 = ["get","watch","list","create","delete","update","patch"] - ruleGroup4["apiGroups"] = apiGroup4 - ruleGroup4["resources"] = resourceGroup4 - ruleGroup4["verbs"] = verbsGroup4 - all_resources.extend(resourceGroup4) - - # CRUD on platformapi.kubeplus - ruleGroup5 = {} - apiGroup5 = ["platformapi.kubeplus"] - resourceGroup5 = ["*"] - verbsGroup5 = ["get","watch","list","create","delete","update","patch"] - ruleGroup5["apiGroups"] = apiGroup5 - ruleGroup5["resources"] = resourceGroup5 - ruleGroup5["verbs"] = verbsGroup5 - - # CRUD on secrets, serviceaccounts, configmaps - ruleGroup6 = {} - apiGroup6 = [""] - resourceGroup6 = ["secrets", "serviceaccounts", "configmaps","events","persistentvolumeclaims","serviceaccounts/token","services","services/proxy","endpoints"] - verbsGroup6 = ["get","watch","list","create","delete","update","patch", "deletecollection"] - ruleGroup6["apiGroups"] = apiGroup6 - ruleGroup6["resources"] = resourceGroup6 - ruleGroup6["verbs"] = verbsGroup6 - all_resources.extend(resourceGroup6) - - # CRUD on namespaces - ruleGroup7 = {} - apiGroup7 = [""] - resourceGroup7 = ["namespaces"] - verbsGroup7 = ["get","watch","list","create","delete","update","patch"] - ruleGroup7["apiGroups"] = apiGroup7 - ruleGroup7["resources"] = resourceGroup7 - ruleGroup7["verbs"] = verbsGroup7 - all_resources.extend(resourceGroup7) - - # CRUD on Deployments - ruleGroup8 = {} - apiGroup8 = ["apps"] - resourceGroup8 = ["deployments","daemonsets","deployments/rollback","deployments/scale","replicasets","replicasets/scale","statefulsets","statefulsets/scale"] - verbsGroup8 = ["get","watch","list","create","delete","update","patch","deletecollection"] - ruleGroup8["apiGroups"] = apiGroup8 - ruleGroup8["resources"] = resourceGroup8 - ruleGroup8["verbs"] = verbsGroup8 - all_resources.extend(resourceGroup8) - - # Impersonate users, groups, serviceaccounts - ruleGroup9 = {} - apiGroup9 = [""] - resourceGroup9 = ["users","groups","serviceaccounts"] - verbsGroup9 = ["impersonate"] - ruleGroup9["apiGroups"] = apiGroup9 - ruleGroup9["resources"] = resourceGroup9 - ruleGroup9["verbs"] = verbsGroup9 - all_resources.extend(resourceGroup9) - - # Exec into the Pods and others in the "" apiGroup - ruleGroup10 = {} - apiGroup10 = [""] - resourceGroup10 = ["pods","pods/attach","pods/exec","pods/portforward","pods/proxy","pods/eviction","replicationcontrollers","replicationcontrollers/scale"] - verbsGroup10 = ["get","list","create","update","delete","watch","patch","deletecollection"] - ruleGroup10["apiGroups"] = apiGroup10 - ruleGroup10["resources"] = resourceGroup10 - ruleGroup10["verbs"] = verbsGroup10 - all_resources.extend(resourceGroup10) - - # AdmissionRegistration - ruleGroup11 = {} - apiGroup11 = ["admissionregistration.k8s.io"] - resourceGroup11 = ["mutatingwebhookconfigurations"] - verbsGroup11 = ["get","create","delete","update"] - ruleGroup11["apiGroups"] = apiGroup11 - ruleGroup11["resources"] = resourceGroup11 - ruleGroup11["verbs"] = verbsGroup11 - all_resources.extend(resourceGroup11) - - # APIExtension - ruleGroup12 = {} - apiGroup12 = ["apiextensions.k8s.io"] - resourceGroup12 = ["customresourcedefinitions"] - verbsGroup12 = ["get","create","delete","update", "patch"] - ruleGroup12["apiGroups"] = apiGroup12 - ruleGroup12["resources"] = resourceGroup12 - ruleGroup12["verbs"] = verbsGroup12 - all_resources.extend(resourceGroup12) - - # Certificates - ruleGroup13 = {} - apiGroup13 = ["certificates.k8s.io"] - resourceGroup13 = ["signers"] - resourceNames13 = ["kubernetes.io/legacy-unknown","kubernetes.io/kubelet-serving","kubernetes.io/kube-apiserver-client","cloudark.io/kubeplus"] - verbsGroup13 = ["get","create","delete","update", "patch", "approve"] - ruleGroup13["apiGroups"] = apiGroup13 - ruleGroup13["resources"] = resourceGroup13 - ruleGroup13["resourceNames"] = resourceNames13 - ruleGroup13["verbs"] = verbsGroup13 - all_resources.extend(resourceGroup13) - - # Read all - ruleGroup14 = {} - apiGroup14 = ["*"] - resourceGroup14 = ["*"] - verbsGroup14 = ["get"] - ruleGroup14["apiGroups"] = apiGroup14 - ruleGroup14["resources"] = resourceGroup14 - ruleGroup14["verbs"] = verbsGroup14 - - ruleGroup15 = {} - apiGroup15 = ["certificates.k8s.io"] - resourceGroup15 = ["certificatesigningrequests", "certificatesigningrequests/approval"] - verbsGroup15 = ["create","delete","update", "patch"] - ruleGroup15["apiGroups"] = apiGroup15 - ruleGroup15["resources"] = resourceGroup15 - ruleGroup15["verbs"] = verbsGroup15 - all_resources.extend(resourceGroup15) - - ruleGroup16 = {} - apiGroup16 = ["extensions"] - resourceGroup16 = ["deployments","daemonsets","deployments/rollback","deployments/scale","replicasets","replicasets/scale","replicationcontrollers/scale","ingresses","networkpolicies"] - verbsGroup16 = ["get","watch","list","create","delete","update","patch","deletecollection"] - ruleGroup16["apiGroups"] = apiGroup16 - ruleGroup16["resources"] = resourceGroup16 - ruleGroup16["verbs"] = verbsGroup16 - all_resources.extend(resourceGroup16) - - ruleGroup17 = {} - apiGroup17 = ["networking.k8s.io"] - resourceGroup17 = ["ingresses","networkpolicies"] - verbsGroup17 = ["get","watch","list","create","delete","update","patch","deletecollection"] - ruleGroup17["apiGroups"] = apiGroup17 - ruleGroup17["resources"] = resourceGroup17 - ruleGroup17["verbs"] = verbsGroup17 - all_resources.extend(resourceGroup17) - - ruleGroup18 = {} - apiGroup18 = ["authorization.k8s.io"] - resourceGroup18 = ["localsubjectaccessreviews"] - verbsGroup18 = ["create"] - ruleGroup18["apiGroups"] = apiGroup18 - ruleGroup18["resources"] = resourceGroup18 - ruleGroup18["verbs"] = verbsGroup18 - all_resources.extend(resourceGroup18) - - ruleGroup19 = {} - apiGroup19 = ["autoscaling"] - resourceGroup19 = ["horizontalpodautoscalers"] - verbsGroup19 = ["create", "delete", "deletecollection", "patch", "update"] - ruleGroup19["apiGroups"] = apiGroup19 - ruleGroup19["resources"] = resourceGroup19 - ruleGroup19["verbs"] = verbsGroup19 - all_resources.extend(resourceGroup19) - - ruleGroup20 = {} - apiGroup20 = ["batch"] - resourceGroup20 = ["cronjobs","jobs"] - verbsGroup20 = ["create", "delete", "deletecollection", "patch", "update"] - ruleGroup20["apiGroups"] = apiGroup20 - ruleGroup20["resources"] = resourceGroup20 - ruleGroup20["verbs"] = verbsGroup20 - all_resources.extend(resourceGroup20) - - ruleGroup21 = {} - apiGroup21 = ["policy"] - resourceGroup21 = ["poddisruptionbudgets"] - verbsGroup21 = ["create", "delete", "deletecollection", "patch", "update"] - ruleGroup21["apiGroups"] = apiGroup21 - ruleGroup21["resources"] = resourceGroup21 - ruleGroup21["verbs"] = verbsGroup21 - all_resources.extend(resourceGroup21) - - ruleGroup22 = {} - apiGroup22 = [""] - resourceGroup22 = ["resourcequotas"] - verbsGroup22 = ["create", "delete", "deletecollection", "patch", "update"] - ruleGroup22["apiGroups"] = apiGroup22 - ruleGroup22["resources"] = resourceGroup22 - ruleGroup22["verbs"] = verbsGroup22 - all_resources.extend(resourceGroup22) - - ruleList = [] - ruleList.append(ruleGroup1) - ruleList.append(ruleGroup2) - ruleList.append(ruleGroup3) - ruleList.append(ruleGroup4) - ruleList.append(ruleGroup5) - ruleList.append(ruleGroup6) - ruleList.append(ruleGroup7) - ruleList.append(ruleGroup8) - ruleList.append(ruleGroup9) - ruleList.append(ruleGroup10) - ruleList.append(ruleGroup11) - ruleList.append(ruleGroup12) - ruleList.append(ruleGroup13) - ruleList.append(ruleGroup14) - ruleList.append(ruleGroup15) - ruleList.append(ruleGroup16) - ruleList.append(ruleGroup17) - ruleList.append(ruleGroup18) - ruleList.append(ruleGroup19) - ruleList.append(ruleGroup20) - ruleList.append(ruleGroup21) - ruleList.append(ruleGroup22) - - role["rules"] = ruleList - - roleName = sa + "-role.yaml" - create_role_rolebinding(role, roleName, kubeconfig) - - roleBinding = {} - roleBinding["apiVersion"] = "rbac.authorization.k8s.io/v1" - roleBinding["kind"] = "ClusterRoleBinding" - metadata = {} - metadata["name"] = sa - roleBinding["metadata"] = metadata - - subject = {} - subject["kind"] = "ServiceAccount" - subject["name"] = sa - subject["apiGroup"] = "" - subject["namespace"] = namespace - subjectList = [] - subjectList.append(subject) - roleBinding["subjects"] = subjectList - - roleRef = {} - roleRef["kind"] = "ClusterRole" - roleRef["name"] = sa - roleRef["apiGroup"] = "rbac.authorization.k8s.io" - roleBinding["roleRef"] = roleRef - - roleBindingName = sa + "-rolebinding.yaml" - create_role_rolebinding(roleBinding, roleBindingName, kubeconfig) - - # create configmap to store all resources - fp = open("kubeplus-saas-provider-perms.txt", "w") - all_resources.sort() - all_resources_uniq = [] - [all_resources_uniq.append(x) for x in all_resources if x not in all_resources_uniq] - fp.write(str(all_resources_uniq)) - fp.close() - cmd = "kubectl create configmap kubeplus-saas-provider-perms -n " + namespace + " --from-file=kubeplus-saas-provider-perms.txt" - self.run_command(cmd) - - def _update_rbac(self, permissionfile, sa, namespace, kubeconfig): - role = {} - role["apiVersion"] = "rbac.authorization.k8s.io/v1" - role["kind"] = "ClusterRole" - metadata = {} - metadata["name"] = sa + "-update" - role["metadata"] = metadata - - ruleList = [] - ruleGroup = {} - - fp = open(permissionfile, "r") - data = fp.read() - perms_data = json.loads(data) - perms = perms_data["perms"] - new_resources = [] - for apiGroup, res_actions in perms.items(): - for res in res_actions: - for resource, verbs in res.items(): - print(apiGroup + " " + resource + " " + str(verbs)) - if resource not in new_resources: - new_resources.append(resource.strip()) - ruleGroup = {} - if apiGroup == "non-apigroup": - if 'nonResourceURL' in resource: - parts = resource.split("nonResourceURL::") - nonRes = parts[0].strip() - ruleGroup['nonResourceURLs'] = [nonRes] - ruleGroup['verbs'] = verbs - else: - ruleGroup["apiGroups"] = [apiGroup] - ruleGroup["verbs"] = verbs - if 'resourceName' in resource: - parts = resource.split("/resourceName::") - resNameParent = parts[0].strip() - resName = parts[1].strip() - ruleGroup["resources"] = [resNameParent] - ruleGroup["resourceNames"] = [resName] - else: - ruleGroup["resources"] = [resource] - - - ruleList.append(ruleGroup) - - role["rules"] = ruleList - - roleName = sa + "-update-role.yaml" - create_role_rolebinding(role, roleName, kubeconfig) - - roleBinding = {} - roleBinding["apiVersion"] = "rbac.authorization.k8s.io/v1" - roleBinding["kind"] = "ClusterRoleBinding" - metadata = {} - metadata["name"] = sa + "-update" - roleBinding["metadata"] = metadata - - subject = {} - subject["kind"] = "ServiceAccount" - subject["name"] = sa - subject["apiGroup"] = "" - subject["namespace"] = namespace - subjectList = [] - subjectList.append(subject) - roleBinding["subjects"] = subjectList - - roleRef = {} - roleRef["kind"] = "ClusterRole" - roleRef["name"] = sa + "-update" - roleRef["apiGroup"] = "rbac.authorization.k8s.io" - roleBinding["roleRef"] = roleRef - - roleBindingName = sa + "-update-rolebinding.yaml" - create_role_rolebinding(roleBinding, roleBindingName, kubeconfig) - - # Read configmap to get earlier permissions; delete it and create it with all new permissions: - cmd = "kubectl get configmap kubeplus-saas-provider-perms -o json -n " + namespace - out1, err1 = self.run_command(cmd) - print("Original Perms Out:" + str(out1)) - print("Perms Err:" + str(err1)) - kubeplus_perms = [] - if out1 != '': - json_op = json.loads(out1) - perms = json_op['data']['kubeplus-saas-provider-perms.txt'] - print(perms) - k_perms = perms.split(",") - for p in k_perms: - p = p.replace("'","") - p = p.replace("[","") - p = p.replace("]","") - p = p.strip() - kubeplus_perms.append(p) - - new_resources.extend(kubeplus_perms) - - print("New perms:" + str(new_resources)) - - cmd = "kubectl delete configmap kubeplus-saas-provider-perms -n " + namespace - self.run_command(cmd) - - # create configmap to store all resources - fp = open("kubeplus-saas-provider-perms.txt", "w") - new_resources.sort() - new_resources_uniq = [] - [new_resources_uniq.append(x) for x in new_resources if x not in new_resources_uniq] - fp.write(str(new_resources_uniq)) - fp.close() - cmd = "kubectl create configmap kubeplus-saas-provider-perms -n " + namespace + " --from-file=kubeplus-saas-provider-perms.txt" - self.run_command(cmd) - - - def _apply_rbac(self, sa, namespace, entity='', kubeconfig=''): - if entity == 'provider': - self._apply_provider_rbac(sa, namespace, kubeconfig) - if entity == 'consumer': - self._apply_consumer_rbac(sa, namespace, kubeconfig) - - def _create_secret(self, sa, namespace, kubeconfig): - - annotations = {} - annotations['kubernetes.io/service-account.name'] = sa - - metadata = {} - metadata['name'] = sa - metadata['namespace'] = namespace - metadata['annotations'] = annotations - - secret = {} - secret['apiVersion'] = "v1" - secret['kind'] = "Secret" - secret['metadata'] = metadata - secret['type'] = 'kubernetes.io/service-account-token' - - secretName = sa + "-secret.yaml" - - filePath = os.getcwd() + "/" + secretName - fp = open(filePath, "w") - yaml_content = yaml.dump(secret) - fp.write(yaml_content) - fp.close() - #print("---") - #print(yaml_content) - #print("---") - created = False - count = 0 - while not created and count < 5: - cmd = " kubectl create -f " + filePath + kubeconfig - out, err = self.run_command(cmd) - if out != '': - out = out.decode('utf-8').strip() - #print(out) - if 'created' in out: - created = True - else: - time.sleep(2) - count = count + 1 - #print("Create secret:" + out) - if not created and count >= 5: - print(err) - sys.exit() - return out - - def _extract_kubeconfig(self, sa, namespace, filename, serverip='', kubecfg=''): - #print("Extracting kubeconfig") - secretName = sa - tokenFound = False - kubeconfig = kubecfg - api_server_ip = serverip - cmdprefix = "" - while not tokenFound: - cmd1 = " kubectl describe secret " + secretName + " -n " + namespace + kubeconfig - cmdToRun = cmdprefix + " " + cmd1 - out1 = subprocess.Popen(cmdToRun, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] - out1 = out1.decode('utf-8') - #print(out1) - token = '' - for line in out1.split("\n"): - if 'token' in line: - parts = line.split(":") - token = parts[1].strip() - if token != '': - tokenFound = True + def run_command(self, cmd): + #print("Inside run_command") + #print(cmd) + cmdOut = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() + out = cmdOut[0] + err = cmdOut[1] + #print(out) + #print("---") + #print(err) + return out, err + + def _create_kubecfg_file(self, sa, namespace, filename, token, server): + # Initialize the kubeconfig dictionary + kubeconfig = { + "apiVersion": "v1", + "kind": "Config", + "clusters": [ + { + "name": sa, + "cluster": { + #"certificate-authority-data": ca, + "server": server, + "insecure-skip-tls-verify": True + } + } + ], + "users": [ + { + "name": sa, + "user": { + "token": token, + } + } + ], + "contexts": [ + { + "name": sa, + "context": { + "cluster": sa, + "user": sa, + "namespace": namespace, + } + } + ], + "current-context": sa, + } + + # Write the kubeconfig to a file + kubeconfig_json = json.dumps(kubeconfig) + with open(filename, 'w') as f: + f.write(kubeconfig_json) + + print(f"Kubeconfig file '{filename}' created successfully.") + + # Create ConfigMap + configmap_body = client.V1ConfigMap( + api_version = "v1", + kind = "ConfigMap", + metadata = {"name": sa}, + data = {filename: kubeconfig_json}, + ) + + try: + corev1.create_namespaced_config_map(namespace=namespace, body=configmap_body) + except ApiException as e: + print(f"Exception when creating ConfigMap: {e}\n") + + def _apply_consumer_rbac(self, sa, namespace): + + # Cluster role + + rbac_v1 = client.RbacAuthorizationV1Api() + + metadata = client.V1ObjectMeta(name=sa) + + rules = [ + client.V1PolicyRule(api_groups=["*", ""], resources=["*"], verbs=["get", "watch", "list"]), + client.V1PolicyRule(api_groups=[""], resources=["users", "groups", "serviceaccounts"], verbs=["impersonate"]), + client.V1PolicyRule(api_groups=[""], resources=["pods/portforward"], verbs=["create", "get"]) + ] + + # Create the ClusterRole object + cluster_role = client.V1ClusterRole( + api_version="rbac.authorization.k8s.io/v1", + kind="ClusterRole", + metadata=metadata, + rules=rules + ) + + try: + rbac_v1.create_cluster_role(body=cluster_role) + print(f"ClusterRole '{sa}' created successfully.") + except client.exceptions.ApiException as e: + print(f"Exception when creating ClusterRole: {e}\n") + + # Cluster role binding + + subject = client.V1Subject( + kind="ServiceAccount", + name=sa, + namespace=namespace + ) + + role_ref = client.V1RoleRef( + kind="ClusterRole", + name=sa, + api_group="rbac.authorization.k8s.io" + ) + + cluster_role_binding = client.V1ClusterRoleBinding( + api_version="rbac.authorization.k8s.io/v1", + kind="ClusterRoleBinding", + metadata=metadata, + subjects=[subject], + role_ref=role_ref + ) + try: + rbac_v1.create_cluster_role_binding(body=cluster_role_binding) + print(f"ClusterRoleBinding '{sa}' created successfully.") + except client.exceptions.ApiException as e: + print(f"Exception when creating ClusterRoleBinding: {e}\n") + + def _apply_provider_rbac(self, sa, namespace): + rbac_v1 = client.RbacAuthorizationV1Api() + + api_version="rbac.authorization.k8s.io/v1" + kind = "ClusterRole" + roleName = sa + metadata = client.V1ObjectMeta(name=roleName) + + rule_groups = [ + { + "api_groups": ["*", ""], + "resource_groups": ["*"], + "verbs": ["get", "watch", "list"] + }, + { + "api_groups": ["workflows.kubeplus"], + "resource_groups": ["resourcecompositions", "resourcemonitors", "resourcepolicies", "resourceevents"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch"] + }, + { + "api_groups": ["rbac.authorization.k8s.io"], + "resource_groups": ["clusterroles", "clusterrolebindings", "roles", "rolebindings"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch", "deletecollection"] + }, + { + "api_groups": [""], + "resource_groups": ["pods/portforward"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch"] + }, + { + "api_groups": ["platformapi.kubeplus"], + "resource_groups": ["*"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch"] + }, + { + "api_groups": [""], + "resource_groups": ["secrets", "serviceaccounts", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts/token", "services", "services/proxy", "endpoints"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch", "deletecollection"] + }, + { + "api_groups": [""], + "resource_groups": ["namespaces"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch"] + }, + { + "api_groups": ["apps"], + "resource_groups": ["deployments", "daemonsets", "deployments/rollback", "deployments/scale", "replicasets", "replicasets/scale", "statefulsets", "statefulsets/scale"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch", "deletecollection"] + }, + { + "api_groups": [""], + "resource_groups": ["users", "groups", "serviceaccounts"], + "verbs": ["impersonate"] + }, + { + "api_groups": [""], + "resource_groups": ["pods", "pods/attach", "pods/exec", "pods/portforward", "pods/proxy", "pods/eviction", "replicationcontrollers", "replicationcontrollers/scale"], + "verbs": ["get", "list", "create", "update", "delete", "watch", "patch", "deletecollection"] + }, + { + "api_groups": ["admissionregistration.k8s.io"], + "resource_groups": ["mutatingwebhookconfigurations"], + "verbs": ["get", "create", "delete", "update"] + }, + { + "api_groups": ["apiextensions.k8s.io"], + "resource_groups": ["customresourcedefinitions"], + "verbs": ["get", "create", "delete", "update", "patch"] + }, + { + "api_groups": ["certificates.k8s.io"], + "resource_groups": ["signers"], + "resource_names": ["kubernetes.io/legacy-unknown", "kubernetes.io/kubelet-serving", "kubernetes.io/kube-apiserver-client", "cloudark.io/kubeplus"], + "verbs": ["get", "create", "delete", "update", "patch", "approve"] + }, + { + "api_groups": ["*"], + "resource_groups": ["*"], + "verbs": ["get"] + }, + { + "api_groups": ["certificates.k8s.io"], + "resource_groups": ["certificatesigningrequests", "certificatesigningrequests/approval"], + "verbs": ["create", "delete", "update", "patch"] + }, + { + "api_groups": ["extensions"], + "resource_groups": ["deployments", "daemonsets", "deployments/rollback", "deployments/scale", "replicasets", "replicasets/scale", "replicationcontrollers/scale", "ingresses", "networkpolicies"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch", "deletecollection"] + }, + { + "api_groups": ["networking.k8s.io"], + "resource_groups": ["ingresses", "networkpolicies"], + "verbs": ["get", "watch", "list", "create", "delete", "update", "patch", "deletecollection"] + }, + { + "api_groups": ["authorization.k8s.io"], + "resource_groups": ["localsubjectaccessreviews"], + "verbs": ["create"] + }, + { + "api_groups": ["autoscaling"], + "resource_groups": ["horizontalpodautoscalers"], + "verbs": ["create", "delete", "deletecollection", "patch", "update"] + }, + { + "api_groups": ["batch"], + "resource_groups": ["cronjobs", "jobs"], + "verbs": ["create", "delete", "deletecollection", "patch", "update"] + }, + { + "api_groups": ["policy"], + "resource_groups": ["poddisruptionbudgets"], + "verbs": ["create", "delete", "deletecollection", "patch", "update"] + }, + { + "api_groups": [""], + "resource_groups": ["resourcequotas"], + "verbs": ["create", "delete", "deletecollection", "patch", "update"] + } + ] + + rules = [] + all_resources = set() + + for rule_group in rule_groups: + resources = rule_group["resource_groups"] + rule = client.V1PolicyRule( + api_groups=rule_group["api_groups"], + resources=resources, + verbs=rule_group["verbs"] + ) + if "*" not in resources: + for resource in resources: + all_resources.add(resource) + + if "resource_names" in rule_group: + rule.resource_names = rule_group["resource_names"] + rules.append(rule) + + cluster_role_body = client.V1ClusterRole( + api_version=api_version, + kind=kind, + metadata=metadata, + rules=rules + ) + + try: + rbac_v1.replace_cluster_role(name=sa, body=cluster_role_body) + print(f"ClusterRole '{sa}' replaced successfully.") + except client.exceptions.ApiException as e: + print(f"Error replacing ClusterRole: {e}") + + role_binding = client.V1ClusterRoleBinding( + api_version="rbac.authorization.k8s.io/v1", + kind="ClusterRoleBinding", + metadata=client.V1ObjectMeta(name=sa), + subjects=[{ + "kind": "ServiceAccount", + "name": sa, + "apiGroup": "", + "namespace": namespace + }], + role_ref={ + "kind": "ClusterRole", + "name": sa, + "apiGroup": "rbac.authorization.k8s.io" + } + ) + try: + rbac_v1.replace_cluster_role_binding(name=sa, body=role_binding) + print(f"ClusterRole '{sa}' replaced successfully.") + except client.exceptions.ApiException as e: + print(f"Error replacing ClusterRole: {e}") + + config_map_body = client.V1ConfigMap( + metadata=client.V1ObjectMeta(name="kubeplus-saas-provider-perms"), + data={"kubeplus-saas-provider-perms.txt": "\n".join(all_resources)} + ) + + corev1.create_namespaced_config_map( + namespace=namespace, + body=config_map_body + ) + + def _update_rbac(self, permissionfile, sa, namespace, kubeconfig): + rbac_v1 = client.RbacAuthorizationV1Api() + role = {} + role["apiVersion"] = "rbac.authorization.k8s.io/v1" + role["kind"] = "ClusterRole" + metadata = {} + metadata["name"] = sa + "-update" + role["metadata"] = metadata + + ruleList = [] + ruleGroup = {} + + fp = open(permissionfile, "r") + data = fp.read() + perms_data = json.loads(data) + perms = perms_data["perms"] + new_resources_set = set() + for apiGroup, res_actions in perms.items(): + for res in res_actions: + for resource, verbs in res.items(): + print(apiGroup + " " + resource + " " + str(verbs)) + if resource not in new_resources_set: + new_resources_set.add(resource.strip()) + ruleGroup = {} + if apiGroup == "non-apigroup": + if 'nonResourceURL' in resource: + parts = resource.split("nonResourceURL::") + nonRes = parts[0].strip() + ruleGroup['nonResourceURLs'] = [nonRes] + ruleGroup['verbs'] = verbs else: - time.sleep(2) - - cmd1 = " kubectl get secret " + secretName + " -n " + namespace + " -o json " + kubeconfig - cmdToRun = cmdprefix + " " + cmd1 - out1 = subprocess.Popen(cmdToRun, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] - out1 = out1.decode('utf-8') - json_output1 = json.loads(out1) - ca_cert = json_output1["data"]["ca.crt"].strip() - #print("CA Cert:" + ca_cert) - - #cmd2 = " kubectl config view --minify -o json " - server = '' - if api_server_ip == '': - cmd2 = "kubectl -n default get endpoints kubernetes " + kubeconfig + " | awk '{print $2}' | grep -v ENDPOINTS" - cmdToRun = cmdprefix + " " + cmd2 - out2 = subprocess.Popen(cmdToRun, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] - #print("Config view Minify:") - #print(out2) - out2 = out2.decode('utf-8') - #json_output2 = json.loads(out2) - #server = json_output2["clusters"][0]["cluster"]["server"].strip() - server = out2.strip() - server = "https://" + server + ruleGroup["apiGroups"] = [apiGroup] + ruleGroup["verbs"] = verbs + if 'resourceName' in resource: + parts = resource.split("/resourceName::") + resNameParent = parts[0].strip() + resName = parts[1].strip() + ruleGroup["resources"] = [resNameParent] + ruleGroup["resourceNames"] = [resName] + else: + ruleGroup["resources"] = [resource] + + + ruleList.append(ruleGroup) + + role["rules"] = ruleList + + roleName = sa + "-update-role.yaml" + filePath = os.getcwd() + "/" + roleName + fp = open(filePath, "w") + yaml_content = yaml.dump(role) + fp.write(yaml_content) + fp.close() + cmd = " kubectl apply -f " + filePath + '--kubeconfig ' + kubeconfig + self.run_command(cmd) + + role_binding = client.V1ClusterRoleBinding( + api_version="rbac.authorization.k8s.io/v1", + kind="ClusterRoleBinding", + metadata=client.V1ObjectMeta(name=sa + "-update"), + subjects=[{ + "kind": "ServiceAccount", + "name": sa, + "apiGroup": "", + "namespace": namespace + }], + role_ref={ + "kind": "ClusterRole", + "name": sa + "-update", + "apiGroup": "rbac.authorization.k8s.io" + } + ) + + rbac_v1.create_cluster_role_binding(body=role_binding) + + # Read configmap to get earlier permissions; delete it and create it with all new permissions: + cmd = "kubectl get configmap kubeplus-saas-provider-perms -o json -n " + namespace + out1, err1 = self.run_command(cmd) + print("Original Perms Out:" + str(out1)) + print("Perms Err:" + str(err1)) + kubeplus_perms = [] + if out1 != '': + json_op = json.loads(out1) + perms = json_op['data']['kubeplus-saas-provider-perms.txt'] + print(perms) + k_perms = perms.split(",") + for p in k_perms: + p = p.replace("'","") + p = p.replace("[","") + p = p.replace("]","") + p = p.strip() + kubeplus_perms.append(p) + + new_resources_set.extend(kubeplus_perms) + + print("New perms:" + str(list(new_resources_set))) + + try: + corev1.delete_namespaced_config_map(name="kubeplus-saas-provider-perms", namespace=namespace) + print("ConfigMap deleted successfully.") + except Exception as e: + print(f"Error deleting ConfigMap: {e}") + + # create configmap to store all resources + config_map_body = client.V1ConfigMap( + metadata=client.V1ObjectMeta(name="kubeplus-saas-provider-perms"), + data={"kubeplus-saas-provider-perms.txt": "\n".join(new_resources_set)} + ) + + corev1.create_namespaced_config_map( + namespace=namespace, + body=config_map_body + ) + + + def _apply_rbac(self, sa, namespace, entity=''): + if entity == 'provider': + self._apply_provider_rbac(sa, namespace) + if entity == 'consumer': + self._apply_consumer_rbac(sa, namespace) + + def _create_secret(self, sa, namespace): + + # Define annotations + annotations = { + 'kubernetes.io/service-account.name': sa + } + + # Define metadata + metadata = client.V1ObjectMeta( + name=sa, + namespace=namespace, + annotations=annotations + ) + + # Define secret object + secret = client.V1Secret( + api_version="v1", + kind="Secret", + metadata=metadata, + type='kubernetes.io/service-account-token' + ) + + # Apply secret to the Kubernetes cluster + try: + corev1.create_namespaced_secret(namespace, secret) + # Watch for the creation of the secret + w = watch.Watch() + for event in w.stream(corev1.list_namespaced_secret, namespace=namespace, watch=False): + if event['type'] == 'ADDED' and event['object'].metadata.name == sa: + break + print(f"Secret '{sa}' created successfully.") + except ApiException as e: + return e + return None + + def _extract_kubeconfig(self, sa, namespace): + # Retrieve the secret + try: + secret = corev1.read_namespaced_secret(name=sa, namespace=namespace) + except ApiException as e: + print("Exception when calling CoreV1Api->read_namespaced_secret: %s\n" % e) + + # Extract the token from the secret + token = base64.b64decode(secret.data.get('token')).decode('utf-8') + if not token: + print(f'Token not found in the secret: {sa}.') + exit(1) + + ca_cert = secret.data.get('ca.crt') + if not ca_cert: + print(f'CA cert not found in the secret: {sa}.') + exit(1) + return token, ca_cert + + def _generate_kubeconfig(self, sa, namespace, apiserver_ip): + body = client.V1ServiceAccount(metadata={"name": sa}) + + try: + # Create ServiceAccount in the namespace + corev1.create_namespaced_service_account(namespace, body) + print(f"ServiceAccount '{sa}' created successfully.") + except ApiException as e: + print(f"Error: Failed to create ServiceAccount '{sa}': {e.reason}") + + err = self._create_secret(sa, namespace) + if err is None: + token, ca_cert = self._extract_kubeconfig(sa, namespace) else: - if "https" not in api_server_ip: - server = "https://" + api_server_ip - else: - server = api_server_ip - #print("Kube API Server:" + server) - self._create_kubecfg_file(sa, namespace, filename, token, ca_cert, server, kubeconfig) - + print(f"Error: Failed to create Secret '{sa}': {err.reason}") + exit(1) + + self._create_kubecfg_file(sa, namespace, filename, token, apiserver_ip) + + def delete_sa(self, sa_name, namespace): + try: + corev1.delete_namespaced_service_account(name=sa_name, namespace=namespace) + print(f"ServiceAccount {sa_name} deleted successfully.") + except ApiException as e: + print(f"Error deleting ServiceAccount {sa_name}: {e}") + + def delete_config_map(self, cm_name, namespace): + try: + corev1.delete_namespaced_config_map(name=cm_name, namespace=namespace) + print(f"ConfigMap {cm_name} deleted successfully.") + except ApiException as e: + print(f"Error deleting ConfigMap {cm_name}: {e}") + + def delete_cluster_role(self, role_name): + try: + rbacv1.delete_cluster_role(name=role_name) + print(f"ClusterRole {role_name} deleted successfully.") + except ApiException as e: + print(f"Error deleting ClusterRole {role_name}: {e}") + + def delete_cluster_role_binding(self, binding_name): + try: + rbacv1.delete_cluster_role_binding(name=binding_name) + print(f"ClusterRoleBinding {binding_name} deleted successfully.") + except ApiException as e: + print(f"Error deleting ClusterRoleBinding {binding_name}: {e}") + +def check_and_create_namespace(namespace): + try: + # Check if the namespace exists + corev1.read_namespace(namespace) + print(f'Namespace: {namespace} already exisit') + except ApiException as e: + if e.status == 404: + # Namespace doesn't exist, create it + body = client.V1Namespace(metadata=client.V1ObjectMeta(name=namespace)) + try: + corev1.create_namespace(body) + print(f"Namespace '{namespace}' created successfully.") + except ApiException as ex: + print(f"Error: Failed to create namespace '{namespace}': {ex.reason}") + else: + print(f"Error: Failed to check namespace '{namespace}': {e.reason}") + +def label_namespace(namespace): + try: + # Get current labels of the namespace + namespace_obj = corev1.read_namespace(namespace) + + # Add/overwrite the 'managedby=kubeplus' label + if not namespace_obj.metadata.labels: + namespace_obj.metadata.labels = {} + namespace_obj.metadata.labels['managedby'] = 'kubeplus' + + # Update the namespace with the new label + corev1.patch_namespace(namespace, namespace_obj) + print(f"Namespace '{namespace}' labeled successfully.") + except ApiException as e: + print(f"Error: Failed to label namespace '{namespace}': {e.reason}") + + +class ValidatePermissionFile(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + if values == 'update' and not getattr(namespace, 'permissionfile', None): + parser.error(f"Permission file is required when using 'update' action: --permission-file\ + {permission_help}") + if values != 'update' and getattr(namespace, 'permissionfile', None): + parser.error(f"Permissions file should only be used with 'update' command.") + if values == 'delete' and not getattr(namespace, 'kubeconfig', None): + parser.error(f"Please provide the kubeconfig file path with 'update' argument.") + +def ensure_https_scheme(apiserver_ip): + # Ensures the given URL has an 'https' scheme. If the URL does not have a scheme, 'https' is added as the scheme. + parsed_url = urlparse(apiserver_ip) + if not parsed_url.scheme: + parsed_url = parsed_url._replace(scheme="https") + return urlunparse(parsed_url) + +def delete_file(file_path): + """Safely remove a file if it exists.""" + try: + if os.path.exists(file_path): + os.remove(file_path) + print(f"Removed {file_path}") + else: + print(f"{file_path} does not exist.") + except Exception as e: + print(f"Error removing {file_path}: {e}") - def _generate_kubeconfig(self, sa, namespace, filename, api_server_ip='', kubeconfig=''): - cmdprefix = "" - cmd = " kubectl create sa " + sa + " -n " + namespace + kubeconfig - cmdToRun = cmdprefix + " " + cmd - self.run_command(cmdToRun) - #cmd = " kubectl get sa " + sa + " -n " + namespace + " -o json " - #cmdToRun = cmdprefix + " " + cmd - #out = subprocess.Popen(cmdToRun, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] +if __name__ == '__main__': - secretName = sa - out = self._create_secret(secretName, namespace, kubeconfig) - #print("Create secret:" + out) - if 'secret/' + sa + ' created' in out: - #json_output = json.loads(out) - #secretName = json_output["secrets"][0]["name"] - #print("Secret Name:" + secretName) + parser = argparse.ArgumentParser() - # Moving from here - #print("Got secret token") - self._extract_kubeconfig(sa, namespace, filename, serverip=api_server_ip, kubecfg=kubeconfig) + parser.add_argument("action", help="command", choices=['create', 'delete', 'update', 'extract'], action=ValidatePermissionFile) + + parser.add_argument("-n", "--namespace", required=True, help="namespace in which KubePlus will be installed.") + + parser.add_argument("-k", "--kubeconfig", default=DEFAULT_KUBECONFIG_PATH, help='This flag is used to specify the path\ + of the kubeconfig file that should be used for executing steps in provider-kubeconfig.\ + (default: %(default)s)') + + parser.add_argument("-s", "--apiserver-url", dest="apiserverurl", default='', help='This flag is to be used to pass the API Server URL of the\ + API server on which KubePlus is installed. This API Server URL will be used in constructing the server endpoint in\ + the provider kubeconfig. Use the command `kubectl config view --minify -o jsonpath="{.clusters[0].cluster.server}"`\ + to retrieve the API Server URL.') + + parser.add_argument("-f", "--filename", default=PROVIDER_KUBECONFIG, help='This flag is used to specify the output file name\ + in which generated provider kubeconfig will be store\ + (default: %(default)s)') + + permission_help = ''' +permissions file (Only for 'update' command). Should be a JSON file with the following structure: +{ +"perms": { + "": [ + { + "resource1|resource/resourceName::": [ + "verb1", + "verb2", + "..." + ] + }, + { + "resource2": [ + "..." + ] + } + ], + "": [ + { + "resource3": [ + "..." + ] + } + ] +} +} + +''' + parser.add_argument("-p", "--permission-file", dest="permissionfile", default='', type=str, help=permission_help) + + args = parser.parse_args() + action = args.action + namespace = args.namespace + kubeconfigString = args.kubeconfig + apiserver_ip = args.apiserverurl + permission_file = args.permissionfile + filename = args.filename + config.load_kube_config(config_file=kubeconfigString) + corev1 = client.CoreV1Api() + rbacv1 = client.RbacAuthorizationV1Api() -if __name__ == '__main__': - kubeconfigPath = os.getenv("HOME") + "/.kube/config" - parser = argparse.ArgumentParser() - parser.add_argument("action", help="command", choices=['create', 'delete', 'update', 'extract']) - parser.add_argument("namespace", help="namespace in which KubePlus will be installed.") - parser.add_argument("-k", "--kubeconfig", help='''This flag is used to specify the path - of the kubeconfig file that should be used for executing steps in provider-kubeconfig. - The default value is ~/.kube/config''') - parser.add_argument("-s", "--apiserverurl", help='''This flag is to be used to pass the API Server URL of the - API server on which KubePlus is installed. This API Server URL will be used in constructing the - server endpoint in the provider kubeconfig. Use the command - `kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'` - to retrieve the API Server URL.''') - parser.add_argument("-f", "--filename", help='''This flag is used to specify the - output file name in which generated provider kubeconfig will be store - (The default value is kubeplus-saas-provider.json)''') - permission_help = "permissions file - use with update command.\n" - permission_help = permission_help + "Should be a JSON file with the following structure:\n" - permission_help = permission_help + "{perms:{:[{resource1|resource/resourceName::: [verb1, verb2, ...]}, {resource2: [..]}], {:[...]}}}" - parser.add_argument("-p", "--permissionfile", help=permission_help) - args = parser.parse_args() - #print(args.action) - #print(args.namespace) - - action = args.action - namespace = args.namespace - - if args.kubeconfig: - #print("Kubeconfig file:" + args.kubeconfig) - kubeconfigPath = args.kubeconfig - - kubeconfigString = " --kubeconfig=" + kubeconfigPath - - api_s_ip = '' - if args.apiserverurl: - #print("Server ip:" + args.serverip) - api_s_ip = args.apiserverurl - - permission_file = '' - if args.permissionfile: - #print("Permission file:" + args.permissionfile) - permission_file = args.permissionfile - - if action == 'update' and permission_file == '': - print("Permission file missing. Please provide permission file.") - print(permission_help) - exit(0) - - kubeconfigGenerator = KubeconfigGenerator() - sa = 'kubeplus-saas-provider' - - filename = sa - if args.filename: - filename = args.filename - if not filename.endswith(".json"): - filename += ".json" - - if action == "create": - if permission_file: - print("Permissions file should be used with update command.") - exit(1) - - create_ns = "kubectl get ns " + namespace + kubeconfigString - out, err = run_command(create_ns) - if 'not found' in out or 'not found' in err: - run_command(create_ns) - - cmd = "kubectl label --overwrite=true ns " + namespace + " managedby=kubeplus " + kubeconfigString - run_command(cmd) - - # 1. Generate Provider kubeconfig - kubeconfigGenerator._generate_kubeconfig(sa, namespace, filename, api_server_ip=api_s_ip, kubeconfig=kubeconfigString) - kubeconfigGenerator._apply_rbac(sa, namespace, entity='provider', kubeconfig=kubeconfigString) - print("Provider kubeconfig created: " + filename) - - if action == "extract": - kubeconfigGenerator._extract_kubeconfig(sa, namespace, filename, serverip=api_s_ip, kubecfg=kubeconfigString) - print("Provider kubeconfig created: " + filename) - - if action == "update": - kubeconfigGenerator._update_rbac(permission_file, sa, namespace, kubeconfigString) - print("Provider kubeconfig permissions updated: " + filename) - - - if action == "delete": - run_command("kubectl delete sa " + sa + " -n " + namespace + kubeconfigString) - run_command("kubectl delete configmap " + sa + " -n " + namespace + kubeconfigString) - run_command("kubectl delete clusterrole " + sa + " -n " + namespace + kubeconfigString) - run_command("kubectl delete clusterrolebinding " + sa + " -n " + namespace + kubeconfigString) - run_command("kubectl delete clusterrole " + sa + "-update" + " -n " + namespace + kubeconfigString) - run_command("kubectl delete clusterrolebinding " + sa + "-update" + " -n " + namespace + kubeconfigString) - run_command("kubectl delete configmap kubeplus-saas-provider-perms -n " + namespace) - cwd = os.getcwd() - run_command("rm " + cwd + "/kubeplus-saas-provider-secret.yaml") - run_command("rm " + cwd + "/" + filename) - run_command("rm " + cwd + "/kubeplus-saas-provider-role.yaml") - run_command("rm " + cwd + "/kubeplus-saas-provider-update-role.yaml") - run_command("rm " + cwd + "/kubeplus-saas-provider-rolebinding.yaml") - run_command("rm " + cwd + "/kubeplus-saas-provider-update-rolebinding.yaml") - run_command("rm " + cwd + "/kubeplus-saas-provider-perms.txt") - run_command("rm " + cwd + "/kubeplus-saas-provider-perms-update.txt") + if not apiserver_ip: + # Get the API server IP + configuration = client.Configuration().get_default_copy() + apiserver_ip = configuration.host + + apiserver_ip = ensure_https_scheme(apiserver_ip) + print(f'API Server IP: {apiserver_ip}') + + kubeconfigGenerator = KubeconfigGenerator() + sa = 'kubeplus-saas-provider' + + if not filename.endswith(".json"): + filename += ".json" + + match action: + case "create": + check_and_create_namespace(namespace) + label_namespace(namespace) + + # 1. Generate Provider kubeconfig + kubeconfigGenerator._generate_kubeconfig(sa, namespace, apiserver_ip) + kubeconfigGenerator._apply_rbac(sa, namespace, entity='provider') + print("Provider kubeconfig created: " + filename) + + case "extract": + kubeconfigGenerator._extract_kubeconfig(sa, namespace, filename, serverip=apiserver_ip) + print("Provider kubeconfig created: " + filename) + + case "update": + kubeconfigGenerator._update_rbac(permission_file, sa, namespace) + print("Provider kubeconfig permissions updated: " + filename) + + case "delete": + kubeconfigGenerator.delete_sa(sa, namespace) + + # Delete ConfigMap + kubeconfigGenerator.delete_config_map(sa, namespace) + + # Delete ClusterRole + kubeconfigGenerator.delete_cluster_role(sa) + + # Delete ClusterRoleBinding + kubeconfigGenerator.delete_cluster_role_binding(sa) + + # Delete Update ClusterRole + kubeconfigGenerator.delete_cluster_role(sa + "-update") + + # Delete Update ClusterRoleBinding + kubeconfigGenerator.delete_cluster_role_binding(sa + "-update") + + # Delete kubeplus-saas-provider-perms ConfigMap + kubeconfigGenerator.delete_config_map("kubeplus-saas-provider-perms", namespace) + + cwd = os.getcwd() + files_to_delete = [ + "kubeplus-saas-provider-secret.yaml", + filename, + "kubeplus-saas-provider-role.yaml", + "kubeplus-saas-provider-update-role.yaml", + "kubeplus-saas-provider-rolebinding.yaml", + "kubeplus-saas-provider-update-rolebinding.yaml", + "kubeplus-saas-provider-perms.txt", + "kubeplus-saas-provider-perms-update.txt" + ] + + for file_name in files_to_delete: + delete_file(cwd + "/" + file_name) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c3726e8b..cff29c58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -pyyaml +kubernetes==12.0.1 +PyYAML==6.0.1