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)