From e3af42cbcefba02c76e77a05f256f48970bdbbed Mon Sep 17 00:00:00 2001 From: Tom Davidson Date: Mon, 12 Aug 2019 11:28:31 +0100 Subject: [PATCH] Separate report "sending" into modules (#156) * moved report output into dispatchers, stdout by default with config option of http(s) * notes in arg config on how to configure http dispatcher * removed some debug log visibility indicators * missing import * env vars more descriptive: KUBEHUNTER_HTTP_DISPATCH_METHOD and KUBEHUNTER_HTTP_DISPATCH_URL * optimisation: delayed instantiation of the dispatcher until after selection to avoid instantiating unnecessarily * refactor: config selection as per reporter selection * bugfix: fall-back to default required if unknown reporter or dispatcher specified * swapping urllib3 for requests * corrected visibility levels for logging * moving dispatchers into a file in reporters rather than it's own place to fit with theme and support dynamic module loading --- kube-hunter.py | 27 ++++++++++++---- src/core/events/types/common.py | 4 +++ src/modules/report/collector.py | 8 ++--- src/modules/report/dispatchers.py | 52 +++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 src/modules/report/dispatchers.py diff --git a/kube-hunter.py b/kube-hunter.py index 8be5cfa..d55f813 100755 --- a/kube-hunter.py +++ b/kube-hunter.py @@ -15,6 +15,7 @@ parser.add_argument('--remote', nargs='+', metavar="HOST", default=list(), help= 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 (use KUBEHUNTER_HTTP_DISPATCH_URL and KUBEHUNTER_HTTP_DISPATCH_METHOD to configure)") parser.add_argument('--statistics', action="store_true", help="set hunting statistics") import plugins @@ -31,13 +32,27 @@ if config.log.lower() != "none": from src.modules.report.plain import PlainReporter from src.modules.report.yaml import YAMLReporter from src.modules.report.json_reporter import JSONReporter - -if config.report.lower() == "yaml": - config.reporter = YAMLReporter() -elif config.report.lower() == "json": - config.reporter = JSONReporter() +reporters = { + 'yaml': YAMLReporter, + 'json': JSONReporter, + 'plain': PlainReporter +} +if config.report.lower() in reporters.keys(): + config.reporter = reporters[config.report.lower()]() else: - config.reporter = PlainReporter() + logging.warning('Unknown reporter selected, using plain') + config.reporter = reporters['plain']() + +from src.modules.report.dispatchers import STDOUTDispatcher, HTTPDispatcher +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']() from src.core.events import handler from src.core.events.types import HuntFinished, HuntStarted diff --git a/src/core/events/types/common.py b/src/core/events/types/common.py index 4da785d..1819cc1 100644 --- a/src/core/events/types/common.py +++ b/src/core/events/types/common.py @@ -145,3 +145,7 @@ class HuntFinished(Event): class HuntStarted(Event): pass + + +class ReportDispatched(Event): + pass diff --git a/src/modules/report/collector.py b/src/modules/report/collector.py index 06f5e50..b9e4afb 100644 --- a/src/modules/report/collector.py +++ b/src/modules/report/collector.py @@ -2,7 +2,7 @@ import logging from __main__ import config from src.core.events import handler -from src.core.events.types import Event, Service, Vulnerability, HuntFinished, HuntStarted +from src.core.events.types import Event, Service, Vulnerability, HuntFinished, HuntStarted, ReportDispatched import threading @@ -82,10 +82,8 @@ class SendFullReport(object): def execute(self): report = config.reporter.get_report() - if config.report == "plain": - logging.info("\n{div}\n{report}".format(div="-" * 10, report=report)) - else: - print(report) + config.dispatcher.dispatch(report) + handler.publish_event(ReportDispatched()) handler.publish_event(TablesPrinted()) diff --git a/src/modules/report/dispatchers.py b/src/modules/report/dispatchers.py new file mode 100644 index 0000000..66af6ee --- /dev/null +++ b/src/modules/report/dispatchers.py @@ -0,0 +1,52 @@ +import logging +import os +import requests +from __main__ import config + + +class HTTPDispatcher(object): + def dispatch(self, report): + logging.info('Dispatching report via http') + dispatchMethod = os.environ.get( + 'KUBEHUNTER_HTTP_DISPATCH_METHOD', + 'POST' + ).upper() + dispatchURL = os.environ.get( + 'KUBEHUNTER_HTTP_DISPATCH_URL', + 'https://localhost/' + ) + logging.info( + 'Dispatching report via {method} to {url}'.format( + method=dispatchMethod, + url=dispatchURL + ) + ) + try: + r = requests.request( + dispatchMethod, + dispatchURL, + json=report, + headers={'Content-Type': 'application/json'} + ) + r.raise_for_status() + logging.info( + "\tResponse Code: {status}\n\tResponse Data:\n{data}".format( + status=r.status_code, + data=r.text + ) + ) + except requests.HTTPError as e: + logging.error( + "Dispatcher failed to deliver\n\tResponse Code: {status}\n\tResponse Data:\n{data}".format( + status=r.status_code, + data=r.text + ) + ) + +class STDOUTDispatcher(object): + def dispatch(self, report): + logging.info('Dispatching report via stdout') + if config.report == "plain": + logging.info("\n{div}\n{report}".format(div="-" * 10, report=report)) + else: + print(report)