From 72ae8c071949b4b45b529a90386001a3b4b955d0 Mon Sep 17 00:00:00 2001 From: danielsagi Date: Fri, 4 Sep 2020 14:01:16 +0300 Subject: [PATCH] reformatted files to pass new linting (#369) Co-authored-by: Daniel Sagi --- kube_hunter/conf/__init__.py | 2 +- kube_hunter/conf/parser.py | 23 +++- kube_hunter/core/events/types.py | 9 +- kube_hunter/modules/discovery/hosts.py | 10 +- kube_hunter/modules/hunting/aks.py | 6 +- kube_hunter/modules/hunting/apiserver.py | 109 +++++++++++----- kube_hunter/modules/hunting/arp.py | 10 +- kube_hunter/modules/hunting/capabilities.py | 5 +- kube_hunter/modules/hunting/certificates.py | 6 +- kube_hunter/modules/hunting/cves.py | 26 +++- kube_hunter/modules/hunting/dashboard.py | 6 +- kube_hunter/modules/hunting/dns.py | 10 +- kube_hunter/modules/hunting/etcd.py | 16 ++- kube_hunter/modules/hunting/kubelet.py | 131 ++++++++++++++++---- kube_hunter/modules/hunting/mounts.py | 29 ++++- kube_hunter/modules/hunting/proxy.py | 18 ++- kube_hunter/modules/hunting/secrets.py | 5 +- kube_hunter/modules/report/dispatchers.py | 5 +- tests/core/test_cloud.py | 2 +- tests/discovery/test_apiserver.py | 12 +- tests/hunting/test_apiserver_hunter.py | 21 ++-- tests/hunting/test_kubelet.py | 4 +- 22 files changed, 360 insertions(+), 105 deletions(-) diff --git a/kube_hunter/conf/__init__.py b/kube_hunter/conf/__init__.py index f1dcba7..96d8079 100644 --- a/kube_hunter/conf/__init__.py +++ b/kube_hunter/conf/__init__.py @@ -4,7 +4,7 @@ from typing import Any, Optional @dataclass class Config: - """ Config is a configuration container. + """Config is a configuration container. It contains the following fields: - active: Enable active hunters - cidr: Network subnets to scan diff --git a/kube_hunter/conf/parser.py b/kube_hunter/conf/parser.py index c6bf24f..f955bbd 100644 --- a/kube_hunter/conf/parser.py +++ b/kube_hunter/conf/parser.py @@ -9,7 +9,9 @@ def parser_add_arguments(parser): Contains initialization for all default arguments """ parser.add_argument( - "--list", action="store_true", help="Displays all tests in kubehunter (add --active flag to see active tests)", + "--list", + action="store_true", + help="Displays all tests in kubehunter (add --active flag to see active tests)", ) parser.add_argument("--interface", action="store_true", help="Set hunting on all network interfaces") @@ -19,7 +21,9 @@ def parser_add_arguments(parser): parser.add_argument("--quick", action="store_true", help="Prefer quick scan (subnet 24)") parser.add_argument( - "--include-patched-versions", action="store_true", help="Don't skip patched versions when scanning", + "--include-patched-versions", + action="store_true", + help="Don't skip patched versions when scanning", ) parser.add_argument( @@ -29,11 +33,17 @@ def parser_add_arguments(parser): ) parser.add_argument( - "--mapping", action="store_true", help="Outputs only a mapping of the cluster's nodes", + "--mapping", + action="store_true", + help="Outputs only a mapping of the cluster's nodes", ) parser.add_argument( - "--remote", nargs="+", metavar="HOST", default=list(), help="One or more remote ip/dns to hunt", + "--remote", + nargs="+", + metavar="HOST", + default=list(), + help="One or more remote ip/dns to hunt", ) parser.add_argument("--active", action="store_true", help="Enables active hunting") @@ -47,7 +57,10 @@ def parser_add_arguments(parser): ) parser.add_argument( - "--report", type=str, default="plain", help="Set report type, options are: plain, yaml, json", + "--report", + type=str, + default="plain", + help="Set report type, options are: plain, yaml, json", ) parser.add_argument( diff --git a/kube_hunter/core/events/types.py b/kube_hunter/core/events/types.py index 28acb23..ef9afc0 100644 --- a/kube_hunter/core/events/types.py +++ b/kube_hunter/core/events/types.py @@ -144,7 +144,8 @@ class NewHostEvent(Event): logger.debug("Checking whether the cluster is deployed on azure's cloud") # Leverage 3rd tool https://github.com/blrchen/AzureSpeed for Azure cloud ip detection result = requests.get( - f"https://api.azurespeed.com/api/region?ipOrUrl={self.host}", timeout=config.network_timeout, + f"https://api.azurespeed.com/api/region?ipOrUrl={self.host}", + timeout=config.network_timeout, ).json() return result["cloud"] or "NoCloud" except requests.ConnectionError: @@ -194,7 +195,11 @@ class K8sVersionDisclosure(Vulnerability, Event): def __init__(self, version, from_endpoint, extra_info=""): Vulnerability.__init__( - self, KubernetesCluster, "K8s Version Disclosure", category=InformationDisclosure, vid="KHV002", + self, + KubernetesCluster, + "K8s Version Disclosure", + category=InformationDisclosure, + vid="KHV002", ) self.version = version self.from_endpoint = from_endpoint diff --git a/kube_hunter/modules/discovery/hosts.py b/kube_hunter/modules/discovery/hosts.py index 40e29af..ddf86bb 100644 --- a/kube_hunter/modules/discovery/hosts.py +++ b/kube_hunter/modules/discovery/hosts.py @@ -46,7 +46,11 @@ class AzureMetadataApi(Vulnerability, Event): def __init__(self, cidr): Vulnerability.__init__( - self, Azure, "Azure Metadata Exposure", category=InformationDisclosure, vid="KHV003", + self, + Azure, + "Azure Metadata Exposure", + category=InformationDisclosure, + vid="KHV003", ) self.cidr = cidr self.evidence = "cidr: {}".format(cidr) @@ -140,7 +144,9 @@ class FromPodHostDiscovery(Discovery): def traceroute_discovery(self): config = get_config() node_internal_ip = srp1( - Ether() / IP(dst="1.1.1.1", ttl=1) / ICMP(), verbose=0, timeout=config.network_timeout, + Ether() / IP(dst="1.1.1.1", ttl=1) / ICMP(), + verbose=0, + timeout=config.network_timeout, )[IP].src return [[node_internal_ip, "24"]] diff --git a/kube_hunter/modules/hunting/aks.py b/kube_hunter/modules/hunting/aks.py index 0e386ab..a5877c3 100644 --- a/kube_hunter/modules/hunting/aks.py +++ b/kube_hunter/modules/hunting/aks.py @@ -16,7 +16,11 @@ class AzureSpnExposure(Vulnerability, Event): def __init__(self, container): Vulnerability.__init__( - self, Azure, "Azure SPN Exposure", category=IdentityTheft, vid="KHV004", + self, + Azure, + "Azure SPN Exposure", + category=IdentityTheft, + vid="KHV004", ) self.container = container diff --git a/kube_hunter/modules/hunting/apiserver.py b/kube_hunter/modules/hunting/apiserver.py index 92a7f83..f8f78ba 100644 --- a/kube_hunter/modules/hunting/apiserver.py +++ b/kube_hunter/modules/hunting/apiserver.py @@ -29,7 +29,11 @@ class ServerApiAccess(Vulnerability, Event): name = "Unauthenticated access to API" category = UnauthenticatedAccess Vulnerability.__init__( - self, KubernetesCluster, name=name, category=category, vid="KHV005", + self, + KubernetesCluster, + name=name, + category=category, + vid="KHV005", ) self.evidence = evidence @@ -42,7 +46,11 @@ class ServerApiHTTPAccess(Vulnerability, Event): name = "Insecure (HTTP) access to API" category = UnauthenticatedAccess Vulnerability.__init__( - self, KubernetesCluster, name=name, category=category, vid="KHV006", + self, + KubernetesCluster, + name=name, + category=category, + vid="KHV006", ) self.evidence = evidence @@ -54,7 +62,11 @@ class ApiInfoDisclosure(Vulnerability, Event): else: name += " as anonymous user" Vulnerability.__init__( - self, KubernetesCluster, name=name, category=InformationDisclosure, vid="KHV007", + self, + KubernetesCluster, + name=name, + category=InformationDisclosure, + vid="KHV007", ) self.evidence = evidence @@ -89,12 +101,14 @@ class ListClusterRoles(ApiInfoDisclosure): class CreateANamespace(Vulnerability, Event): - """ Creating a namespace might give an attacker an area with default (exploitable) permissions to run pods in. - """ + """Creating a namespace might give an attacker an area with default (exploitable) permissions to run pods in.""" def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Created a namespace", category=AccessRisk, + self, + KubernetesCluster, + name="Created a namespace", + category=AccessRisk, ) self.evidence = evidence @@ -105,14 +119,17 @@ class DeleteANamespace(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Delete a namespace", category=AccessRisk, + self, + KubernetesCluster, + name="Delete a namespace", + category=AccessRisk, ) self.evidence = evidence class CreateARole(Vulnerability, Event): - """ Creating a role might give an attacker the option to harm the normal behavior of newly created pods - within the specified namespaces. + """Creating a role might give an attacker the option to harm the normal behavior of newly created pods + within the specified namespaces. """ def __init__(self, evidence): @@ -121,37 +138,46 @@ class CreateARole(Vulnerability, Event): class CreateAClusterRole(Vulnerability, Event): - """ Creating a cluster role might give an attacker the option to harm the normal behavior of newly created pods - across the whole cluster + """Creating a cluster role might give an attacker the option to harm the normal behavior of newly created pods + across the whole cluster """ def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Created a cluster role", category=AccessRisk, + self, + KubernetesCluster, + name="Created a cluster role", + category=AccessRisk, ) self.evidence = evidence class PatchARole(Vulnerability, Event): - """ Patching a role might give an attacker the option to create new pods with custom roles within the + """Patching a role might give an attacker the option to create new pods with custom roles within the specific role's namespace scope """ def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Patched a role", category=AccessRisk, + self, + KubernetesCluster, + name="Patched a role", + category=AccessRisk, ) self.evidence = evidence class PatchAClusterRole(Vulnerability, Event): - """ Patching a cluster role might give an attacker the option to create new pods with custom roles within the whole + """Patching a cluster role might give an attacker the option to create new pods with custom roles within the whole cluster scope. """ def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Patched a cluster role", category=AccessRisk, + self, + KubernetesCluster, + name="Patched a cluster role", + category=AccessRisk, ) self.evidence = evidence @@ -161,7 +187,10 @@ class DeleteARole(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Deleted a role", category=AccessRisk, + self, + KubernetesCluster, + name="Deleted a role", + category=AccessRisk, ) self.evidence = evidence @@ -171,7 +200,10 @@ class DeleteAClusterRole(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Deleted a cluster role", category=AccessRisk, + self, + KubernetesCluster, + name="Deleted a cluster role", + category=AccessRisk, ) self.evidence = evidence @@ -181,7 +213,10 @@ class CreateAPod(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Created A Pod", category=AccessRisk, + self, + KubernetesCluster, + name="Created A Pod", + category=AccessRisk, ) self.evidence = evidence @@ -191,7 +226,10 @@ class CreateAPrivilegedPod(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Created A PRIVILEGED Pod", category=AccessRisk, + self, + KubernetesCluster, + name="Created A PRIVILEGED Pod", + category=AccessRisk, ) self.evidence = evidence @@ -201,7 +239,10 @@ class PatchAPod(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Patched A Pod", category=AccessRisk, + self, + KubernetesCluster, + name="Patched A Pod", + category=AccessRisk, ) self.evidence = evidence @@ -211,7 +252,10 @@ class DeleteAPod(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Deleted A Pod", category=AccessRisk, + self, + KubernetesCluster, + name="Deleted A Pod", + category=AccessRisk, ) self.evidence = evidence @@ -225,7 +269,7 @@ class ApiServerPassiveHunterFinished(Event): # If we have a service account token we'll also trigger AccessApiServerWithToken below @handler.subscribe(ApiServer) class AccessApiServer(Hunter): - """ API Server Hunter + """API Server Hunter Checks if API server is accessible """ @@ -268,7 +312,10 @@ class AccessApiServer(Hunter): try: if not namespace: r = requests.get( - f"{self.path}/api/v1/pods", headers=self.headers, verify=False, timeout=config.network_timeout, + f"{self.path}/api/v1/pods", + headers=self.headers, + verify=False, + timeout=config.network_timeout, ) else: r = requests.get( @@ -319,7 +366,7 @@ class AccessApiServer(Hunter): @handler.subscribe(ApiServer, predicate=lambda x: x.auth_token) class AccessApiServerWithToken(AccessApiServer): - """ API Server Hunter + """API Server Hunter Accessing the API server using the service account token obtained from a compromised pod """ @@ -411,7 +458,8 @@ class AccessApiServerActive(ActiveHunter): def patch_a_pod(self, namespace, pod_name): data = [{"op": "add", "path": "/hello", "value": ["world"]}] return self.patch_item( - path=f"{self.path}/api/v1/namespaces/{namespace}/pods/{pod_name}", data=json.dumps(data), + path=f"{self.path}/api/v1/namespaces/{namespace}/pods/{pod_name}", + data=json.dumps(data), ) def create_namespace(self): @@ -438,7 +486,8 @@ class AccessApiServerActive(ActiveHunter): "rules": [{"apiGroups": [""], "resources": ["pods"], "verbs": ["get", "watch", "list"]}], } return self.create_item( - path=f"{self.path}/apis/rbac.authorization.k8s.io/v1/namespaces/{namespace}/roles", data=json.dumps(role), + path=f"{self.path}/apis/rbac.authorization.k8s.io/v1/namespaces/{namespace}/roles", + data=json.dumps(role), ) def create_a_cluster_role(self): @@ -450,7 +499,8 @@ class AccessApiServerActive(ActiveHunter): "rules": [{"apiGroups": [""], "resources": ["pods"], "verbs": ["get", "watch", "list"]}], } return self.create_item( - path=f"{self.path}/apis/rbac.authorization.k8s.io/v1/clusterroles", data=json.dumps(cluster_role), + path=f"{self.path}/apis/rbac.authorization.k8s.io/v1/clusterroles", + data=json.dumps(cluster_role), ) def delete_a_role(self, namespace, name): @@ -477,7 +527,8 @@ class AccessApiServerActive(ActiveHunter): def patch_a_cluster_role(self, cluster_role): data = [{"op": "add", "path": "/hello", "value": ["world"]}] return self.patch_item( - path=f"{self.path}/apis/rbac.authorization.k8s.io/v1/clusterroles/{cluster_role}", data=json.dumps(data), + path=f"{self.path}/apis/rbac.authorization.k8s.io/v1/clusterroles/{cluster_role}", + data=json.dumps(data), ) def execute(self): diff --git a/kube_hunter/modules/hunting/arp.py b/kube_hunter/modules/hunting/arp.py index a799438..b8c1ea3 100644 --- a/kube_hunter/modules/hunting/arp.py +++ b/kube_hunter/modules/hunting/arp.py @@ -17,7 +17,11 @@ class PossibleArpSpoofing(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, KubernetesCluster, "Possible Arp Spoof", category=IdentityTheft, vid="KHV020", + self, + KubernetesCluster, + "Possible Arp Spoof", + category=IdentityTheft, + vid="KHV020", ) @@ -55,7 +59,9 @@ class ArpSpoofHunter(ActiveHunter): config = get_config() self_ip = sr1(IP(dst="1.1.1.1", ttl=1) / ICMP(), verbose=0, timeout=config.network_timeout)[IP].dst arp_responses, _ = srp( - Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=f"{self_ip}/24"), timeout=config.network_timeout, verbose=0, + Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=f"{self_ip}/24"), + timeout=config.network_timeout, + verbose=0, ) # arp enabled on cluster and more than one pod on node diff --git a/kube_hunter/modules/hunting/capabilities.py b/kube_hunter/modules/hunting/capabilities.py index 17d0496..802ae93 100644 --- a/kube_hunter/modules/hunting/capabilities.py +++ b/kube_hunter/modules/hunting/capabilities.py @@ -17,7 +17,10 @@ class CapNetRawEnabled(Event, Vulnerability): def __init__(self): Vulnerability.__init__( - self, KubernetesCluster, name="CAP_NET_RAW Enabled", category=AccessRisk, + self, + KubernetesCluster, + name="CAP_NET_RAW Enabled", + category=AccessRisk, ) diff --git a/kube_hunter/modules/hunting/certificates.py b/kube_hunter/modules/hunting/certificates.py index d6329b8..bfbffe4 100644 --- a/kube_hunter/modules/hunting/certificates.py +++ b/kube_hunter/modules/hunting/certificates.py @@ -16,7 +16,11 @@ class CertificateEmail(Vulnerability, Event): def __init__(self, email): Vulnerability.__init__( - self, KubernetesCluster, "Certificate Includes Email Address", category=InformationDisclosure, vid="KHV021", + self, + KubernetesCluster, + "Certificate Includes Email Address", + category=InformationDisclosure, + vid="KHV021", ) self.email = email self.evidence = "email: {}".format(self.email) diff --git a/kube_hunter/modules/hunting/cves.py b/kube_hunter/modules/hunting/cves.py index 4c5facc..697bab1 100644 --- a/kube_hunter/modules/hunting/cves.py +++ b/kube_hunter/modules/hunting/cves.py @@ -33,7 +33,7 @@ class ServerApiVersionEndPointAccessPE(Vulnerability, Event): class ServerApiVersionEndPointAccessDos(Vulnerability, Event): """Node not patched for CVE-2019-1002100. Depending on your RBAC settings, - a crafted json-patch could cause a Denial of Service.""" + a crafted json-patch could cause a Denial of Service.""" def __init__(self, evidence): Vulnerability.__init__( @@ -52,7 +52,11 @@ class PingFloodHttp2Implementation(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Possible Ping Flood Attack", category=DenialOfService, vid="KHV024", + self, + KubernetesCluster, + name="Possible Ping Flood Attack", + category=DenialOfService, + vid="KHV024", ) self.evidence = evidence @@ -63,7 +67,11 @@ class ResetFloodHttp2Implementation(Vulnerability, Event): def __init__(self, evidence): Vulnerability.__init__( - self, KubernetesCluster, name="Possible Reset Flood Attack", category=DenialOfService, vid="KHV025", + self, + KubernetesCluster, + name="Possible Reset Flood Attack", + category=DenialOfService, + vid="KHV025", ) self.evidence = evidence @@ -89,7 +97,11 @@ class IncompleteFixToKubectlCpVulnerability(Vulnerability, Event): def __init__(self, binary_version): Vulnerability.__init__( - self, KubectlClient, "Kubectl Vulnerable To CVE-2019-11246", category=RemoteCodeExec, vid="KHV027", + self, + KubectlClient, + "Kubectl Vulnerable To CVE-2019-11246", + category=RemoteCodeExec, + vid="KHV027", ) self.binary_version = binary_version self.evidence = "kubectl version: {}".format(self.binary_version) @@ -101,7 +113,11 @@ class KubectlCpVulnerability(Vulnerability, Event): def __init__(self, binary_version): Vulnerability.__init__( - self, KubectlClient, "Kubectl Vulnerable To CVE-2019-1002101", category=RemoteCodeExec, vid="KHV028", + self, + KubectlClient, + "Kubectl Vulnerable To CVE-2019-1002101", + category=RemoteCodeExec, + vid="KHV028", ) self.binary_version = binary_version self.evidence = "kubectl version: {}".format(self.binary_version) diff --git a/kube_hunter/modules/hunting/dashboard.py b/kube_hunter/modules/hunting/dashboard.py index 609131b..bb31b24 100644 --- a/kube_hunter/modules/hunting/dashboard.py +++ b/kube_hunter/modules/hunting/dashboard.py @@ -16,7 +16,11 @@ class DashboardExposed(Vulnerability, Event): def __init__(self, nodes): Vulnerability.__init__( - self, KubernetesCluster, "Dashboard Exposed", category=RemoteCodeExec, vid="KHV029", + self, + KubernetesCluster, + "Dashboard Exposed", + category=RemoteCodeExec, + vid="KHV029", ) self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None diff --git a/kube_hunter/modules/hunting/dns.py b/kube_hunter/modules/hunting/dns.py index 7503de2..3705bd9 100644 --- a/kube_hunter/modules/hunting/dns.py +++ b/kube_hunter/modules/hunting/dns.py @@ -18,7 +18,11 @@ class PossibleDnsSpoofing(Vulnerability, Event): def __init__(self, kubedns_pod_ip): Vulnerability.__init__( - self, KubernetesCluster, "Possible DNS Spoof", category=IdentityTheft, vid="KHV030", + self, + KubernetesCluster, + "Possible DNS Spoof", + category=IdentityTheft, + vid="KHV030", ) self.kubedns_pod_ip = kubedns_pod_ip self.evidence = "kube-dns at: {}".format(self.kubedns_pod_ip) @@ -61,7 +65,9 @@ class DnsSpoofHunter(ActiveHunter): self_ip = dns_info_res[IP].dst arp_responses, _ = srp( - Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=f"{self_ip}/24"), timeout=config.network_timeout, verbose=0, + Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=f"{self_ip}/24"), + timeout=config.network_timeout, + verbose=0, ) for _, response in arp_responses: if response[Ether].src == kubedns_pod_mac: diff --git a/kube_hunter/modules/hunting/etcd.py b/kube_hunter/modules/hunting/etcd.py index 15b3af3..6155fa2 100644 --- a/kube_hunter/modules/hunting/etcd.py +++ b/kube_hunter/modules/hunting/etcd.py @@ -26,7 +26,11 @@ class EtcdRemoteWriteAccessEvent(Vulnerability, Event): def __init__(self, write_res): Vulnerability.__init__( - self, KubernetesCluster, name="Etcd Remote Write Access Event", category=RemoteCodeExec, vid="KHV031", + self, + KubernetesCluster, + name="Etcd Remote Write Access Event", + category=RemoteCodeExec, + vid="KHV031", ) self.evidence = write_res @@ -36,7 +40,11 @@ class EtcdRemoteReadAccessEvent(Vulnerability, Event): def __init__(self, keys): Vulnerability.__init__( - self, KubernetesCluster, name="Etcd Remote Read Access Event", category=AccessRisk, vid="KHV032", + self, + KubernetesCluster, + name="Etcd Remote Read Access Event", + category=AccessRisk, + vid="KHV032", ) self.evidence = keys @@ -149,7 +157,9 @@ class EtcdRemoteAccess(Hunter): logger.debug(f"Trying to access etcd insecurely at {self.event.host}") try: r = requests.get( - f"http://{self.event.host}:{ETCD_PORT}/version", verify=False, timeout=config.network_timeout, + f"http://{self.event.host}:{ETCD_PORT}/version", + verify=False, + timeout=config.network_timeout, ) return r.content if r.status_code == 200 and r.content else False except requests.exceptions.ConnectionError: diff --git a/kube_hunter/modules/hunting/kubelet.py b/kube_hunter/modules/hunting/kubelet.py index cd02192..eb750a8 100644 --- a/kube_hunter/modules/hunting/kubelet.py +++ b/kube_hunter/modules/hunting/kubelet.py @@ -35,7 +35,10 @@ class ExposedPodsHandler(Vulnerability, Event): def __init__(self, pods): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Pods", category=InformationDisclosure, + self, + component=Kubelet, + name="Exposed Pods", + category=InformationDisclosure, ) self.pods = pods self.evidence = f"count: {len(self.pods)}" @@ -47,7 +50,11 @@ class AnonymousAuthEnabled(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Anonymous Authentication", category=RemoteCodeExec, vid="KHV036", + self, + component=Kubelet, + name="Anonymous Authentication", + category=RemoteCodeExec, + vid="KHV036", ) @@ -56,7 +63,11 @@ class ExposedContainerLogsHandler(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Container Logs", category=InformationDisclosure, vid="KHV037", + self, + component=Kubelet, + name="Exposed Container Logs", + category=InformationDisclosure, + vid="KHV037", ) @@ -66,7 +77,11 @@ class ExposedRunningPodsHandler(Vulnerability, Event): def __init__(self, count): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Running Pods", category=InformationDisclosure, vid="KHV038", + self, + component=Kubelet, + name="Exposed Running Pods", + category=InformationDisclosure, + vid="KHV038", ) self.count = count self.evidence = "{} running pods".format(self.count) @@ -77,7 +92,11 @@ class ExposedExecHandler(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Exec On Container", category=RemoteCodeExec, vid="KHV039", + self, + component=Kubelet, + name="Exposed Exec On Container", + category=RemoteCodeExec, + vid="KHV039", ) @@ -86,7 +105,11 @@ class ExposedRunHandler(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Run Inside Container", category=RemoteCodeExec, vid="KHV040", + self, + component=Kubelet, + name="Exposed Run Inside Container", + category=RemoteCodeExec, + vid="KHV040", ) @@ -95,7 +118,11 @@ class ExposedPortForwardHandler(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Port Forward", category=RemoteCodeExec, vid="KHV041", + self, + component=Kubelet, + name="Exposed Port Forward", + category=RemoteCodeExec, + vid="KHV041", ) @@ -105,7 +132,11 @@ class ExposedAttachHandler(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Attaching To Container", category=RemoteCodeExec, vid="KHV042", + self, + component=Kubelet, + name="Exposed Attaching To Container", + category=RemoteCodeExec, + vid="KHV042", ) @@ -115,7 +146,11 @@ class ExposedHealthzHandler(Vulnerability, Event): def __init__(self, status): Vulnerability.__init__( - self, component=Kubelet, name="Cluster Health Disclosure", category=InformationDisclosure, vid="KHV043", + self, + component=Kubelet, + name="Cluster Health Disclosure", + category=InformationDisclosure, + vid="KHV043", ) self.status = status self.evidence = f"status: {self.status}" @@ -143,7 +178,11 @@ class PrivilegedContainers(Vulnerability, Event): def __init__(self, containers): Vulnerability.__init__( - self, component=KubernetesCluster, name="Privileged Container", category=AccessRisk, vid="KHV044", + self, + component=KubernetesCluster, + name="Privileged Container", + category=AccessRisk, + vid="KHV044", ) self.containers = containers self.evidence = f"pod: {containers[0][0]}, " f"container: {containers[0][1]}, " f"count: {len(containers)}" @@ -154,7 +193,11 @@ class ExposedSystemLogs(Vulnerability, Event): def __init__(self): Vulnerability.__init__( - self, component=Kubelet, name="Exposed System Logs", category=InformationDisclosure, vid="KHV045", + self, + component=Kubelet, + name="Exposed System Logs", + category=InformationDisclosure, + vid="KHV045", ) @@ -163,7 +206,11 @@ class ExposedKubeletCmdline(Vulnerability, Event): def __init__(self, cmdline): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Kubelet Cmdline", category=InformationDisclosure, vid="KHV046", + self, + component=Kubelet, + name="Exposed Kubelet Cmdline", + category=InformationDisclosure, + vid="KHV046", ) self.cmdline = cmdline self.evidence = f"cmdline: {self.cmdline}" @@ -270,7 +317,9 @@ class SecureKubeletPortHunter(Hunter): def test_container_logs(self): config = get_config() logs_url = self.path + KubeletHandlers.CONTAINERLOGS.value.format( - pod_namespace=self.pod["namespace"], pod_id=self.pod["name"], container_name=self.pod["container"], + pod_namespace=self.pod["namespace"], + pod_id=self.pod["name"], + container_name=self.pod["container"], ) return self.session.get(logs_url, verify=False, timeout=config.network_timeout).status_code == 200 @@ -288,7 +337,11 @@ class SecureKubeletPortHunter(Hunter): return ( "/cri/exec/" in self.session.get( - exec_url, headers=headers, allow_redirects=False, verify=False, timeout=config.network_timeout, + exec_url, + headers=headers, + allow_redirects=False, + verify=False, + timeout=config.network_timeout, ).text ) @@ -303,10 +356,16 @@ class SecureKubeletPortHunter(Hunter): "Sec-Websocket-Protocol": "SPDY", } pf_url = self.path + KubeletHandlers.PORTFORWARD.value.format( - pod_namespace=self.pod["namespace"], pod_id=self.pod["name"], port=80, + pod_namespace=self.pod["namespace"], + pod_id=self.pod["name"], + port=80, ) self.session.get( - pf_url, headers=headers, verify=False, stream=True, timeout=config.network_timeout, + pf_url, + headers=headers, + verify=False, + stream=True, + timeout=config.network_timeout, ).status_code == 200 # TODO: what to return? @@ -314,7 +373,10 @@ class SecureKubeletPortHunter(Hunter): def test_run_container(self): config = get_config() run_url = self.path + KubeletHandlers.RUN.value.format( - pod_namespace="test", pod_id="test", container_name="test", cmd="", + pod_namespace="test", + pod_id="test", + container_name="test", + cmd="", ) # if we get a Method Not Allowed, we know we passed Authentication and Authorization. return self.session.get(run_url, verify=False, timeout=config.network_timeout).status_code == 405 @@ -339,7 +401,10 @@ class SecureKubeletPortHunter(Hunter): return ( "/cri/attach/" in self.session.get( - attach_url, allow_redirects=False, verify=False, timeout=config.network_timeout, + attach_url, + allow_redirects=False, + verify=False, + timeout=config.network_timeout, ).text ) @@ -347,7 +412,8 @@ class SecureKubeletPortHunter(Hunter): def test_logs_endpoint(self): config = get_config() logs_url = self.session.get( - self.path + KubeletHandlers.LOGS.value.format(path=""), timeout=config.network_timeout, + self.path + KubeletHandlers.LOGS.value.format(path=""), + timeout=config.network_timeout, ).text return "
" in logs_url
 
@@ -355,7 +421,9 @@ class SecureKubeletPortHunter(Hunter):
         def test_pprof_cmdline(self):
             config = get_config()
             cmd = self.session.get(
-                self.path + KubeletHandlers.PPROF_CMDLINE.value, verify=False, timeout=config.network_timeout,
+                self.path + KubeletHandlers.PPROF_CMDLINE.value,
+                verify=False,
+                timeout=config.network_timeout,
             )
             return cmd.text if cmd.status_code == 200 else None
 
@@ -647,7 +715,10 @@ class MaliciousIntentViaSecureKubeletPort(ActiveHunter):
         )
 
         self.rmdir_command(
-            run_request_url, directory_created, number_of_rmdir_attempts, seconds_to_wait_for_os_command,
+            run_request_url,
+            directory_created,
+            number_of_rmdir_attempts,
+            seconds_to_wait_for_os_command,
         )
 
     def check_file_exists(self, run_request_url, file):
@@ -718,7 +789,11 @@ class MaliciousIntentViaSecureKubeletPort(ActiveHunter):
         return ProveAnonymousAuth.has_no_error_nor_exception(directory_exists)
 
     def rmdir_command(
-        self, run_request_url, directory_to_remove, number_of_rmdir_attempts, seconds_to_wait_for_os_command,
+        self,
+        run_request_url,
+        directory_to_remove,
+        number_of_rmdir_attempts,
+        seconds_to_wait_for_os_command,
     ):
         if self.check_directory_exists(run_request_url, directory_to_remove):
             for _ in range(number_of_rmdir_attempts):
@@ -985,13 +1060,17 @@ class ProveRunHandler(ActiveHunter):
             cmd=command,
         )
         return self.event.session.post(
-            f"{self.base_path}/{run_url}", verify=False, timeout=config.network_timeout,
+            f"{self.base_path}/{run_url}",
+            verify=False,
+            timeout=config.network_timeout,
         ).text
 
     def execute(self):
         config = get_config()
         r = self.event.session.get(
-            f"{self.base_path}/" + KubeletHandlers.PODS.value, verify=False, timeout=config.network_timeout,
+            f"{self.base_path}/" + KubeletHandlers.PODS.value,
+            verify=False,
+            timeout=config.network_timeout,
         )
         if "items" in r.text:
             pods_data = r.json()["items"]
@@ -1025,7 +1104,9 @@ class ProveContainerLogsHandler(ActiveHunter):
     def execute(self):
         config = get_config()
         pods_raw = self.event.session.get(
-            self.base_url + KubeletHandlers.PODS.value, verify=False, timeout=config.network_timeout,
+            self.base_url + KubeletHandlers.PODS.value,
+            verify=False,
+            timeout=config.network_timeout,
         ).text
         if "items" in pods_raw:
             pods_data = json.loads(pods_raw)["items"]
diff --git a/kube_hunter/modules/hunting/mounts.py b/kube_hunter/modules/hunting/mounts.py
index 6a0d09e..ebd730f 100644
--- a/kube_hunter/modules/hunting/mounts.py
+++ b/kube_hunter/modules/hunting/mounts.py
@@ -25,7 +25,11 @@ class WriteMountToVarLog(Vulnerability, Event):
 
     def __init__(self, pods):
         Vulnerability.__init__(
-            self, KubernetesCluster, "Pod With Mount To /var/log", category=PrivilegeEscalation, vid="KHV047",
+            self,
+            KubernetesCluster,
+            "Pod With Mount To /var/log",
+            category=PrivilegeEscalation,
+            vid="KHV047",
         )
         self.pods = pods
         self.evidence = "pods: {}".format(", ".join((pod["metadata"]["name"] for pod in self.pods)))
@@ -37,7 +41,10 @@ class DirectoryTraversalWithKubelet(Vulnerability, Event):
 
     def __init__(self, output):
         Vulnerability.__init__(
-            self, KubernetesCluster, "Root Traversal Read On The Kubelet", category=PrivilegeEscalation,
+            self,
+            KubernetesCluster,
+            "Root Traversal Read On The Kubelet",
+            category=PrivilegeEscalation,
         )
         self.output = output
         self.evidence = "output: {}".format(self.output)
@@ -82,7 +89,10 @@ class ProveVarLogMount(ActiveHunter):
 
     def run(self, command, container):
         run_url = KubeletHandlers.RUN.value.format(
-            podNamespace=container["namespace"], podID=container["pod"], containerName=container["name"], cmd=command,
+            podNamespace=container["namespace"],
+            podID=container["pod"],
+            containerName=container["name"],
+            cmd=command,
         )
         return self.event.session.post(f"{self.base_path}/{run_url}", verify=False).text
 
@@ -91,7 +101,9 @@ class ProveVarLogMount(ActiveHunter):
         config = get_config()
         logger.debug("accessing /pods manually on ProveVarLogMount")
         pods = self.event.session.get(
-            f"{self.base_path}/" + KubeletHandlers.PODS.value, verify=False, timeout=config.network_timeout,
+            f"{self.base_path}/" + KubeletHandlers.PODS.value,
+            verify=False,
+            timeout=config.network_timeout,
         ).json()["items"]
         for pod in pods:
             volume = VarLogMountHunter(ExposedPodsHandler(pods=pods)).has_write_mount_to(pod, "/var/log")
@@ -117,7 +129,9 @@ class ProveVarLogMount(ActiveHunter):
             path=re.sub(r"^/var/log", "", host_path) + symlink_name
         )
         content = self.event.session.get(
-            f"{self.base_path}/{path_in_logs_endpoint}", verify=False, timeout=config.network_timeout,
+            f"{self.base_path}/{path_in_logs_endpoint}",
+            verify=False,
+            timeout=config.network_timeout,
         ).text
         # removing symlink
         self.run(f"rm {mount_path}/{symlink_name}", container=container)
@@ -134,7 +148,10 @@ class ProveVarLogMount(ActiveHunter):
                 }
                 try:
                     output = self.traverse_read(
-                        "/etc/shadow", container=cont, mount_path=mount_path, host_path=volume["hostPath"]["path"],
+                        "/etc/shadow",
+                        container=cont,
+                        mount_path=mount_path,
+                        host_path=volume["hostPath"]["path"],
                     )
                     self.publish_event(DirectoryTraversalWithKubelet(output=output))
                 except Exception:
diff --git a/kube_hunter/modules/hunting/proxy.py b/kube_hunter/modules/hunting/proxy.py
index 63405c7..4f5a7af 100644
--- a/kube_hunter/modules/hunting/proxy.py
+++ b/kube_hunter/modules/hunting/proxy.py
@@ -23,7 +23,11 @@ class KubeProxyExposed(Vulnerability, Event):
 
     def __init__(self):
         Vulnerability.__init__(
-            self, KubernetesCluster, "Proxy Exposed", category=InformationDisclosure, vid="KHV049",
+            self,
+            KubernetesCluster,
+            "Proxy Exposed",
+            category=InformationDisclosure,
+            vid="KHV049",
         )
 
 
@@ -89,7 +93,9 @@ class ProveProxyExposed(ActiveHunter):
     def execute(self):
         config = get_config()
         version_metadata = requests.get(
-            f"http://{self.event.host}:{self.event.port}/version", verify=False, timeout=config.network_timeout,
+            f"http://{self.event.host}:{self.event.port}/version",
+            verify=False,
+            timeout=config.network_timeout,
         ).json()
         if "buildDate" in version_metadata:
             self.event.evidence = "build date: {}".format(version_metadata["buildDate"])
@@ -107,11 +113,15 @@ class K8sVersionDisclosureProve(ActiveHunter):
     def execute(self):
         config = get_config()
         version_metadata = requests.get(
-            f"http://{self.event.host}:{self.event.port}/version", verify=False, timeout=config.network_timeout,
+            f"http://{self.event.host}:{self.event.port}/version",
+            verify=False,
+            timeout=config.network_timeout,
         ).json()
         if "gitVersion" in version_metadata:
             self.publish_event(
                 K8sVersionDisclosure(
-                    version=version_metadata["gitVersion"], from_endpoint="/version", extra_info="on kube-proxy",
+                    version=version_metadata["gitVersion"],
+                    from_endpoint="/version",
+                    extra_info="on kube-proxy",
                 )
             )
diff --git a/kube_hunter/modules/hunting/secrets.py b/kube_hunter/modules/hunting/secrets.py
index 563da1d..8e2f3b6 100644
--- a/kube_hunter/modules/hunting/secrets.py
+++ b/kube_hunter/modules/hunting/secrets.py
@@ -28,7 +28,10 @@ class SecretsAccess(Vulnerability, Event):
 
     def __init__(self, evidence):
         Vulnerability.__init__(
-            self, component=KubernetesCluster, name="Access to pod's secrets", category=AccessRisk,
+            self,
+            component=KubernetesCluster,
+            name="Access to pod's secrets",
+            category=AccessRisk,
         )
         self.evidence = evidence
 
diff --git a/kube_hunter/modules/report/dispatchers.py b/kube_hunter/modules/report/dispatchers.py
index 7d8eaf5..6b5b6ba 100644
--- a/kube_hunter/modules/report/dispatchers.py
+++ b/kube_hunter/modules/report/dispatchers.py
@@ -12,7 +12,10 @@ class HTTPDispatcher:
         dispatch_url = os.environ.get("KUBEHUNTER_HTTP_DISPATCH_URL", "https://localhost/")
         try:
             r = requests.request(
-                dispatch_method, dispatch_url, json=report, headers={"Content-Type": "application/json"},
+                dispatch_method,
+                dispatch_url,
+                json=report,
+                headers={"Content-Type": "application/json"},
             )
             r.raise_for_status()
             logger.info(f"Report was dispatched to: {dispatch_url}")
diff --git a/tests/core/test_cloud.py b/tests/core/test_cloud.py
index 3e40add..04cb5da 100644
--- a/tests/core/test_cloud.py
+++ b/tests/core/test_cloud.py
@@ -8,7 +8,7 @@ set_config(Config())
 
 
 def test_presetcloud():
-    """ Testing if it doesn't try to run get_cloud if the cloud type is already set.
+    """Testing if it doesn't try to run get_cloud if the cloud type is already set.
     get_cloud(1.2.3.4) will result with an error
     """
     expcted = "AWS"
diff --git a/tests/discovery/test_apiserver.py b/tests/discovery/test_apiserver.py
index bc9d513..5a9a16e 100644
--- a/tests/discovery/test_apiserver.py
+++ b/tests/discovery/test_apiserver.py
@@ -20,7 +20,9 @@ def test_ApiServer():
         m.get("https://mockOther:443", text="elephant")
         m.get("https://mockKubernetes:443", text='{"code":403}', status_code=403)
         m.get(
-            "https://mockKubernetes:443/version", text='{"major": "1.14.10"}', status_code=200,
+            "https://mockKubernetes:443/version",
+            text='{"major": "1.14.10"}',
+            status_code=200,
         )
 
         e = Event()
@@ -44,11 +46,15 @@ def test_ApiServerWithServiceAccountToken():
     counter = 0
     with requests_mock.Mocker() as m:
         m.get(
-            "https://mockKubernetes:443", request_headers={"Authorization": "Bearer very_secret"}, text='{"code":200}',
+            "https://mockKubernetes:443",
+            request_headers={"Authorization": "Bearer very_secret"},
+            text='{"code":200}',
         )
         m.get("https://mockKubernetes:443", text='{"code":403}', status_code=403)
         m.get(
-            "https://mockKubernetes:443/version", text='{"major": "1.14.10"}', status_code=200,
+            "https://mockKubernetes:443/version",
+            text='{"major": "1.14.10"}',
+            status_code=200,
         )
         m.get("https://mockOther:443", text="elephant")
 
diff --git a/tests/hunting/test_apiserver_hunter.py b/tests/hunting/test_apiserver_hunter.py
index fe0b187..3318251 100644
--- a/tests/hunting/test_apiserver_hunter.py
+++ b/tests/hunting/test_apiserver_hunter.py
@@ -56,7 +56,8 @@ def test_AccessApiServer():
     with requests_mock.Mocker() as m:
         m.get("https://mockKubernetes:443/api", text="{}")
         m.get(
-            "https://mockKubernetes:443/api/v1/namespaces", text='{"items":[{"metadata":{"name":"hello"}}]}',
+            "https://mockKubernetes:443/api/v1/namespaces",
+            text='{"items":[{"metadata":{"name":"hello"}}]}',
         )
         m.get(
             "https://mockKubernetes:443/api/v1/pods",
@@ -64,10 +65,12 @@ def test_AccessApiServer():
                             {"metadata":{"name":"podB", "namespace":"namespaceB"}}]}',
         )
         m.get(
-            "https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/roles", status_code=403,
+            "https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/roles",
+            status_code=403,
         )
         m.get(
-            "https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/clusterroles", text='{"items":[]}',
+            "https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/clusterroles",
+            text='{"items":[]}',
         )
         m.get(
             "https://mockkubernetes:443/version",
@@ -91,7 +94,8 @@ def test_AccessApiServer():
         # TODO check that these responses reflect what Kubernetes does
         m.get("https://mocktoken:443/api", text="{}")
         m.get(
-            "https://mocktoken:443/api/v1/namespaces", text='{"items":[{"metadata":{"name":"hello"}}]}',
+            "https://mocktoken:443/api/v1/namespaces",
+            text='{"items":[{"metadata":{"name":"hello"}}]}',
         )
         m.get(
             "https://mocktoken:443/api/v1/pods",
@@ -99,7 +103,8 @@ def test_AccessApiServer():
                             {"metadata":{"name":"podB", "namespace":"namespaceB"}}]}',
         )
         m.get(
-            "https://mocktoken:443/apis/rbac.authorization.k8s.io/v1/roles", status_code=403,
+            "https://mocktoken:443/apis/rbac.authorization.k8s.io/v1/roles",
+            status_code=403,
         )
         m.get(
             "https://mocktoken:443/apis/rbac.authorization.k8s.io/v1/clusterroles",
@@ -228,10 +233,12 @@ def test_AccessApiServerActive():
         )
         m.post("https://mockKubernetes:443/api/v1/clusterroles", text="{}")
         m.post(
-            "https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/clusterroles", text="{}",
+            "https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/clusterroles",
+            text="{}",
         )
         m.post(
-            "https://mockkubernetes:443/api/v1/namespaces/hello-namespace/pods", text="{}",
+            "https://mockkubernetes:443/api/v1/namespaces/hello-namespace/pods",
+            text="{}",
         )
         m.post(
             "https://mockkubernetes:443" "/apis/rbac.authorization.k8s.io/v1/namespaces/hello-namespace/roles",
diff --git a/tests/hunting/test_kubelet.py b/tests/hunting/test_kubelet.py
index 1b54e87..2d377ba 100644
--- a/tests/hunting/test_kubelet.py
+++ b/tests/hunting/test_kubelet.py
@@ -73,8 +73,8 @@ def create_test_event_type_one():
 
 
 def create_test_event_type_two():
-    exposed_existing_privileged_containers_via_secure_kubelet_port_event = ExposedExistingPrivilegedContainersViaSecureKubeletPort(
-        exposed_privileged_containers
+    exposed_existing_privileged_containers_via_secure_kubelet_port_event = (
+        ExposedExistingPrivilegedContainersViaSecureKubeletPort(exposed_privileged_containers)
     )
     exposed_existing_privileged_containers_via_secure_kubelet_port_event.host = "localhost"
     exposed_existing_privileged_containers_via_secure_kubelet_port_event.session = requests.Session()