mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-08-23 22:26:23 +00:00
Cleanup conf refactor (#310)
Reorganize config files, and argparse. Resolves #289 Resolves #292
This commit is contained in:
+14
-39
@@ -1,66 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from kube_hunter.conf import config
|
||||
from kube_hunter.modules.report.plain import PlainReporter
|
||||
from kube_hunter.modules.report.yaml import YAMLReporter
|
||||
from kube_hunter.modules.report.json import JSONReporter
|
||||
from kube_hunter.modules.report.dispatchers import STDOUTDispatcher, HTTPDispatcher
|
||||
from kube_hunter.core.events import handler
|
||||
from kube_hunter.core.events.types import HuntFinished, HuntStarted
|
||||
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent, HostScanEvent
|
||||
from kube_hunter.modules.report import get_reporter, get_dispatcher
|
||||
|
||||
|
||||
loglevel = getattr(logging, config.log.upper(), logging.INFO)
|
||||
|
||||
if config.log.lower() != "none":
|
||||
logging.basicConfig(level=loglevel, format='%(message)s', datefmt='%H:%M:%S')
|
||||
|
||||
reporters = {
|
||||
'yaml': YAMLReporter,
|
||||
'json': JSONReporter,
|
||||
'plain': PlainReporter
|
||||
}
|
||||
|
||||
if config.report.lower() in reporters.keys():
|
||||
config.reporter = reporters[config.report.lower()]()
|
||||
else:
|
||||
logging.warning('Unknown reporter selected, using plain')
|
||||
config.reporter = reporters['plain']()
|
||||
|
||||
dispatchers = {
|
||||
'stdout': STDOUTDispatcher,
|
||||
'http': HTTPDispatcher
|
||||
}
|
||||
|
||||
if config.dispatch.lower() in dispatchers.keys():
|
||||
config.dispatcher = dispatchers[config.dispatch.lower()]()
|
||||
else:
|
||||
logging.warning('Unknown dispatcher selected, using stdout')
|
||||
config.dispatcher = dispatchers['stdout']()
|
||||
config.reporter = get_reporter(config.report)
|
||||
config.dispatcher = get_dispatcher(config.dispatch)
|
||||
|
||||
import kube_hunter
|
||||
|
||||
|
||||
def interactive_set_config():
|
||||
"""Sets config manually, returns True for success"""
|
||||
options = [("Remote scanning", "scans one or more specific IPs or DNS names"),
|
||||
("Interface scanning","scans subnets on all local network interfaces"),
|
||||
("IP range scanning","scans a given IP range")]
|
||||
options = [("Remote scanning",
|
||||
"scans one or more specific IPs or DNS names"),
|
||||
("Interface scanning",
|
||||
"scans subnets on all local network interfaces"),
|
||||
("IP range scanning", "scans a given IP range")]
|
||||
|
||||
print("Choose one of the options below:")
|
||||
for i, (option, explanation) in enumerate(options):
|
||||
print("{}. {} ({})".format(i+1, option.ljust(20), explanation))
|
||||
choice = input("Your choice: ")
|
||||
if choice == '1':
|
||||
config.remote = input("Remotes (separated by a ','): ").replace(' ', '').split(',')
|
||||
config.remote = input("Remotes (separated by a ','): ").\
|
||||
replace(' ', '').split(',')
|
||||
elif choice == '2':
|
||||
config.interface = True
|
||||
elif choice == '3':
|
||||
config.cidr = input("CIDR (example - 192.168.1.0/24): ").replace(' ', '')
|
||||
config.cidr = input("CIDR (example - 192.168.1.0/24): ").\
|
||||
replace(' ', '')
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
@@ -83,6 +57,7 @@ global hunt_started_lock
|
||||
hunt_started_lock = threading.Lock()
|
||||
hunt_started = False
|
||||
|
||||
|
||||
def main():
|
||||
global hunt_started
|
||||
scan_options = [
|
||||
@@ -127,4 +102,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
from argparse import ArgumentParser
|
||||
import logging
|
||||
from kube_hunter.conf.parser import parse_args
|
||||
|
||||
parser = ArgumentParser(description='Kube-Hunter - hunts for security weaknesses in Kubernetes clusters')
|
||||
parser.add_argument('--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 of all network interfaces")
|
||||
parser.add_argument('--pod', action="store_true", help="set hunter as an insider pod")
|
||||
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")
|
||||
parser.add_argument('--cidr', type=str, help="set an ip range to scan, example: 192.168.0.0/16")
|
||||
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: debug, info, warn, none")
|
||||
parser.add_argument('--report', type=str, default='plain', help="set report type, options are: plain, yaml, json")
|
||||
parser.add_argument('--dispatch', type=str, default='stdout', help="where to send the report to, options are: stdout, http (set KUBEHUNTER_HTTP_DISPATCH_URL and KUBEHUNTER_HTTP_DISPATCH_METHOD environment variables to configure)")
|
||||
parser.add_argument('--statistics', action="store_true", help="set hunting statistics")
|
||||
config = parse_args()
|
||||
|
||||
loglevel = getattr(logging, config.log.upper(), None)
|
||||
|
||||
if not loglevel:
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(message)s',
|
||||
datefmt='%H:%M:%S')
|
||||
logging.warning('Unknown log level selected, using info')
|
||||
elif config.log.lower() != "none":
|
||||
logging.basicConfig(level=loglevel,
|
||||
format='%(message)s',
|
||||
datefmt='%H:%M:%S')
|
||||
|
||||
import plugins
|
||||
|
||||
config = parser.parse_args()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
from argparse import ArgumentParser
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = ArgumentParser(
|
||||
description='Kube-Hunter - hunts for security '
|
||||
'weaknesses in Kubernetes clusters')
|
||||
|
||||
parser.add_argument(
|
||||
'--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")
|
||||
|
||||
parser.add_argument(
|
||||
'--pod',
|
||||
action="store_true",
|
||||
help="Set hunter as an insider pod")
|
||||
|
||||
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")
|
||||
|
||||
parser.add_argument(
|
||||
'--cidr',
|
||||
type=str,
|
||||
help="Set an ip range to scan, example: 192.168.0.0/16")
|
||||
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: debug, info, warn, none")
|
||||
|
||||
parser.add_argument(
|
||||
'--report',
|
||||
type=str,
|
||||
default='plain',
|
||||
help="Set report type, options are: plain, yaml, json")
|
||||
|
||||
parser.add_argument(
|
||||
'--dispatch',
|
||||
type=str,
|
||||
default='stdout',
|
||||
help="Where to send the report to, options are: "
|
||||
"stdout, http (set KUBEHUNTER_HTTP_DISPATCH_URL and "
|
||||
"KUBEHUNTER_HTTP_DISPATCH_METHOD environment variables to configure)")
|
||||
|
||||
parser.add_argument(
|
||||
'--statistics',
|
||||
action="store_true",
|
||||
help="Show hunting statistics")
|
||||
|
||||
return parser.parse_args()
|
||||
@@ -1,7 +1 @@
|
||||
from os.path import dirname, basename, isfile
|
||||
import glob
|
||||
|
||||
# dynamically importing all modules in folder
|
||||
files = glob.glob(dirname(__file__)+"/*.py")
|
||||
for module_name in (basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')):
|
||||
exec('from .{} import *'.format(module_name))
|
||||
from kube_hunter.modules.report.factory import get_reporter, get_dispatcher
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from kube_hunter.conf import config
|
||||
from kube_hunter.core.types import Discovery
|
||||
from kube_hunter.modules.report.collector import services, vulnerabilities, hunters, services_lock, vulnerabilities_lock
|
||||
from kube_hunter.modules.report.collector import services, vulnerabilities, \
|
||||
hunters, services_lock, vulnerabilities_lock
|
||||
|
||||
|
||||
class BaseReporter(object):
|
||||
@@ -11,29 +11,36 @@ class BaseReporter(object):
|
||||
for service in services:
|
||||
node_location = str(service.host)
|
||||
if node_location not in node_locations:
|
||||
nodes.append({"type": "Node/Master", "location": str(service.host)})
|
||||
nodes.append({
|
||||
"type": "Node/Master",
|
||||
"location": str(service.host)
|
||||
})
|
||||
node_locations.add(node_location)
|
||||
return nodes
|
||||
|
||||
def get_services(self):
|
||||
with services_lock:
|
||||
services_data = [{"service": service.get_name(),
|
||||
"location": "{}:{}{}".format(service.host, service.port, service.get_path()),
|
||||
"description": service.explain()}
|
||||
for service in services]
|
||||
services_data = [{
|
||||
"service": service.get_name(),
|
||||
"location": f"{service.host}:"
|
||||
f"{service.port}"
|
||||
f"{service.get_path()}",
|
||||
"description": service.explain()
|
||||
} for service in services]
|
||||
return services_data
|
||||
|
||||
def get_vulnerabilities(self):
|
||||
with vulnerabilities_lock:
|
||||
vulnerabilities_data = [{"location": vuln.location(),
|
||||
"vid": vuln.get_vid(),
|
||||
"category": vuln.category.name,
|
||||
"severity": vuln.get_severity(),
|
||||
"vulnerability": vuln.get_name(),
|
||||
"description": vuln.explain(),
|
||||
"evidence": str(vuln.evidence),
|
||||
"hunter": vuln.hunter.get_name()}
|
||||
for vuln in vulnerabilities]
|
||||
vulnerabilities_data = [{
|
||||
"location": vuln.location(),
|
||||
"vid": vuln.get_vid(),
|
||||
"category": vuln.category.name,
|
||||
"severity": vuln.get_severity(),
|
||||
"vulnerability": vuln.get_name(),
|
||||
"description": vuln.explain(),
|
||||
"evidence": str(vuln.evidence),
|
||||
"hunter": vuln.hunter.get_name()
|
||||
} for vuln in vulnerabilities]
|
||||
return vulnerabilities_data
|
||||
|
||||
def get_hunter_statistics(self):
|
||||
@@ -41,17 +48,21 @@ class BaseReporter(object):
|
||||
for hunter, docs in hunters.items():
|
||||
if not Discovery in hunter.__mro__:
|
||||
name, doc = hunter.parse_docs(docs)
|
||||
hunters_data.append({"name": name, "description": doc, "vulnerabilities": hunter.publishedVulnerabilities})
|
||||
hunters_data.append({
|
||||
"name": name,
|
||||
"description": doc,
|
||||
"vulnerabilities": hunter.publishedVulnerabilities
|
||||
})
|
||||
return hunters_data
|
||||
|
||||
def get_report(self):
|
||||
def get_report(self, *, statistics, **kwargs):
|
||||
report = {
|
||||
"nodes": self.get_nodes(),
|
||||
"services": self.get_services(),
|
||||
"vulnerabilities": self.get_vulnerabilities()
|
||||
}
|
||||
|
||||
if config.statistics:
|
||||
if statistics:
|
||||
report["hunter_statistics"] = self.get_hunter_statistics()
|
||||
|
||||
report["kburl"] = "https://aquasecurity.github.io/kube-hunter/kb/{vid}"
|
||||
|
||||
@@ -79,7 +79,7 @@ class SendFullReport(object):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
report = config.reporter.get_report()
|
||||
report = config.reporter.get_report(statistics=config.statistics, mapping=config.mapping)
|
||||
config.dispatcher.dispatch(report)
|
||||
handler.publish_event(ReportDispatched())
|
||||
handler.publish_event(TablesPrinted())
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
from kube_hunter.modules.report.json import JSONReporter
|
||||
from kube_hunter.modules.report.yaml import YAMLReporter
|
||||
from kube_hunter.modules.report.plain import PlainReporter
|
||||
from kube_hunter.modules.report.dispatchers import \
|
||||
STDOUTDispatcher, HTTPDispatcher
|
||||
|
||||
import logging
|
||||
|
||||
reporters = {
|
||||
'yaml': YAMLReporter,
|
||||
'json': JSONReporter,
|
||||
'plain': PlainReporter
|
||||
}
|
||||
|
||||
dispatchers = {
|
||||
'stdout': STDOUTDispatcher,
|
||||
'http': HTTPDispatcher
|
||||
}
|
||||
|
||||
|
||||
def get_reporter(name):
|
||||
try:
|
||||
return reporters[name.lower()]
|
||||
except KeyError:
|
||||
logging.warning('Unknown reporter selected, using plain')
|
||||
return reporters['plain']()
|
||||
|
||||
|
||||
def get_dispatcher(name):
|
||||
try:
|
||||
return dispatchers[name.lower()]
|
||||
except KeyError:
|
||||
logging.warning('Unknown dispatcher selected, using stdout')
|
||||
return dispatchers['stdout']()
|
||||
@@ -3,6 +3,6 @@ from kube_hunter.modules.report.base import BaseReporter
|
||||
|
||||
|
||||
class JSONReporter(BaseReporter):
|
||||
def get_report(self):
|
||||
report = super().get_report()
|
||||
def get_report(self, **kwargs):
|
||||
report = super().get_report(**kwargs)
|
||||
return json.dumps(report)
|
||||
|
||||
@@ -2,9 +2,9 @@ from __future__ import print_function
|
||||
|
||||
from prettytable import ALL, PrettyTable
|
||||
|
||||
from kube_hunter.conf import config
|
||||
from kube_hunter.modules.report.base import BaseReporter
|
||||
from kube_hunter.modules.report.collector import services, vulnerabilities, hunters, services_lock, vulnerabilities_lock
|
||||
from kube_hunter.modules.report.collector import services, vulnerabilities, \
|
||||
hunters, services_lock, vulnerabilities_lock
|
||||
|
||||
EVIDENCE_PREVIEW = 40
|
||||
MAX_TABLE_WIDTH = 20
|
||||
@@ -13,7 +13,7 @@ KB_LINK = "https://github.com/aquasecurity/kube-hunter/tree/master/docs/_kb"
|
||||
|
||||
class PlainReporter(BaseReporter):
|
||||
|
||||
def get_report(self):
|
||||
def get_report(self, *, statistics=None, mapping=None, **kwargs):
|
||||
"""generates report tables"""
|
||||
output = ""
|
||||
|
||||
@@ -27,13 +27,13 @@ class PlainReporter(BaseReporter):
|
||||
|
||||
if services_len:
|
||||
output += self.nodes_table()
|
||||
if not config.mapping:
|
||||
if not mapping:
|
||||
output += self.services_table()
|
||||
if vulnerabilities_len:
|
||||
output += self.vulns_table()
|
||||
else:
|
||||
output += "\nNo vulnerabilities were found"
|
||||
if config.statistics:
|
||||
if statistics:
|
||||
if hunters_len:
|
||||
output += self.hunters_table()
|
||||
else:
|
||||
@@ -73,12 +73,20 @@ class PlainReporter(BaseReporter):
|
||||
services_table.header_style = "upper"
|
||||
with services_lock:
|
||||
for service in services:
|
||||
services_table.add_row([service.get_name(), "{}:{}{}".format(service.host, service.port, service.get_path()), service.explain()])
|
||||
detected_services_ret = "\nDetected Services\n{}\n".format(services_table)
|
||||
services_table.add_row(
|
||||
[service.get_name(),
|
||||
f"{service.host}:"
|
||||
f"{service.port}"
|
||||
f"{service.get_path()}",
|
||||
service.explain()])
|
||||
detected_services_ret = "\nDetected Services\n" \
|
||||
f"{services_table}\n"
|
||||
return detected_services_ret
|
||||
|
||||
def vulns_table(self):
|
||||
column_names = ["ID", "Location", "Category", "Vulnerability", "Description", "Evidence"]
|
||||
column_names = ["ID", "Location",
|
||||
"Category", "Vulnerability",
|
||||
"Description", "Evidence"]
|
||||
vuln_table = PrettyTable(column_names, hrules=ALL)
|
||||
vuln_table.align = "l"
|
||||
vuln_table.max_width = MAX_TABLE_WIDTH
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
from io import StringIO
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from kube_hunter.conf import config
|
||||
from kube_hunter.modules.report.base import BaseReporter
|
||||
|
||||
|
||||
class YAMLReporter(BaseReporter):
|
||||
def get_report(self):
|
||||
report = super().get_report()
|
||||
def get_report(self, **kwargs):
|
||||
report = super().get_report(**kwargs)
|
||||
output = StringIO()
|
||||
yaml = YAML()
|
||||
yaml.dump(report, output)
|
||||
|
||||
Reference in New Issue
Block a user