Merge branch 'report_token_feature' of bitbucket.org:scalock/kube-hunter

This commit is contained in:
daniel_sagi
2018-07-04 11:54:58 +03:00
8 changed files with 377 additions and 201 deletions

View File

@@ -6,42 +6,81 @@ import sys
import time
parser = argparse.ArgumentParser(description='Kube-Hunter - hunts for security weaknesses in Kubernetes clusters')
parser.add_argument('--internal', action="store_true", help="set hunting of all internal network interfaces")
parser.add_argument('--pod', action="store_true", help="set hunter as an insider pod")
parser.add_argument('--cidr', type=str, help="set manual cidr to scan, example: 192.168.0.0/16")
parser.add_argument('--quick', action="store_true", help="scanning only known small sections of the subnet")
parser.add_argument('--mapping', action="store_true", help="outputs only mapping of cluster's nodes")
parser.add_argument('--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")
parser.add_argument('--active', action="store_true", help="enables active hunting")
parser.add_argument('--log', type=str, metavar="LOGLEVEL", default='INFO', help="set log level, options are:\nDEBUG INFO WARNING")
parser.add_argument('--log', type=str, metavar="LOGLEVEL", default='INFO', help="set log level, options are: debug, info, warn, none")
parser.add_argument('--token', type=str, metavar="AQUA_TOKEN", help="specify the token retrieved from Aqua, after finished executing, the report will be visible on kube-hunter's site")
config = parser.parse_args()
try:
loglevel = getattr(logging, config.log.upper())
except:
pass
logging.basicConfig(level=loglevel, format='%(asctime)s - [%(levelname)s]: %(message)s')
if config.log.lower() != "none":
logging.basicConfig(level=loglevel, format='%(asctime)s - [%(levelname)s]: %(message)s')
import log
from report import reporter
from src.core.events import handler
from src.modules.discovery import HostDiscovery
from src.modules.discovery.hosts import HostScanEvent
def interactive_set_config():
"""Sets config manually, returns True for success"""
options = {
"Remote scanning": "scans one or more specific IPs or DNS names",
"Internal scanning": "scans all network interfaces",
"CIDR scanning": "scans a spesific cidr"
} # maps between option and its explanation
print "Choose one of the options below:"
for i, (option, explanation) in enumerate(options.items()):
print "{}. {} ({})".format(i+1, option.ljust(20), explanation)
choice = raw_input("Your choice: ")
if choice == '1':
config.remote = raw_input("Remotes (seperated by a ','): ").replace(' ', '').split(',')
elif choice == '2':
config.internal = True
elif choice == '3':
config.cidr = raw_input("CIDR (example - 192.168.1.0/24): ").replace(' ', '')
else:
return False
return True
def main():
logging.info("Started")
scan_options = [
config.pod,
config.cidr,
config.remote,
config.internal
]
hunt_started = False
try:
handler.publish_event(HostScanEvent(predefined_hosts=config.remote))
if not any(scan_options):
if not interactive_set_config(): return
hunt_started = True
logging.info("Started")
handler.publish_event(HostScanEvent())
# Blocking to see discovery output
handler.join()
except KeyboardInterrupt:
logging.debug("Kube-Hunter stopped by user")
finally:
handler.free()
logging.debug("Cleaned Queue")
log.print_results()
if hunt_started:
handler.free()
logging.debug("Cleaned Queue")
if config.token:
reporter.send_report(token=config.token)
else:
reporter.print_tables()
if config.pod:
while True: time.sleep(5)
if __name__ == '__main__':
main()
# Proof -> Evidence
main()

View File

@@ -1,101 +0,0 @@
import logging
from prettytable import ALL, PrettyTable
from __main__ import config
from src.core.events import handler
from src.core.events.types import Service, Vulnerability
services = list()
vulnerabilities = list()
EVIDENCE_PREVIEW = 40
MAX_WIDTH_VULNS = 70
MAX_WIDTH_SERVICES = 60
@handler.subscribe(Vulnerability)
class VulnerabilityReport(object):
def __init__(self, event):
self.vulnerability = event
def execute(self):
logging.info("[VULNERABILITY - {name}] {desc}".format(
name=self.vulnerability.get_name(),
desc=self.vulnerability.explain(),
))
vulnerabilities.append(self.vulnerability)
# TODO: Add ActiveHunter replacement by id, when a vulnerability comes from active hunter, it replaces it's predecessor
@handler.subscribe(Service)
class OpenServiceReport(object):
def __init__(self, event):
self.service = event
def execute(self):
logging.info("[OPEN SERVICE - {name}] IP:{host} PORT:{port}".format(
name=self.service.name,
desc=self.service.desc,
host=self.service.host,
port=self.service.port
))
services.append(self.service)
def print_nodes():
nodes_table = PrettyTable(["Type", "Location"], hrules=ALL)
nodes_table.align="l"
nodes_table.max_width=MAX_WIDTH_SERVICES
nodes_table.padding_width=1
nodes_table.sortby="Type"
nodes_table.reversesort=True
nodes_table.header_style="upper"
# TODO: replace with sets
id_memory = list()
for service in services:
if service.id not in id_memory:
nodes_table.add_row(["Slave/Master", service.host])
id_memory.append(service.id)
print "Nodes:"
print nodes_table
print
def print_services():
services_table = PrettyTable(["Service", "Location", "Description"], hrules=ALL)
services_table.align="l"
services_table.max_width=MAX_WIDTH_SERVICES
services_table.padding_width=1
services_table.sortby="Service"
services_table.reversesort=True
services_table.header_style="upper"
for service in services:
services_table.add_row([service.get_name(), "{}:{}{}".format(service.host, service.port, service.get_path()), service.explain()])
print "Open Services:"
print services_table
print
def print_vulnerabilities():
column_names = ["Location", "Category", "Vulnerability", "Description"]
if config.active: column_names.append("Evidence")
vuln_table = PrettyTable(column_names, hrules=ALL)
vuln_table.align="l"
vuln_table.max_width=MAX_WIDTH_VULNS
vuln_table.sortby="Category"
vuln_table.reversesort=True
vuln_table.padding_width=1
vuln_table.header_style="upper"
for vuln in vulnerabilities:
row = ["{}:{}".format(vuln.host, vuln.port) if vuln.host else "", vuln.component.name, vuln.get_name(), vuln.explain()]
if config.active:
evidence = str(vuln.evidence)[:EVIDENCE_PREVIEW] + "..." if len(str(vuln.evidence)) > EVIDENCE_PREVIEW else str(vuln.evidence)
row.append(evidence)
vuln_table.add_row(row)
print "Vulnerabilities:"
print vuln_table
print
def print_results():
print_nodes()
if not config.mapping:
print_services()
print_vulnerabilities()

209
report/reporter.py Normal file
View File

@@ -0,0 +1,209 @@
import json
import logging
import time
from collections import defaultdict
import requests
from prettytable import ALL, PrettyTable
from __main__ import config
from src.core.events import handler
from src.core.events.types import Service, Vulnerability
# [event, ...]
services = list()
# [(TypeClass, event), ...]
insights = list()
vulnerabilities = list()
EVIDENCE_PREVIEW = 40
MAX_WIDTH_VULNS = 70
MAX_WIDTH_SERVICES = 60
AQUA_PUSH_URL = "https://qlyscbqwl7.execute-api.us-east-1.amazonaws.com/Prod/submit?token={token}"
AQUA_RESULTS_URL = "https://qlyscbqwl7.execute-api.us-east-1.amazonaws.com/Prod/result?token={token}"
@handler.subscribe(Service)
@handler.subscribe(Vulnerability)
class Reporter(object):
"""Reportes can be initiated by the event handler, and by regular decaration. for usage on end of runtime"""
def __init__(self, event=None):
self.event = event
self.insights_by_id = defaultdict(list)
self.services_by_id = defaultdict(list)
def execute(self):
"""function is called only when collecting data"""
global services, insights
bases = self.event.__class__.__mro__
if Service in bases:
services.append(self.event)
logging.info("[OPEN SERVICE - {name}] IP:{host} PORT:{port}".format(
host=self.event.host,
port=self.event.port,
name=self.event.get_name(),
desc=self.event.explain()
))
elif Vulnerability in bases:
insights.append((Vulnerability, self.event))
vulnerabilities.append(self.event)
logging.info("[VULNERABILITY - {name}] {desc}".format(
name=self.event.get_name(),
desc=self.event.explain(),
))
if config.token:
self.send_report(token=config.token)
def print_tables(self):
"""generates report tables and outputs to stdout"""
if len(services):
print_nodes()
if not config.mapping:
print_services()
print_vulnerabilities()
else:
print "\nKube Hunter couldn't find any clusters"
# print "\nKube Hunter couldn't find any clusters. {}".format("Maybe try with --active?" if not config.active else "")
def build_sub_services(self, services_list):
# correlation functions
def get_insights_by_service(service):
"""generates list of insights related to a given service"""
insights = list()
for insight_type, insight in self.insights_by_id[service.event_id]:
if service in insight.history:
insights.append((insight_type, insight))
return insights
def get_services_by_service(parent_service):
"""generates list of insights related to a given service"""
services = list()
for service in self.services_by_id[parent_service.event_id]:
if service != parent_service and parent_service in service.history:
services.append(service)
self.services_by_id[parent_service.event_id].remove(service)
return services
current_list = list()
for service in services_list:
current_list.append(
{
"type": service.get_name(),
"metadata": {
"port": service.port,
"path": service.get_path()
},
"description": service.explain()
})
next_services = get_services_by_service(service)
if next_services:
current_list[-1]["services"] = self.build_sub_services(next_services)
current_list[-1]["insights"] = [{
"type": insight_type.__name__,
"name": insight.get_name(),
"description": insight.explain(),
"evidence": insight.evidence if insight_type == Vulnerability else ""
} for insight_type, insight in get_insights_by_service(service)]
return current_list
def send_report(self, token):
def generate_report():
"""function generates a report corresponding to specifications of the frontend of kubehunter"""
for service in services:
self.services_by_id[service.event_id].append(service)
for insight_type, insight in insights:
self.insights_by_id[insight.event_id].append((insight_type, insight))
# building first layer of services (nodes)
report = defaultdict(list)
for _, services_list in self.services_by_id.items():
service_report = {
"type": "Node", # on future, determine if slave or master
"metadata": {
"host": str(services_list[0].host)
},
# then constructing their sub services tree
"services": self.build_sub_services(services_list)
}
report["services"].append(service_report)
return report
finished = (not handler.unfinished_tasks)
logging.debug("generating report")
report = {
'results': generate_report(),
'metadata': {
'finished': finished
}
}
logging.debug("uploading report")
r = requests.put(AQUA_PUSH_URL.format(token=token), json=report)
if r.status_code == 201: # created status
logging.debug("report was uploaded successfully")
if finished:
print "\nYour report: \n{}".format(AQUA_RESULTS_URL.format(token=token))
else:
logging.debug("Failed sending report with:{}, {}".format(r.status_code, r.text))
if finished:
print "\nCould not send report.\n{}".format(json.loads(r.text).get("status", ""))
reporter = Reporter()
""" Tables Generation """
def print_nodes():
nodes_table = PrettyTable(["Type", "Location"], hrules=ALL)
nodes_table.align="l"
nodes_table.max_width=MAX_WIDTH_SERVICES
nodes_table.padding_width=1
nodes_table.sortby="Type"
nodes_table.reversesort=True
nodes_table.header_style="upper"
# TODO: replace with sets
id_memory = list()
for service in services:
if service.event_id not in id_memory:
nodes_table.add_row(["Slave/Master", service.host])
id_memory.append(service.event_id)
print "Nodes:"
print nodes_table
print
def print_services():
services_table = PrettyTable(["Service", "Location", "Description"], hrules=ALL)
services_table.align="l"
services_table.max_width=MAX_WIDTH_SERVICES
services_table.padding_width=1
services_table.sortby="Service"
services_table.reversesort=True
services_table.header_style="upper"
for service in services:
services_table.add_row([service.get_name(), "{}:{}{}".format(service.host, service.port, service.get_path()), service.explain()])
print "Open Services:"
print services_table
print
def print_vulnerabilities():
column_names = ["Location", "Category", "Vulnerability", "Description"]
if config.active: column_names.append("Evidence")
vuln_table = PrettyTable(column_names, hrules=ALL)
vuln_table.align="l"
vuln_table.max_width=MAX_WIDTH_VULNS
vuln_table.sortby="Category"
vuln_table.reversesort=True
vuln_table.padding_width=1
vuln_table.header_style="upper"
for vuln in vulnerabilities:
row = ["{}:{}".format(vuln.host, vuln.port) if vuln.host else "", vuln.component.name, vuln.get_name(), vuln.explain()]
if config.active:
evidence = str(vuln.evidence)[:EVIDENCE_PREVIEW] + "..." if len(str(vuln.evidence)) > EVIDENCE_PREVIEW else str(vuln.evidence)
row.append(evidence)
vuln_table.add_row(row)
print "Vulnerabilities:"
print vuln_table
print

View File

@@ -41,9 +41,6 @@ class Service(object):
def explain(self):
return self.__doc__
def proof(self):
return self.name
class Vulnerability(object):
def __init__(self, component, name):
self.component = component
@@ -64,7 +61,7 @@ class NewHostEvent(Event):
def __init__(self, host, cloud=None):
global event_id_count
self.host = host
self.id = event_id_count
self.event_id = event_id_count
self.cloud = cloud
event_id_count += 1

View File

@@ -33,14 +33,14 @@ class HostScanEvent(Event):
def get_auth_token(self):
if config.pod:
with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file:
return token_file.read()
return None
try:
with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file:
return token_file.read()
except IOError:
pass
def get_client_cert(self):
if config.pod:
return "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
return None
@handler.subscribe(HostScanEvent)
class HostDiscovery(Hunter):
@@ -58,16 +58,16 @@ class HostDiscovery(Hunter):
self.publish_event(NewHostEvent(host=ip, cloud=cloud))
except:
logging.error("unable to parse cidr")
elif config.internal:
self.scan_interfaces()
elif len(config.remote) > 0:
for host in config.remote:
self.publish_event(NewHostEvent(host=host, cloud=self.get_cloud(host)))
elif config.pod:
if self.is_azure_pod():
self.azure_metadata_discovery()
else:
self.traceroute_discovery()
elif len(self.event.predefined_hosts) == 0:
self.scan_interfaces()
else:
for host in self.event.predefined_hosts:
self.publish_event(NewHostEvent(host=host, cloud=self.get_cloud(host)))
def get_cloud(self, host):
metadata = requests.get("http://www.azurespeed.com/api/region?ipOrUrl={ip}".format(ip=host)).text
@@ -76,11 +76,10 @@ class HostDiscovery(Hunter):
def is_azure_pod(self):
try:
if requests.get("http://169.254.169.254/metadata/instance?api-version=2017-08-01", headers={"Metadata":"true"}).status_code == 200:
if requests.get("http://169.254.169.254/metadata/instance?api-version=2017-08-01", headers={"Metadata":"true"}, timeout=5).status_code == 200:
return True
except Exception as ex:
logging.debug("Not azure cluster " + str(ex.message))
return False
except requests.exceptions.ConnectionError:
return False
# for pod scanning
def traceroute_discovery(self):

View File

@@ -24,22 +24,6 @@ class SecureKubeletEvent(Service, Event):
Service.__init__(self, name="Kubelet API")
""" Vulnerabilities """
class ExposedPodsHandler(Vulnerability, Event):
"""Exposes sensitive information about pods that are bound to the node"""
def __init__(self):
Vulnerability.__init__(self, Kubelet, "Exposed /pods")
class AnonymousAuthEnabled(Vulnerability, Event):
"""Anonymous Auth to the kubelet, exposes secure access to all requests on the kubelet"""
def __init__(self):
Vulnerability.__init__(self, Kubelet, "Anonymous Authentication")
def proof(self):
pass # TODO: decide on an appropriate proof
class KubeletPorts(Enum):
SECURED = 10250
READ_ONLY = 10255
@@ -53,21 +37,18 @@ class KubeletDiscovery(Hunter):
logging.debug(self.event.host)
r = requests.get("http://{host}:{port}/pods".format(host=self.event.host, port=self.event.port))
if r.status_code == 200:
self.publish_event(ExposedPodsHandler())
self.publish_event(ReadOnlyKubeletEvent())
def get_secure_access(self):
event = SecureKubeletEvent()
if self.ping_kubelet(authenticate=False) == 200:
self.publish_event(ExposedPodsHandler())
self.publish_event(AnonymousAuthEnabled())
event.anonymous_auth = True
event.secure = False
# anonymous authentication is disabled
elif self.ping_kubelet(authenticate=True) == 200:
event.anonymous_auth = False
event.secure = True
self.publish_event(event)
def ping_kubelet(self, authenticate=False):
def ping_kubelet(self, authenticate):
r = requests.Session()
if authenticate:
if self.event.auth_token:

View File

@@ -8,12 +8,22 @@ import urllib3
from __main__ import config
from ...core.events import handler
from ...core.events.types import Vulnerability, Event
from ..discovery.kubelet import ReadOnlyKubeletEvent, SecureKubeletEvent, ExposedPodsHandler
from ..discovery.kubelet import ReadOnlyKubeletEvent, SecureKubeletEvent
from ...core.types import Hunter, ActiveHunter, KubernetesCluster, Kubelet
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
""" Vulnerabilities """
class ExposedPodsHandler(Vulnerability, Event):
"""Exposes all complete PodSpecs bound to a node"""
def __init__(self):
Vulnerability.__init__(self, Kubelet, "Exposed /pods")
class AnonymousAuthEnabled(Vulnerability, Event):
"""Anonymous Auth to the kubelet, exposes secure access to all requests on the kubelet"""
def __init__(self):
Vulnerability.__init__(self, Kubelet, "Anonymous Authentication")
class ExposedContainerLogsHandler(Vulnerability, Event):
"""Outputs logs from a running container"""
def __init__(self):
@@ -50,6 +60,11 @@ class ExposedAttachHandler(Vulnerability, Event):
Vulnerability.__init__(self, Kubelet, "Exposed /attach")
self.remediation="--enable-debugging-handlers=False On Kubelet"
class ExposedHealthzHandler(Vulnerability, Event):
"""By accessing open /healthz handler, an attacker could get the cluster health state"""
def __init__(self):
Vulnerability.__init__(self, Kubelet, "Cluster Health Disclosure")
class K8sVersionDisclosure(Vulnerability, Event):
"""Discloses the kubernetes version, exposed from a log on the /metrics endpoint"""
def __init__(self, version):
@@ -70,6 +85,7 @@ class ReadOnlyKubeletPortHunter(Hunter):
def __init__(self, event):
self.event = event
self.path = "http://{}:{}/".format(self.event.host, self.event.port)
self.pods_endpoint_data = ""
def get_k8s_version(self):
metrics = requests.get(self.path + "metrics").text
@@ -82,23 +98,35 @@ class ReadOnlyKubeletPortHunter(Hunter):
# returns list of tuples of Privileged container and their pod.
def find_privileged_containers(self):
pods = json.loads(requests.get(self.path + "pods").text)
privileged_containers = list()
if "items" in pods:
for pod in pods["items"]:
if self.pods_endpoint_data:
for pod in self.pods_endpoint_data["items"]:
for container in pod["spec"]["containers"]:
if "securityContext" in container and "privileged" in container["securityContext"] and container["securityContext"]["privileged"]:
privileged_containers.append((pod["metadata"]["name"], container["name"]))
return privileged_containers if len(privileged_containers) > 0 else None
def get_pods_endpoint(self):
response = requests.get(self.path + "pods")
if "items" in response.text:
return json.loads(response.text)
def check_healthz_endpoint(self):
return requests.get(self.path + "healthz", verify=False).status_code == 200
def execute(self):
self.pods_endpoint_data = self.get_pods_endpoint()
k8s_version = self.get_k8s_version()
privileged_containers = self.find_privileged_containers()
if k8s_version:
self.publish_event(K8sVersionDisclosure(version=k8s_version))
if privileged_containers:
self.publish_event(PrivilegedContainers(containers=privileged_containers))
if self.pods_endpoint_data:
self.publish_event(ExposedPodsHandler())
if self.check_healthz_endpoint():
self.publish_event(ExposedHealthzHandler())
@handler.subscribe(SecureKubeletEvent)
class SecureKubeletPortHunter(Hunter):
class DebugHandlers(object):
@@ -186,55 +214,69 @@ class SecureKubeletPortHunter(Hunter):
self.session = requests.Session()
if self.event.secure:
self.session.headers.update({"Authorization": "Bearer {}".format(self.event.auth_token)})
self.session.cert = self.event.client_cert
# self.session.cert = self.event.client_cert
self.path = "https://{}:{}/".format(self.event.host, 10250)
self.kubehunter_pod = {"name": "kube-hunter", "namespace": "default", "container": "kube-hunter"}
self.pods_endpoint_data = ""
def get_pods_endpoint(self):
response = self.session.get(self.path + "pods", verify=False)
if "items" in response.text:
return json.loads(response.text)
def check_healthz_endpoint(self):
return requests.get(self.path + "healthz", verify=False).status_code == 200
def execute(self):
self.test_debugging_handlers()
self.pods_endpoint_data = self.get_pods_endpoint()
if not self.event.secure:
self.publish_event(AnonymousAuthEnabled())
if self.pods_endpoint_data:
self.publish_event(ExposedPodsHandler())
if self.check_healthz_endpoint():
self.publish_event(ExposedHealthzHandler())
self.test_handlers()
def test_debugging_handlers(self):
def test_handlers(self):
# if kube-hunter runs in a pod, we test with kube-hunter's pod
pod = self.get_self_pod() if config.pod else self.get_random_pod()
debug_handlers = self.DebugHandlers(self.path, pod=pod, session=self.session)
try:
if debug_handlers.test_container_logs():
self.publish_event(ExposedContainerLogsHandler())
if debug_handlers.test_exec_container():
self.publish_event(ExposedExecHandler())
if debug_handlers.test_run_container():
self.publish_event(ExposedRunHandler())
if debug_handlers.test_running_pods():
self.publish_event(ExposedRunningPodsHandler())
if debug_handlers.test_port_forward():
self.publish_event(ExposedPortForwardHandler()) # not implemented
if debug_handlers.test_attach_container():
self.publish_event(ExposedAttachHandler())
except Exception as ex:
logging.debug(str(ex.message))
def get_self_pod(self):
return {"name": "kube-hunter",
"namespace": "default",
"container": "kube-hunter"}
pod = self.kubehunter_pod if config.pod else self.get_random_pod()
if pod:
debug_handlers = self.DebugHandlers(self.path, pod=pod, session=self.session)
try:
if debug_handlers.test_container_logs():
self.publish_event(ExposedContainerLogsHandler())
if debug_handlers.test_exec_container():
self.publish_event(ExposedExecHandler())
if debug_handlers.test_run_container():
self.publish_event(ExposedRunHandler())
if debug_handlers.test_running_pods():
self.publish_event(ExposedRunningPodsHandler())
if debug_handlers.test_port_forward():
self.publish_event(ExposedPortForwardHandler()) # not implemented
if debug_handlers.test_attach_container():
self.publish_event(ExposedAttachHandler())
except Exception as ex:
logging.debug(str(ex.message))
else:
pass # no pod to check on.
# trying to get a pod from default namespace, if doesnt exist, gets a kube-system one
def get_random_pod(self):
pods_data = json.loads(self.session.get("https://{host}:{port}/pods".format(host=self.event.host, port=self.event.port), verify=False).text)['items']
# filter running kubesystem pod
is_default_pod = lambda pod: pod["metadata"]["namespace"] == "default" and pod["status"]["phase"] == "Running"
is_kubesystem_pod = lambda pod: pod["metadata"]["namespace"] == "kube-system" and pod["status"]["phase"] == "Running"
pod_data = next((pod_data for pod_data in pods_data if is_default_pod(pod_data)), None)
if not pod_data:
pod_data = next((pod_data for pod_data in pods_data if is_kubesystem_pod(pod_data)), None)
container_data = (container_data for container_data in pod_data["spec"]["containers"]).next()
return {
"name": pod_data["metadata"]["name"],
"container": container_data["name"],
"namespace": pod_data["metadata"]["namespace"]
}
if self.pods_endpoint_data:
pods_data = self.pods_endpoint_data["items"]
# filter running kubesystem pod
is_default_pod = lambda pod: pod["metadata"]["namespace"] == "default" and pod["status"]["phase"] == "Running"
is_kubesystem_pod = lambda pod: pod["metadata"]["namespace"] == "kube-system" and pod["status"]["phase"] == "Running"
pod_data = next((pod_data for pod_data in pods_data if is_default_pod(pod_data)), None)
if not pod_data:
pod_data = next((pod_data for pod_data in pods_data if is_kubesystem_pod(pod_data)), None)
container_data = (container_data for container_data in pod_data["spec"]["containers"]).next()
return {
"name": pod_data["metadata"]["name"],
"container": container_data["name"],
"namespace": pod_data["metadata"]["namespace"]
}
@handler.subscribe(ExposedRunHandler)
class ProveRunHandler(ActiveHunter):
@@ -267,6 +309,16 @@ class ProveRunHandler(ActiveHunter):
self.event.evidence = "uname: " + output
break
@handler.subscribe(ExposedHealthzHandler)
class ProveHealthzHandler(ActiveHunter):
def __init__(self, event):
self.event = event
def execute(self):
protocol = "https" if self.event.port == 10250 else "http"
self.event.evidence = requests.get("{protocol}://{host}:{port}/healthz".format(protocol=protocol, host=self.event.host, port=self.event.port), verify=False).text
@handler.subscribe(ExposedPodsHandler)
class ProvePodsHandler(ActiveHunter):
def __init__(self, event):