Cleanup conf refactor (#310)

Reorganize config files, and argparse.
Resolves #289
Resolves #292
This commit is contained in:
mormamn
2020-02-25 12:29:18 +02:00
committed by GitHub
parent e75c0ff37b
commit a8128b7ea0
10 changed files with 197 additions and 96 deletions
+14 -39
View File
@@ -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()
+15 -17
View File
@@ -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()
+82
View File
@@ -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
View File
@@ -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
+30 -19
View File
@@ -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}"
+1 -1
View File
@@ -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())
+34
View File
@@ -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']()
+2 -2
View File
@@ -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)
+16 -8
View File
@@ -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
+2 -3
View File
@@ -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)