diff --git a/groups/group9_gdpr b/groups/group9_gdpr index 7df4c6ec..19698d6d 100644 --- a/groups/group9_gdpr +++ b/groups/group9_gdpr @@ -15,7 +15,7 @@ GROUP_ID[9]='gdpr' GROUP_NUMBER[9]='9.0' GROUP_TITLE[9]='GDPR Readiness - ONLY AS REFERENCE - [gdpr] ********************' GROUP_RUN_BY_DEFAULT[9]='N' # run it when execute_all is called -GROUP_CHECKS[9]='extra718,extra725,extra727,check12,check113,check114,extra71,extra731,extra732,extra733,check25,check39,check21,check22,check23,check24,check26,check27,check35,extra726,extra714,extra715,extra717,extra719,extra720,extra721,extra722,check43,check25,extra714,extra729,extra734,extra735,extra736,extra738,extra740' +GROUP_CHECKS[9]='extra718,extra725,extra727,check12,check113,check114,extra71,extra731,extra732,extra733,check25,check39,check21,check22,check23,check24,check26,check27,check35,extra726,extra714,extra715,extra717,extra719,extra720,extra721,extra722,check43,check25,extra714,extra729,extra734,extra735,extra736,extra738,extra739,extra740' # Resources: # https://d1.awsstatic.com/whitepapers/compliance/GDPR_Compliance_on_AWS.pdf diff --git a/integrations/wazuh/0570-prowler_rules.xml b/integrations/wazuh/0570-prowler_rules.xml new file mode 100644 index 00000000..980e3d95 --- /dev/null +++ b/integrations/wazuh/0570-prowler_rules.xml @@ -0,0 +1,45 @@ + + + + + + prowler + Prowler Check Result: $(prowler.status) - Control $(prowler.control_id) + + + + 90001 + Pass + Prowler Check Result: $(prowler.status) - Control $(prowler.control_id) + + + + 90001 + Info + Prowler Check Result: $(prowler.status) - Control $(prowler.control_id) + + + + 90001 + Error + Prowler Check Result: $(prowler.status) - Control $(prowler.control_id) + + + + 90001 + Fail + Prowler Check Result: $(prowler.status) - Control $(prowler.control_id) + + + + 90005 + Not Scored + Prowler Check Result: $(prowler.status) - Control $(prowler.control_id) + + diff --git a/integrations/wazuh/prowler-wrapper.py b/integrations/wazuh/prowler-wrapper.py new file mode 100644 index 00000000..d518fbc7 --- /dev/null +++ b/integrations/wazuh/prowler-wrapper.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python +# +# Authored by Jeremy Phillips +# Copyright: Apache License 2.0 +# +# Wrapper around prowler script to parse results and forward to Wazuh +# Prowler - https://github.com/toniblyx/prowler +# +# TODO: Add ability to disable different groups (EXTRA, etc... +# TODO: Allow to disable individual checks +# TODO: Remove all the commented out stuff +# +# Error Codes: +# 1 - Unknown +# 2 - SIGINT +# 3 - Error output from execution of Prowler +# 4 - Output row is invalid json +# 5 - Wazuh must be running +# 6 - Error sending to socket + + +import signal +import sys +import socket +import argparse +import subprocess +import json +from datetime import datetime +import os +import re + +################################################################################ +# Constants +################################################################################ +WAZUH_PATH = open('/etc/ossec-init.conf').readline().split('"')[1] +DEBUG_LEVEL = 1 # Enable/disable debug mode +PATH_TO_PROWLER = '{0}/integrations/prowler'.format(WAZUH_PATH) # No trailing slash +TEMPLATE_CHECK = ''' +{{ + "integration": "prowler", + "prowler": {0} +}} +''' +TEMPLATE_MSG = '1:Wazuh-Prowler:{0}' +TEMPLATE_ERROR = '''{{ + "aws_account_id": {aws_account_id}, + "aws_profile": "{aws_profile}", + "prowler_error": "{prowler_error}", + "prowler_version": "{prowler_version}", + "timestamp": "{timestamp}", + "status": "Error" +}} +''' +WAZUH_QUEUE = '{0}/queue/ossec/queue'.format(WAZUH_PATH) +FIELD_REMAP = { + "Profile": "aws_profile", + "Control": "control", + "Account Number": "aws_account_id", + "Level": "level", + "Account Alias": "aws_account_alias", + "Timestamp": "timestamp", + "Region": "region", + "Control ID": "control_id", + "Status": "status", + "Scored": "scored", + "Message": "message" +} +CHECKS_FILES_TO_IGNORE = [ + 'check_sample' +] + + +################################################################################ +# Functions +################################################################################ +def _send_msg(msg): + try: + _json_msg = json.dumps(_reformat_msg(msg)) + _debug("Sending Msg: {0}".format(_json_msg), 3) + _socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + _socket.connect(WAZUH_QUEUE) + _socket.send(TEMPLATE_MSG.format(_json_msg).encode()) + _socket.close() + except socket.error as e: + if e.errno == 111: + print('ERROR: Wazuh must be running.') + sys.exit(5) + else: + print("ERROR: Error sending message to wazuh: {}".format(e)) + sys.exit(6) + except Exception as e: + print("ERROR: Error sending message to wazuh: {}".format(e)) + sys.exit(6) + return + + +def _handler(signal, frame): + print("ERROR: SIGINT received.") + sys.exit(12) + + +def _debug(msg, msg_level): + if DEBUG_LEVEL >= msg_level: + print('DEBUG-{level}: {debug_msg}'.format(level=msg_level, debug_msg=msg)) + + +def _get_script_arguments(): + _parser = argparse.ArgumentParser(usage="usage: %(prog)s [options]", + description="Wazuh wodle for evaluating AWS security configuration", + formatter_class=argparse.RawTextHelpFormatter) + _parser.add_argument('-c', '--aws_account_id', dest='aws_account_id', + help='AWS Account ID for logs', + required=False) + _parser.add_argument('-d', '--debug', action='store', dest='debug', default=0, help='Enable debug') + _parser.add_argument('-p', '--aws_profile', dest='aws_profile', help='The name of credential profile to use', + default=None) + _parser.add_argument('-n', '--aws_account_alias', dest='aws_account_alias', + help='AWS Account ID Alias', default='') + _parser.add_argument('-e', '--skip_on_error', action='store_false', dest='skip_on_error', + help='If check output is invalid json, error out instead of skipping the check', default=True) + return _parser.parse_args() + + +def _run_prowler(prowler_args): + _debug('Running prowler with args: {0}'.format(prowler_args), 1) + _prowler_command = '{prowler}/prowler {args}'.format(prowler=PATH_TO_PROWLER, args=prowler_args) + _debug('Running command: {0}'.format(_prowler_command), 2) + _process = subprocess.Popen(_prowler_command, stdout=subprocess.PIPE, shell=True) + _output, _error = _process.communicate() + _debug('Raw prowler output: {0}'.format(_output), 3) + _debug('Raw prowler error: {0}'.format(_error), 3) + if _error is not None: + _debug('PROWLER ERROR: {0}'.format(_error), 1) + exit(3) + return _output + + +def _get_prowler_version(options): + _debug('+++ Get Prowler Version', 1) + # Execute prowler, but only display the version and immediately exit + return _run_prowler('-p {0} -V'.format(options.aws_profile)).rstrip() + + +def _get_prowler_results(options, prowler_check): + _debug('+++ Get Prowler Results - {check}'.format(check=prowler_check), 1) + # Execute prowler with all checks + # -b = disable banner + # -p = credential profile + # -M = output json + + return _run_prowler('-b -c {check} -p {aws_profile} -M json'.format(check=prowler_check, + aws_profile=options.aws_profile)) + +def _get_prowler_checks(): + _prowler_checks = [] + for _directory_path, _directories, _files in os.walk('{path}/checks'.format(path=PATH_TO_PROWLER)): + _debug('Checking in : {}'.format(_directory_path), 3) + for _file in _files: + if _file in CHECKS_FILES_TO_IGNORE: + _debug('Ignoring check - {}'.format(_directory_path, _file), 3) + elif re.match("check\d+", _file): + _prowler_checks.append(_file) + elif re.match("check_extra(\d+)", _file): + _prowler_checks.append(_file[6:]) + else: + _debug('Unknown check file type- {}'.format(_directory_path, _file), 3) + return _prowler_checks + + +def _send_prowler_results(prowler_results, _prowler_version, options): + _debug('+++ Send Prowler Results', 1) + for _check_result in prowler_results.splitlines(): + # Empty row + if len(_check_result) < 1: + continue + # Something failed during prowler check + elif _check_result[:17] == 'An error occurred': + _debug('ERROR MSG --- {0}'.format(_check_result), 2) + _temp_msg = TEMPLATE_ERROR.format( + aws_account_id=options.aws_account_id, + aws_profile=options.aws_profile, + prowler_error=_check_result.replace('"', '\"'), + prowler_version=_prowler_version, + timestamp=datetime.now().isoformat() + ) + _error_msg = json.loads(TEMPLATE_CHECK.format(_temp_msg)) + _send_msg(_error_msg) + continue + try: + _debug('RESULT MSG --- {0}'.format(_check_result), 2) + _check_result = json.loads(TEMPLATE_CHECK.format(_check_result)) + except: + _debug('INVALID JSON --- {0}'.format(TEMPLATE_CHECK.format(_check_result)), 1) + if not options.skip_on_error: + exit(4) + _check_result['prowler']['prowler_version'] = _prowler_version + _check_result['prowler']['aws_account_alias'] = options.aws_account_alias + _send_msg(_check_result) + + return True + + +def _reformat_msg(msg): + for field in FIELD_REMAP: + if field in msg['prowler']: + msg['prowler'][FIELD_REMAP[field]] = msg['prowler'][field] + del msg['prowler'][field] + return msg + + +# Main +############################################################################### +def main(argv): + _debug('+++ Begin script', 1) + # Parse arguments + _options = _get_script_arguments() + + if int(_options.debug) > 0: + global DEBUG_LEVEL + DEBUG_LEVEL = int(_options.debug) + _debug('+++ Debug mode on - Level: {debug}'.format(debug=_options.debug), 1) + + _prowler_version = _get_prowler_version(_options) + _prowler_checks = _get_prowler_checks() + for _check in _prowler_checks: + _prowler_results = _get_prowler_results(_options, _check) + _send_prowler_results(_prowler_results, _prowler_version, _options) + _debug('+++ Finished script', 1) + return + + +if __name__ == '__main__': + try: + _debug('Args: {args}'.format(args=str(sys.argv)), 2) + signal.signal(signal.SIGINT, _handler) + main(sys.argv[1:]) + sys.exit(0) + except Exception as e: + print("Unknown error: {}".format(e)) + if DEBUG_LEVEL > 0: + raise + sys.exit(1)