diff --git a/Dockerfile b/Dockerfile index a16352bf..486d767d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,10 +12,11 @@ LABEL maintainer="https://github.com/jonrau1" \ description="Continuously monitor your AWS services for configurations that can lead to degradation of confidentiality, integrity or availability. All results will be sent to Security Hub for further aggregation and analysis." COPY requirements.txt /tmp/requirements.txt -COPY audit_controller.py . +# NOTE: this will copy current auditors to container along with the required controller files +COPY ./eeauditor/ ./ RUN pip3 install -r /tmp/requirements.txt CMD \ aws s3 cp s3://${SH_SCRIPTS_BUCKET}/ ./auditors --recursive && \ - python audit_controller.py \ No newline at end of file + python controller.py \ No newline at end of file diff --git a/ElectricEye b/ElectricEye deleted file mode 160000 index 52e54f05..00000000 --- a/ElectricEye +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 52e54f0511a34f3c13e7c197e72a2b8316cba81e diff --git a/audit_controller.py b/audit_controller.py deleted file mode 100644 index 9f558637..00000000 --- a/audit_controller.py +++ /dev/null @@ -1,141 +0,0 @@ -# This file is part of ElectricEye. - -# ElectricEye is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# ElectricEye is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License along with ElectricEye. -# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. - -import csv -import json -import getopt -import importlib -import os -import sys -import boto3 -from time import sleep -from functools import reduce -from auditors.Auditor import Auditor, AuditorCollection - - -# Return nested dictionary values by passing in dictionary and keys separated by "." -def deep_get(dictionary, keys): - return reduce( - lambda d, key: d.get(key) if isinstance(d, dict) else None, - keys.split("."), - dictionary, - ) - - -def csv_output(output_file, findings): - csv_columns = [ - {"name": "Id", "path": "Id"}, - {"name": "Title", "path": "Title"}, - {"name": "ProductArn", "path": "ProductArn"}, - {"name": "AwsAccountId", "path": "AwsAccountId"}, - {"name": "Severity", "path": "Severity.Label"}, - {"name": "Confidence", "path": "Confidence"}, - {"name": "Description", "path": "Description"}, - {"name": "RecordState", "path": "RecordState"}, - {"name": "Compliance Status", "path": "Compliance.Status"}, - { - "name": "Remediation Recommendation", - "path": "Remediation.Recommendation.Text", - }, - { - "name": "Remediation Recommendation Link", - "path": "Remediation.Recommendation.Url", - }, - ] - csv_file = output_file - try: - with open(csv_file, "w") as csvfile: - writer = csv.writer(csvfile, dialect="excel") - writer.writerow(item["name"] for item in csv_columns) - for finding in findings: - row_data = [] - for column_dict in csv_columns: - row_data.append(deep_get(finding, column_dict["path"])) - writer.writerow(row_data) - except IOError: - print("I/O error") - - -def main(argv): - findings_list = [] # used if --output is specified - profile_name = "" - auditor_name = "" - check_name = "" - output = False - output_file = "" - help_text = "audit_controller.py [-p -a -c -o ]" - try: - opts, args = getopt.getopt( - argv, "ho:p:a:c:", ["help", "output=", "profile=", "auditor=", "check="] - ) - except getopt.GetoptError: - print(help_text) - sys.exit(2) - for opt, arg in opts: - if opt in ("-h", "--help"): - print(help_text) - sys.exit(2) - if opt in ("-o", "--output"): - output = True - output_file = arg - if opt in ("-p", "--profile"): - profile_name = arg - if opt in ("-a", "--auditor"): - auditor_name = arg - if opt in ("-c", "--check"): - check_name = arg - if profile_name: - boto3.setup_default_session(profile_name=profile_name) - - # load all Auditor plugins in the "auditors" directory - auditors = AuditorCollection("auditors") - securityhub = boto3.client("securityhub") - - for plugin in auditors.plugins: - try: - # if user specifies a specific auditor on CLI, skip all other auditors - if auditor_name: - if "auditors." + auditor_name != plugin.get("auditor"): - continue - check = plugin.get("check") - # if user specifies a specific check on CLI, skip all other checks - if check_name: - if check.name != check_name: - continue - print(f"Executing check: {check.name}") - for finding in check.execute(): - # It would be possible to collect these findings and batch them up before sending. - # This current implementation has the advantage of a small memory footprint, but - # could be a slight performance improvement to batch and make one securityhub - # call per check. - response = securityhub.batch_import_findings(Findings=[finding]) - # if -o arg, add finding to findings list to be used to generate csv output - if output: - findings_list.append(finding) - sleep(0.5) # a hack to avoid api limit by sleeping between checks - except Exception as e: - print(f"Error running plugin {plugin.get('check').name} with exception {e}") - if output: - print(f"Writing {len(findings_list)} findings to {output_file}") - csv_output(output_file, findings_list) - - print("Done") - - -if __name__ == "__main__": - # this is for local testing where the AWS_REGION is not liekly set - if not os.environ.get("AWS_REGION", None): - os.environ["AWS_REGION"] = "us-east-1" - main(sys.argv[1:]) diff --git a/auditors/AWS_Lambda_Auditor.py b/auditors/AWS_Lambda_Auditor.py deleted file mode 100644 index 7672e46b..00000000 --- a/auditors/AWS_Lambda_Auditor.py +++ /dev/null @@ -1,169 +0,0 @@ -# This file is part of ElectricEye. - -# ElectricEye is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# ElectricEye is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License along with ElectricEye. -# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. - -import boto3 -import datetime -import os -from dateutil import parser -from auditors.Auditor import Auditor - -lambda_client = boto3.client("lambda") -cloudwatch = boto3.client("cloudwatch") -sts = boto3.client("sts") - - -class FunctionUnusedCheck(Auditor): - def execute(self): - response = lambda_client.list_functions() - functions = response["Functions"] - # create env vars - awsAccountId = sts.get_caller_identity()["Account"] - awsRegion = os.environ["AWS_REGION"] - for function in functions: - functionName = str(function["FunctionName"]) - lambdaArn = str(function["FunctionArn"]) - metricResponse = cloudwatch.get_metric_data( - MetricDataQueries=[ - { - "Id": "m1", - "MetricStat": { - "Metric": { - "Namespace": "AWS/Lambda", - "MetricName": "Invocations", - "Dimensions": [{"Name": "FunctionName", "Value": functionName},], - }, - "Period": 300, - "Stat": "Sum", - }, - } - ], - StartTime=datetime.datetime.now() - datetime.timedelta(days=30), - EndTime=datetime.datetime.now(), - ) - metrics = metricResponse["MetricDataResults"] - for metric in metrics: - modify_date = parser.parse(function["LastModified"]) - date_delta = datetime.datetime.now(datetime.timezone.utc) - modify_date - iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() - if len(metric["Values"]) > 0 or date_delta.days < 30: - finding = { - "SchemaVersion": "2018-10-08", - "Id": lambdaArn + "/lambda-function-unused-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": lambdaArn, - "AwsAccountId": awsAccountId, - "Types": ["Software and Configuration Checks/AWS Security Best Practices"], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[Lambda.1] Lambda functions should be deleted after 30 days of no use", - "Description": "Lambda function " - + functionName - + " has been used or updated in the last 30 days.", - "Remediation": { - "Recommendation": { - "Text": "For more information on best practices for lambda functions refer to the Best Practices for Working with AWS Lambda Functions section of the Amazon Lambda Developer Guide", - "Url": "https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-configuration", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsLambda", - "Id": lambdaArn, - "Partition": "aws", - "Region": awsRegion, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF ID.AM-2", - "NIST SP 800-53 CM-8", - "NIST SP 800-53 PM-5", - "AICPA TSC CC3.2", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.1.1", - "ISO 27001:2013 A.8.1.2", - "ISO 27001:2013 A.12.5.1", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": lambdaArn + "/lambda-function-unused-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": lambdaArn, - "AwsAccountId": awsAccountId, - "Types": ["Software and Configuration Checks/AWS Security Best Practices"], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "LOW"}, - "Confidence": 99, - "Title": "[Lambda.1] Lambda functions should be deleted after 30 days of no use", - "Description": "Lambda function " - + functionName - + " has not been used or updated in the last 30 days.", - "Remediation": { - "Recommendation": { - "Text": "For more information on best practices for lambda functions refer to the Best Practices for Working with AWS Lambda Functions section of the Amazon Lambda Developer Guide", - "Url": "https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-configuration", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsLambda", - "Id": lambdaArn, - "Partition": "aws", - "Region": awsRegion, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF ID.AM-2", - "NIST SP 800-53 CM-8", - "NIST SP 800-53 PM-5", - "AICPA TSC CC3.2", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.1.1", - "ISO 27001:2013 A.8.1.2", - "ISO 27001:2013 A.12.5.1", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding diff --git a/auditors/Amazon_APIGW_Auditor.py b/auditors/Amazon_APIGW_Auditor.py deleted file mode 100644 index dff090e0..00000000 --- a/auditors/Amazon_APIGW_Auditor.py +++ /dev/null @@ -1,1082 +0,0 @@ -# This file is part of ElectricEye. - -# ElectricEye is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# ElectricEye is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License along with ElectricEye. -# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. - -import boto3 -import datetime -import os -from auditors.Auditor import Auditor - -# import boto3 clients -securityhub = boto3.client("securityhub") -apigateway = boto3.client("apigateway") -sts = boto3.client("sts") - -# create account id & region variables -awsAccountId = sts.get_caller_identity()["Account"] -awsRegion = os.environ["AWS_REGION"] -# loop through API Gateway rest apis -response = apigateway.get_rest_apis(limit=500) -myRestApis = response["items"] - - -class ApiGatewayStageMetricsEnabledCheck(Auditor): - def execute(self): - for restapi in myRestApis: - apiGwApiId = str(restapi["id"]) - apiGwApiName = str(restapi["name"]) - response = apigateway.get_stages(restApiId=apiGwApiId) - for apistages in response["item"]: - apiStageName = str(apistages["stageName"]) - apiStageDeploymentId = str(apistages["deploymentId"]) - apiStageArn = ( - "arn:aws:apigateway:" - + awsRegion - + "::/restapis/" - + apiGwApiId - + "/stages/" - + apiStageName - ) - # is is possible methodSettings is empty indicating metrics are not enabled - try: - metricsCheck = str(apistages["methodSettings"]["*/*"]["metricsEnabled"]) - except KeyError: - metricsCheck = "False" - if metricsCheck == "False": - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-metrics-enabled-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "LOW"}, - "Confidence": 99, - "Title": "[APIGateway.1] API Gateway Rest API Stages should have CloudWatch Metrics enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " does not have CloudWatch metrics enabled. You can monitor API execution by using CloudWatch, which collects and processes raw data from API Gateway into readable, near-real-time metrics. These statistics are recorded for a period of 15 months so you can access historical information and gain a better perspective on how your web application or service is performing. Refer to the remediation instructions if this configuration is not intended", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have CloudWatch Metrics enabled refer to the Monitor API Execution with Amazon CloudWatch section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/monitoring-cloudwatch.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF DE.AE-3", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 IR-5", - "NIST SP 800-53 IR-8", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.7", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - except Exception as e: - print(e) - else: - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-metrics-enabled-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[APIGateway.1] API Gateway Rest API Stages should have CloudWatch Metrics enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " has CloudWatch metrics enabled.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have CloudWatch Metrics enabled refer to the Monitor API Execution with Amazon CloudWatch section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/monitoring-cloudwatch.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF DE.AE-3", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 IR-5", - "NIST SP 800-53 IR-8", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.7", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except Exception as e: - print(e) - - -class ApiGatewayStageLoggingCheck(Auditor): - def execute(self): - for restapi in myRestApis: - apiGwApiId = str(restapi["id"]) - apiGwApiName = str(restapi["name"]) - response = apigateway.get_stages(restApiId=apiGwApiId) - for apistages in response["item"]: - apiStageName = str(apistages["stageName"]) - apiStageDeploymentId = str(apistages["deploymentId"]) - apiStageArn = ( - "arn:aws:apigateway:" - + awsRegion - + "::/restapis/" - + apiGwApiId - + "/stages/" - + apiStageName - ) - # it is possible for methodSettings to be empty indicating logging is Off - try: - loggingCheck = str(apistages["methodSettings"]["*/*"]["loggingLevel"]) - except KeyError: - loggingCheck = "OFF" - if loggingCheck == "OFF": - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-api-logging-enabled-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "LOW"}, - "Confidence": 99, - "Title": "[APIGateway.2] API Gateway Rest API Stages should have CloudWatch API Logging enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " does not have CloudWatch API Logging enabled. To help debug issues related to request execution or client access to your API, you can enable Amazon CloudWatch Logs to log API calls. The logged data includes errors or execution traces (such as request or response parameter values or payloads), data used by Lambda authorizers (formerly known as custom authorizers), whether API keys are required, whether usage plans are enabled, and so on. Refer to the remediation instructions if this configuration is not intended.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have CloudWatch API Logging enabled refer to the Set Up CloudWatch API Logging in API Gateway section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF DE.AE-3", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 IR-5", - "NIST SP 800-53 IR-8", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.7", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - except Exception as e: - print(e) - else: - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-api-logging-enabled-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[APIGateway.2] API Gateway Rest API Stages should have CloudWatch API Logging enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " has CloudWatch API Logging enabled.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have CloudWatch API Logging enabled refer to the Set Up CloudWatch API Logging in API Gateway section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF DE.AE-3", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 IR-5", - "NIST SP 800-53 IR-8", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.7", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except Exception as e: - print(e) - - -class ApiGatewayStageCacheingEnabledCheck(Auditor): - def execute(self): - for restapi in myRestApis: - apiGwApiId = str(restapi["id"]) - apiGwApiName = str(restapi["name"]) - response = apigateway.get_stages(restApiId=apiGwApiId) - for apistages in response["item"]: - apiStageName = str(apistages["stageName"]) - apiStageDeploymentId = str(apistages["deploymentId"]) - apiStageArn = ( - "arn:aws:apigateway:" - + awsRegion - + "::/restapis/" - + apiGwApiId - + "/stages/" - + apiStageName - ) - # it is possible for methodSettings to be empty which indicated caching is not enabled - try: - cachingCheck = str(apistages["methodSettings"]["*/*"]["cachingEnabled"]) - except KeyError: - cachingCheck = "False" - if cachingCheck == "False": - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-caching-enabled-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "LOW"}, - "Confidence": 99, - "Title": "[APIGateway.3] API Gateway Rest API Stages should have Caching enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " does not have Caching enabled. You can enable API caching in Amazon API Gateway to cache your endpoints responses. With caching, you can reduce the number of calls made to your endpoint and also improve the latency of requests to your API. Refer to the remediation instructions if this configuration is not intended", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have caching enabled refer to the Enable API Caching to Enhance Responsiveness section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF ID.BE-5", - "NIST CSF PR.PT-5", - "NIST SP 800-53 CP-2", - "NIST SP 800-53 CP-11", - "NIST SP 800-53 SA-13", - "NIST SP 800-53 SA14", - "AICPA TSC CC3.1", - "AICPA TSC A1.2", - "ISO 27001:2013 A.11.1.4", - "ISO 27001:2013 A.17.1.1", - "ISO 27001:2013 A.17.1.2", - "ISO 27001:2013 A.17.2.1", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - except Exception as e: - print(e) - else: - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-caching-enabled-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[APIGateway.3] API Gateway Rest API Stages should have Caching enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " has Caching enabled.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have caching enabled refer to the Enable API Caching to Enhance Responsiveness section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF ID.BE-5", - "NIST CSF PR.PT-5", - "NIST SP 800-53 CP-2", - "NIST SP 800-53 CP-11", - "NIST SP 800-53 SA-13", - "NIST SP 800-53 SA14", - "AICPA TSC CC3.1", - "AICPA TSC A1.2", - "ISO 27001:2013 A.11.1.4", - "ISO 27001:2013 A.17.1.1", - "ISO 27001:2013 A.17.1.2", - "ISO 27001:2013 A.17.2.1", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except Exception as e: - print(e) - - -class ApiGatewayStageCacheEncryptionCheck(Auditor): - def execute(self): - for restapi in myRestApis: - apiGwApiId = str(restapi["id"]) - apiGwApiName = str(restapi["name"]) - response = apigateway.get_stages(restApiId=apiGwApiId) - for apistages in response["item"]: - apiStageName = str(apistages["stageName"]) - apiStageDeploymentId = str(apistages["deploymentId"]) - apiStageArn = ( - "arn:aws:apigateway:" - + awsRegion - + "::/restapis/" - + apiGwApiId - + "/stages/" - + apiStageName - ) - try: - cachingEncryptionCheck = str( - apistages["methodSettings"]["*/*"]["cacheDataEncrypted"] - ) - except KeyError: - cachingEncryptionCheck = "False" - if cachingEncryptionCheck == "False": - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-cache-encryption-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "HIGH"}, - "Confidence": 99, - "Title": "[APIGateway.4] API Gateway Rest API Stages should have cache encryption enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " does not have cache encryption enabled. If you choose to enable caching for a REST API, you can enable cache encryption. Refer to the remediation instructions if this configuration is not intended", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have caching encryption enabled refer to the Override API Gateway Stage-Level Caching for Method Caching section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html#override-api-gateway-stage-cache-for-method-cache", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF PR.DS-1", - "NIST SP 800-53 MP-8", - "NIST SP 800-53 SC-12", - "NIST SP 800-53 SC-28", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - except Exception as e: - print(e) - else: - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-cache-encryption-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[APIGateway.4] API Gateway Rest API Stages should have cache encryption enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " has cache encryption enabled.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have caching encryption enabled refer to the Override API Gateway Stage-Level Caching for Method Caching section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html#override-api-gateway-stage-cache-for-method-cache", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF PR.DS-1", - "NIST SP 800-53 MP-8", - "NIST SP 800-53 SC-12", - "NIST SP 800-53 SC-28", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except Exception as e: - print(e) - - -class ApiGatewayStageXrayTracingCheck(Auditor): - def execute(self): - for restapi in myRestApis: - apiGwApiId = str(restapi["id"]) - apiGwApiName = str(restapi["name"]) - response = apigateway.get_stages(restApiId=apiGwApiId) - for apistages in response["item"]: - apiStageName = str(apistages["stageName"]) - apiStageDeploymentId = str(apistages["deploymentId"]) - apiStageArn = ( - "arn:aws:apigateway:" - + awsRegion - + "::/restapis/" - + apiGwApiId - + "/stages/" - + apiStageName - ) - xrayTracingCheck = str(apistages["tracingEnabled"]) - if xrayTracingCheck == "False": - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-xray-tracing-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "LOW"}, - "Confidence": 99, - "Title": "[APIGateway.5] API Gateway Rest API Stages should have tracing enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " does not have tracing enabled. Because X-Ray gives you an end-to-end view of an entire request, you can analyze latencies in your APIs and their backend services. You can use an X-Ray service map to view the latency of an entire request and that of the downstream services that are integrated with X-Ray. Refer to the remediation instructions if this configuration is not intended", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have tracing enabled refer to the Set Up X-Ray Tracing in API Gateway section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-set-up-tracing.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF DE.AE-3", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 IR-5", - "NIST SP 800-53 IR-8", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.7", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - except Exception as e: - print(e) - else: - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-xray-tracing-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices" - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[APIGateway.5] API Gateway Rest API Stages should have tracing enabled", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " has tracing enabled.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should have tracing enabled refer to the Set Up X-Ray Tracing in API Gateway section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-set-up-tracing.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF DE.AE-3", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 IR-5", - "NIST SP 800-53 IR-8", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.7", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except Exception as e: - print(e) - - -class ApiGatewayStageWafCheckCheck(Auditor): - def execute(self): - for restapi in myRestApis: - apiGwApiId = str(restapi["id"]) - apiGwApiName = str(restapi["name"]) - response = apigateway.get_stages(restApiId=apiGwApiId) - for apistages in response["item"]: - apiStageName = str(apistages["stageName"]) - apiStageDeploymentId = str(apistages["deploymentId"]) - apiStageArn = ( - "arn:aws:apigateway:" - + awsRegion - + "::/restapis/" - + apiGwApiId - + "/stages/" - + apiStageName - ) - try: - wafCheck = str(apistages["webAclArn"]) - # this is a passing check - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-waf-protection-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[APIGateway.6] API Gateway Rest API Stages should be protected by an AWS WAF Web ACL", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " is protected by an AWS WAF Web ACL.", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should be protected by WAF refer to the Set Up AWS WAF in API Gateway section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-setup-waf.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF DE.AE-2", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.1", - "ISO 27001:2013 A.16.1.4", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except Exception as e: - print(e) - except Exception as e: - if str(e) == "'webAclArn'": - try: - # ISO Time - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - finding = { - "SchemaVersion": "2018-10-08", - "Id": apiStageArn + "/apigateway-stage-waf-protection-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": apiStageArn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "HIGH"}, - "Confidence": 99, - "Title": "[APIGateway.6] API Gateway Rest API Stages should be protected by an AWS WAF Web ACL", - "Description": "API Gateway stage " - + apiStageName - + " for Rest API " - + apiGwApiName - + " is not protected by an AWS WAF Web ACL. You can use AWS WAF to protect your API Gateway API from common web exploits, such as SQL injection and cross-site scripting (XSS) attacks. These could affect API availability and performance, compromise security, or consume excessive resources. Refer to the remediation instructions if this configuration is not intended", - "Remediation": { - "Recommendation": { - "Text": "If your API Gateway stage should be protected by WAF refer to the Set Up AWS WAF in API Gateway section of the Amazon API Gateway Developer Guide", - "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-setup-waf.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsApiGatewayRestApi", - "Id": apiStageArn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "Other": { - "deploymentId": apiStageDeploymentId, - "stageName": apiStageName, - } - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF DE.AE-2", - "NIST SP 800-53 AU-6", - "NIST SP 800-53 CA-7", - "NIST SP 800-53 IR-4", - "NIST SP 800-53 SI-4", - "AICPA TSC CC7.2", - "ISO 27001:2013 A.12.4.1", - "ISO 27001:2013 A.16.1.1", - "ISO 27001:2013 A.16.1.4", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - except Exception as e: - print(e) - else: - print(e) diff --git a/auditors/Amazon_SNS_Auditor.py b/auditors/Amazon_SNS_Auditor.py deleted file mode 100644 index 297a4bbc..00000000 --- a/auditors/Amazon_SNS_Auditor.py +++ /dev/null @@ -1,618 +0,0 @@ -# This file is part of ElectricEye. - -# ElectricEye is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# ElectricEye is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License along with ElectricEye. -# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. - -import datetime -import json -import os -import boto3 -from auditors.Auditor import Auditor - -# import boto3 clients -sns = boto3.client("sns") -sts = boto3.client("sts") -# create region variable -awsRegion = os.environ["AWS_REGION"] - - -class SNSTopicEncryptionCheck(Auditor): - def execute(self): - awsAccountId = sts.get_caller_identity()["Account"] - # loop through SNS topics - response = sns.list_topics() - mySnsTopics = response["Topics"] - for topic in mySnsTopics: - topicarn = str(topic["TopicArn"]) - topicName = topicarn.replace( - "arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "" - ) - response = sns.get_topic_attributes(TopicArn=topicarn) - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - try: - # this is a passing check - encryptionCheck = str(response["Attributes"]["KmsMasterKeyId"]) - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-topic-encryption-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[SNS.1] SNS topics should be encrypted", - "Description": "SNS topic " + topicName + " is encrypted.", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS encryption at rest and how to configure it refer to the Encryption at Rest section of the Amazon Simple Notification Service Developer Guide.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": {"AwsSnsTopic": {"TopicName": topicName}}, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF PR.DS-1", - "NIST SP 800-53 MP-8", - "NIST SP 800-53 SC-12", - "NIST SP 800-53 SC-28", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - except: - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-topic-encryption-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "HIGH"}, - "Confidence": 99, - "Title": "[SNS.1] SNS topics should be encrypted", - "Description": "SNS topic " - + topicName - + " is not encrypted. Refer to the remediation instructions to remediate this behavior", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS encryption at rest and how to configure it refer to the Encryption at Rest section of the Amazon Simple Notification Service Developer Guide.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": {"AwsSnsTopic": {"TopicName": topicName}}, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF PR.DS-1", - "NIST SP 800-53 MP-8", - "NIST SP 800-53 SC-12", - "NIST SP 800-53 SC-28", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - - -class SNSHTTPEncryptionCheck(Auditor): - def execute(self): - awsAccountId = sts.get_caller_identity()["Account"] - # loop through SNS topics - response = sns.list_topics() - mySnsTopics = response["Topics"] - for topic in mySnsTopics: - topicarn = str(topic["TopicArn"]) - topicName = topicarn.replace( - "arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "" - ) - response = sns.list_subscriptions_by_topic(TopicArn=topicarn) - mySubs = response["Subscriptions"] - for subscriptions in mySubs: - subProtocol = str(subscriptions["Protocol"]) - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - if subProtocol == "http": - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-http-subscription-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "HIGH"}, - "Confidence": 99, - "Title": "[SNS.2] SNS topics should not use HTTP subscriptions", - "Description": "SNS topic " - + topicName - + " has a HTTP subscriber. Refer to the remediation instructions to remediate this behavior", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS encryption in transit refer to the Enforce Encryption of Data in Transit section of the Amazon Simple Notification Service Developer Guide.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": {"AwsSnsTopic": {"TopicName": topicName}}, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF ID.AM-2", - "NIST SP 800-53 CM-8", - "NIST SP 800-53 PM-5", - "AICPA TSC CC3.2", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.1.1", - "ISO 27001:2013 A.8.1.2", - "ISO 27001:2013 A.12.5.1", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-http-subscription-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[SNS.2] SNS topics should not use HTTP subscriptions", - "Description": "SNS topic " - + topicName - + " does not have a HTTP subscriber.", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS encryption in transit refer to the Enforce Encryption of Data in Transit section of the Amazon Simple Notification Service Developer Guide.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": {"AwsSnsTopic": {"TopicName": topicName}}, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF ID.AM-2", - "NIST SP 800-53 CM-8", - "NIST SP 800-53 PM-5", - "AICPA TSC CC3.2", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.1.1", - "ISO 27001:2013 A.8.1.2", - "ISO 27001:2013 A.12.5.1", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - - -class SNSPublicAccessCheck(Auditor): - def execute(self): - awsAccountId = sts.get_caller_identity()["Account"] - # loop through SNS topics - response = sns.list_topics() - mySnsTopics = response["Topics"] - for topic in mySnsTopics: - topicarn = str(topic["TopicArn"]) - topicName = topicarn.replace( - "arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "" - ) - response = sns.get_topic_attributes(TopicArn=topicarn) - statement_json = response["Attributes"]["Policy"] - statement = json.loads(statement_json) - fail = False - # this results in one finding per topic instead of one finding per statement - for sid in statement["Statement"]: - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - access = sid["Principal"].get("AWS", None) - if access != "*" or (access == "*" and "Condition" in sid): - continue - else: - fail = True - break - if not fail: - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-public-access-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 75, # The Condition may not effectively limit access - "Title": "[SNS.3] SNS topics should not have public access", - "Description": "SNS topic " - + topicName - + " does not have public access or limited by a Condition. Refer to the remediation instructions to review sns access policy", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS Access Policy Best Practices refer to Amazons Best Practice rules for Amazon SNS.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#ensure-topics-not-publicly-accessible", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": {"AwsSnsTopic": {"TopicName": topicName}}, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF PR.AC-3", - "NIST SP 800-53 AC-1", - "NIST SP 800-53 AC-17", - "NIST SP 800-53 AC-19", - "NIST SP 800-53 AC-20", - "NIST SP 800-53 SC-15", - "AICPA TSC CC6.6", - "ISO 27001:2013 A.6.2.1", - "ISO 27001:2013 A.6.2.2", - "ISO 27001:2013 A.11.2.6", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-public-access-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "HIGH"}, - "Confidence": 99, - "Title": "[SNS.3] SNS topics should not have public access", - "Description": "SNS topic " - + topicName - + " has public access. Refer to the remediation instructions to remediate this behavior", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS Access Policy Best Practices refer to Amazons Best Practice rules for Amazon SNS.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#ensure-topics-not-publicly-accessible", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": {"AwsSnsTopic": {"TopicName": topicName}}, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF PR.AC-3", - "NIST SP 800-53 AC-1", - "NIST SP 800-53 AC-17", - "NIST SP 800-53 AC-19", - "NIST SP 800-53 AC-20", - "NIST SP 800-53 SC-15", - "AICPA TSC CC6.6", - "ISO 27001:2013 A.6.2.1", - "ISO 27001:2013 A.6.2.2", - "ISO 27001:2013 A.11.2.6", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding - -class SNSCrossAccountCheck(Auditor): - def execute(self): - awsAccountId = sts.get_caller_identity()["Account"] - # loop through SNS topics - response = sns.list_topics() - mySnsTopics = response["Topics"] - for topic in mySnsTopics: - topicarn = str(topic["TopicArn"]) - topicName = topicarn.replace( - "arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "" - ) - response = sns.get_topic_attributes(TopicArn=topicarn) - myPolicy_json = str(response["Attributes"]["Policy"]) - myPolicy = json.loads(myPolicy_json) - fail = False - for statement in myPolicy["Statement"]: - iso8601Time = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat() - ) - principal = statement["Principal"].get("AWS", None) - if principal and principal != "*": - if not principal.isdigit(): - # This assumes if it is not a digit that it must be an arn. - # not sure if this is a safe assumption. - principal = principal.split(":")[4] - if principal == awsAccountId: - continue - else: - fail = True - break - if not fail: - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-cross-account-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[SNS.4] SNS topics should not allow cross-account access", - "Description": "SNS topic " - + topicName - + " does not have cross-account access.", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS best practices refer to the Amazon SNS security best practices section of the Amazon Simple Notification Service Developer Guide.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "AwsSnsTopic": {"TopicName": topicName} - }, - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF PR.AC-3", - "NIST SP 800-53 AC-1", - "NIST SP 800-53 AC-17", - "NIST SP 800-53 AC-19", - "NIST SP 800-53 AC-20", - "NIST SP 800-53 SC-15", - "AICPA TSC CC6.6", - "ISO 27001:2013 A.6.2.1", - "ISO 27001:2013 A.6.2.2", - "ISO 27001:2013 A.11.2.6", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - ], - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED", - } - yield finding - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": topicarn + "/sns-cross-account-check", - "ProductArn": "arn:aws:securityhub:" - + awsRegion - + ":" - + awsAccountId - + ":product/" - + awsAccountId - + "/default", - "GeneratorId": topicarn, - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "Low"}, - "Confidence": 99, - "Title": "[SNS.4] SNS topics should not allow cross-account access", - "Description": "SNS topic " - + topicName - + " has cross-account access.", - "Remediation": { - "Recommendation": { - "Text": "For more information on SNS best practices refer to the Amazon SNS security best practices section of the Amazon Simple Notification Service Developer Guide.", - "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", - } - }, - "ProductFields": {"Product Name": "ElectricEye"}, - "Resources": [ - { - "Type": "AwsSnsTopic", - "Id": topicarn, - "Partition": "aws", - "Region": awsRegion, - "Details": { - "AwsSnsTopic": {"TopicName": topicName} - }, - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF PR.AC-3", - "NIST SP 800-53 AC-1", - "NIST SP 800-53 AC-17", - "NIST SP 800-53 AC-19", - "NIST SP 800-53 AC-20", - "NIST SP 800-53 SC-15", - "AICPA TSC CC6.6", - "ISO 27001:2013 A.6.2.1", - "ISO 27001:2013 A.6.2.2", - "ISO 27001:2013 A.11.2.6", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - ], - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE", - } - yield finding diff --git a/auditors/__init__.py b/auditors/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/auditors/tests/__init__.py b/auditors/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/auditors/AMI_Auditor.py b/eeauditor/auditors/AMI_Auditor.py similarity index 100% rename from auditors/AMI_Auditor.py rename to eeauditor/auditors/AMI_Auditor.py diff --git a/auditors/AWS_AppMesh_Auditor.py b/eeauditor/auditors/AWS_AppMesh_Auditor.py similarity index 100% rename from auditors/AWS_AppMesh_Auditor.py rename to eeauditor/auditors/AWS_AppMesh_Auditor.py diff --git a/auditors/AWS_Backup_Auditor.py b/eeauditor/auditors/AWS_Backup_Auditor.py similarity index 100% rename from auditors/AWS_Backup_Auditor.py rename to eeauditor/auditors/AWS_Backup_Auditor.py diff --git a/auditors/AWS_CloudFormation_Auditor.py b/eeauditor/auditors/AWS_CloudFormation_Auditor.py similarity index 100% rename from auditors/AWS_CloudFormation_Auditor.py rename to eeauditor/auditors/AWS_CloudFormation_Auditor.py diff --git a/auditors/AWS_CloudTrail_Auditor.py b/eeauditor/auditors/AWS_CloudTrail_Auditor.py similarity index 100% rename from auditors/AWS_CloudTrail_Auditor.py rename to eeauditor/auditors/AWS_CloudTrail_Auditor.py diff --git a/auditors/AWS_CodeBuild_Auditor.py b/eeauditor/auditors/AWS_CodeBuild_Auditor.py similarity index 100% rename from auditors/AWS_CodeBuild_Auditor.py rename to eeauditor/auditors/AWS_CodeBuild_Auditor.py diff --git a/auditors/AWS_DMS_Auditor.py b/eeauditor/auditors/AWS_DMS_Auditor.py similarity index 100% rename from auditors/AWS_DMS_Auditor.py rename to eeauditor/auditors/AWS_DMS_Auditor.py diff --git a/auditors/AWS_Directory_Service_Auditor.py b/eeauditor/auditors/AWS_Directory_Service_Auditor.py similarity index 100% rename from auditors/AWS_Directory_Service_Auditor.py rename to eeauditor/auditors/AWS_Directory_Service_Auditor.py diff --git a/auditors/AWS_Glue_Auditor.py b/eeauditor/auditors/AWS_Glue_Auditor.py similarity index 100% rename from auditors/AWS_Glue_Auditor.py rename to eeauditor/auditors/AWS_Glue_Auditor.py diff --git a/auditors/AWS_IAM_Auditor.py b/eeauditor/auditors/AWS_IAM_Auditor.py similarity index 100% rename from auditors/AWS_IAM_Auditor.py rename to eeauditor/auditors/AWS_IAM_Auditor.py diff --git a/auditors/AWS_KMS_Auditor.py b/eeauditor/auditors/AWS_KMS_Auditor.py similarity index 100% rename from auditors/AWS_KMS_Auditor.py rename to eeauditor/auditors/AWS_KMS_Auditor.py diff --git a/auditors/AWS_License_Manager_Auditor.py b/eeauditor/auditors/AWS_License_Manager_Auditor.py similarity index 100% rename from auditors/AWS_License_Manager_Auditor.py rename to eeauditor/auditors/AWS_License_Manager_Auditor.py diff --git a/auditors/AWS_Secrets_Manager_Auditor.py b/eeauditor/auditors/AWS_Secrets_Manager_Auditor.py similarity index 100% rename from auditors/AWS_Secrets_Manager_Auditor.py rename to eeauditor/auditors/AWS_Secrets_Manager_Auditor.py diff --git a/auditors/AWS_Security_Hub_Auditor.py b/eeauditor/auditors/AWS_Security_Hub_Auditor.py similarity index 100% rename from auditors/AWS_Security_Hub_Auditor.py rename to eeauditor/auditors/AWS_Security_Hub_Auditor.py diff --git a/auditors/AWS_Security_Services_Auditor.py b/eeauditor/auditors/AWS_Security_Services_Auditor.py similarity index 100% rename from auditors/AWS_Security_Services_Auditor.py rename to eeauditor/auditors/AWS_Security_Services_Auditor.py diff --git a/auditors/Amazon_AppStream_Auditor.py b/eeauditor/auditors/Amazon_AppStream_Auditor.py similarity index 100% rename from auditors/Amazon_AppStream_Auditor.py rename to eeauditor/auditors/Amazon_AppStream_Auditor.py diff --git a/auditors/Amazon_CognitoIdP_Auditor.py b/eeauditor/auditors/Amazon_CognitoIdP_Auditor.py similarity index 100% rename from auditors/Amazon_CognitoIdP_Auditor.py rename to eeauditor/auditors/Amazon_CognitoIdP_Auditor.py diff --git a/auditors/Amazon_DocumentDB_Auditor.py b/eeauditor/auditors/Amazon_DocumentDB_Auditor.py similarity index 100% rename from auditors/Amazon_DocumentDB_Auditor.py rename to eeauditor/auditors/Amazon_DocumentDB_Auditor.py diff --git a/auditors/Amazon_EBS_Auditor.py b/eeauditor/auditors/Amazon_EBS_Auditor.py similarity index 100% rename from auditors/Amazon_EBS_Auditor.py rename to eeauditor/auditors/Amazon_EBS_Auditor.py diff --git a/auditors/Amazon_EC2_Auditor.py b/eeauditor/auditors/Amazon_EC2_Auditor.py similarity index 100% rename from auditors/Amazon_EC2_Auditor.py rename to eeauditor/auditors/Amazon_EC2_Auditor.py diff --git a/auditors/Amazon_EC2_SSM_Auditor.py b/eeauditor/auditors/Amazon_EC2_SSM_Auditor.py similarity index 100% rename from auditors/Amazon_EC2_SSM_Auditor.py rename to eeauditor/auditors/Amazon_EC2_SSM_Auditor.py diff --git a/auditors/Amazon_EC2_Security_Group_Auditor.py b/eeauditor/auditors/Amazon_EC2_Security_Group_Auditor.py similarity index 100% rename from auditors/Amazon_EC2_Security_Group_Auditor.py rename to eeauditor/auditors/Amazon_EC2_Security_Group_Auditor.py diff --git a/auditors/Amazon_ECR_Auditor.py b/eeauditor/auditors/Amazon_ECR_Auditor.py similarity index 100% rename from auditors/Amazon_ECR_Auditor.py rename to eeauditor/auditors/Amazon_ECR_Auditor.py diff --git a/auditors/Amazon_ECS_Auditor.py b/eeauditor/auditors/Amazon_ECS_Auditor.py similarity index 100% rename from auditors/Amazon_ECS_Auditor.py rename to eeauditor/auditors/Amazon_ECS_Auditor.py diff --git a/auditors/Amazon_EFS_Auditor.py b/eeauditor/auditors/Amazon_EFS_Auditor.py similarity index 100% rename from auditors/Amazon_EFS_Auditor.py rename to eeauditor/auditors/Amazon_EFS_Auditor.py diff --git a/auditors/Amazon_EKS_Auditor.py b/eeauditor/auditors/Amazon_EKS_Auditor.py similarity index 100% rename from auditors/Amazon_EKS_Auditor.py rename to eeauditor/auditors/Amazon_EKS_Auditor.py diff --git a/auditors/Amazon_ELB_Auditor.py b/eeauditor/auditors/Amazon_ELB_Auditor.py similarity index 100% rename from auditors/Amazon_ELB_Auditor.py rename to eeauditor/auditors/Amazon_ELB_Auditor.py diff --git a/auditors/Amazon_ELBv2_Auditor.py b/eeauditor/auditors/Amazon_ELBv2_Auditor.py similarity index 100% rename from auditors/Amazon_ELBv2_Auditor.py rename to eeauditor/auditors/Amazon_ELBv2_Auditor.py diff --git a/auditors/Amazon_EMR_Auditor.py b/eeauditor/auditors/Amazon_EMR_Auditor.py similarity index 100% rename from auditors/Amazon_EMR_Auditor.py rename to eeauditor/auditors/Amazon_EMR_Auditor.py diff --git a/auditors/Amazon_Elasticache_Redis_Auditor.py b/eeauditor/auditors/Amazon_Elasticache_Redis_Auditor.py similarity index 100% rename from auditors/Amazon_Elasticache_Redis_Auditor.py rename to eeauditor/auditors/Amazon_Elasticache_Redis_Auditor.py diff --git a/auditors/Amazon_ElasticsearchService_Auditor.py b/eeauditor/auditors/Amazon_ElasticsearchService_Auditor.py similarity index 100% rename from auditors/Amazon_ElasticsearchService_Auditor.py rename to eeauditor/auditors/Amazon_ElasticsearchService_Auditor.py diff --git a/auditors/Amazon_Kinesis_Data_Streams_Auditor.py b/eeauditor/auditors/Amazon_Kinesis_Data_Streams_Auditor.py similarity index 100% rename from auditors/Amazon_Kinesis_Data_Streams_Auditor.py rename to eeauditor/auditors/Amazon_Kinesis_Data_Streams_Auditor.py diff --git a/auditors/Amazon_Kinesis_Firehose_Auditor.py b/eeauditor/auditors/Amazon_Kinesis_Firehose_Auditor.py similarity index 100% rename from auditors/Amazon_Kinesis_Firehose_Auditor.py rename to eeauditor/auditors/Amazon_Kinesis_Firehose_Auditor.py diff --git a/auditors/Amazon_MQ_Auditor.py b/eeauditor/auditors/Amazon_MQ_Auditor.py similarity index 100% rename from auditors/Amazon_MQ_Auditor.py rename to eeauditor/auditors/Amazon_MQ_Auditor.py diff --git a/auditors/Amazon_MSK_Auditor.py b/eeauditor/auditors/Amazon_MSK_Auditor.py similarity index 100% rename from auditors/Amazon_MSK_Auditor.py rename to eeauditor/auditors/Amazon_MSK_Auditor.py diff --git a/auditors/Amazon_Managed_Blockchain_Auditor.py b/eeauditor/auditors/Amazon_Managed_Blockchain_Auditor.py similarity index 100% rename from auditors/Amazon_Managed_Blockchain_Auditor.py rename to eeauditor/auditors/Amazon_Managed_Blockchain_Auditor.py diff --git a/auditors/Amazon_Neptune_Auditor.py b/eeauditor/auditors/Amazon_Neptune_Auditor.py similarity index 100% rename from auditors/Amazon_Neptune_Auditor.py rename to eeauditor/auditors/Amazon_Neptune_Auditor.py diff --git a/auditors/Amazon_RDS_Auditor.py b/eeauditor/auditors/Amazon_RDS_Auditor.py similarity index 100% rename from auditors/Amazon_RDS_Auditor.py rename to eeauditor/auditors/Amazon_RDS_Auditor.py diff --git a/auditors/Amazon_Redshift_Auditor.py b/eeauditor/auditors/Amazon_Redshift_Auditor.py similarity index 100% rename from auditors/Amazon_Redshift_Auditor.py rename to eeauditor/auditors/Amazon_Redshift_Auditor.py diff --git a/auditors/Amazon_S3_Auditor.py b/eeauditor/auditors/Amazon_S3_Auditor.py similarity index 100% rename from auditors/Amazon_S3_Auditor.py rename to eeauditor/auditors/Amazon_S3_Auditor.py diff --git a/auditors/Amazon_SageMaker_Auditor.py b/eeauditor/auditors/Amazon_SageMaker_Auditor.py similarity index 100% rename from auditors/Amazon_SageMaker_Auditor.py rename to eeauditor/auditors/Amazon_SageMaker_Auditor.py diff --git a/auditors/Amazon_Shield_Advanced_Auditor.py b/eeauditor/auditors/Amazon_Shield_Advanced_Auditor.py similarity index 100% rename from auditors/Amazon_Shield_Advanced_Auditor.py rename to eeauditor/auditors/Amazon_Shield_Advanced_Auditor.py diff --git a/auditors/Amazon_VPC_Auditor.py b/eeauditor/auditors/Amazon_VPC_Auditor.py similarity index 100% rename from auditors/Amazon_VPC_Auditor.py rename to eeauditor/auditors/Amazon_VPC_Auditor.py diff --git a/auditors/Amazon_WorkSpaces_Auditor.py b/eeauditor/auditors/Amazon_WorkSpaces_Auditor.py similarity index 100% rename from auditors/Amazon_WorkSpaces_Auditor.py rename to eeauditor/auditors/Amazon_WorkSpaces_Auditor.py diff --git a/auditors/Auditor.py b/eeauditor/auditors/Auditor.py similarity index 100% rename from auditors/Auditor.py rename to eeauditor/auditors/Auditor.py diff --git a/auditors/Shodan_Auditor.py b/eeauditor/auditors/Shodan_Auditor.py similarity index 100% rename from auditors/Shodan_Auditor.py rename to eeauditor/auditors/Shodan_Auditor.py diff --git a/eeauditor/auditors/aws/AWS_Lambda_Auditor.py b/eeauditor/auditors/aws/AWS_Lambda_Auditor.py new file mode 100644 index 00000000..315a158d --- /dev/null +++ b/eeauditor/auditors/aws/AWS_Lambda_Auditor.py @@ -0,0 +1,166 @@ +# This file is part of ElectricEye. + +# ElectricEye is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# ElectricEye is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with ElectricEye. +# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. + +import boto3 +import datetime +from dateutil import parser +from check_register import CheckRegister + +registry = CheckRegister() +lambda_client = boto3.client("lambda") +cloudwatch = boto3.client("cloudwatch") + + +@registry.register_check("lambda") +def unused_function_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + response = lambda_client.list_functions() + functions = response["Functions"] + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + # create env vars + for function in functions: + functionName = str(function["FunctionName"]) + lambdaArn = str(function["FunctionArn"]) + metricResponse = cloudwatch.get_metric_data( + MetricDataQueries=[ + { + "Id": "m1", + "MetricStat": { + "Metric": { + "Namespace": "AWS/Lambda", + "MetricName": "Invocations", + "Dimensions": [{"Name": "FunctionName", "Value": functionName},], + }, + "Period": 300, + "Stat": "Sum", + }, + } + ], + StartTime=datetime.datetime.now() - datetime.timedelta(days=30), + EndTime=datetime.datetime.now(), + ) + metrics = metricResponse["MetricDataResults"] + for metric in metrics: + modify_date = parser.parse(function["LastModified"]) + date_delta = datetime.datetime.now(datetime.timezone.utc) - modify_date + if len(metric["Values"]) > 0 or date_delta.days < 30: + finding = { + "SchemaVersion": "2018-10-08", + "Id": lambdaArn + "/lambda-function-unused-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": lambdaArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[Lambda.1] Lambda functions should be deleted after 30 days of no use", + "Description": "Lambda function " + + functionName + + " has been used or updated in the last 30 days.", + "Remediation": { + "Recommendation": { + "Text": "For more information on best practices for lambda functions refer to the Best Practices for Working with AWS Lambda Functions section of the Amazon Lambda Developer Guide", + "Url": "https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-configuration", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsLambda", + "Id": lambdaArn, + "Partition": "aws", + "Region": awsRegion, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF ID.AM-2", + "NIST SP 800-53 CM-8", + "NIST SP 800-53 PM-5", + "AICPA TSC CC3.2", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.1.1", + "ISO 27001:2013 A.8.1.2", + "ISO 27001:2013 A.12.5.1", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": lambdaArn + "/lambda-function-unused-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": lambdaArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "LOW"}, + "Confidence": 99, + "Title": "[Lambda.1] Lambda functions should be deleted after 30 days of no use", + "Description": "Lambda function " + + functionName + + " has not been used or updated in the last 30 days.", + "Remediation": { + "Recommendation": { + "Text": "For more information on best practices for lambda functions refer to the Best Practices for Working with AWS Lambda Functions section of the Amazon Lambda Developer Guide", + "Url": "https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-configuration", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsLambda", + "Id": lambdaArn, + "Partition": "aws", + "Region": awsRegion, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF ID.AM-2", + "NIST SP 800-53 CM-8", + "NIST SP 800-53 PM-5", + "AICPA TSC CC3.2", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.1.1", + "ISO 27001:2013 A.8.1.2", + "ISO 27001:2013 A.12.5.1", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding diff --git a/eeauditor/auditors/aws/Amazon_APIGW_Auditor.py b/eeauditor/auditors/aws/Amazon_APIGW_Auditor.py new file mode 100644 index 00000000..4cbd8065 --- /dev/null +++ b/eeauditor/auditors/aws/Amazon_APIGW_Auditor.py @@ -0,0 +1,1006 @@ +# This file is part of ElectricEye. + +# ElectricEye is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# ElectricEye is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with ElectricEye. +# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. + +import boto3 +import datetime +from check_register import CheckRegister + +registry = CheckRegister() + +# import boto3 clients +apigateway = boto3.client("apigateway") + + +def get_rest_apis(cache): + response = cache.get("get_rest_apis") + if response: + return response + cache["get_rest_apis"] = apigateway.get_rest_apis(limit=500) + return cache["get_rest_apis"] + + +@registry.register_check("apigateway") +def api_gateway_stage_metrics_enabled_check( + cache: dict, awsAccountId: str, awsRegion: str +) -> dict: + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for restapi in get_rest_apis(cache)["items"]: + apiGwApiId = str(restapi["id"]) + apiGwApiName = str(restapi["name"]) + response = apigateway.get_stages(restApiId=apiGwApiId) + for apistages in response["item"]: + apiStageName = str(apistages["stageName"]) + apiStageDeploymentId = str(apistages["deploymentId"]) + apiStageArn = ( + "arn:aws:apigateway:" + + awsRegion + + "::/restapis/" + + apiGwApiId + + "/stages/" + + apiStageName + ) + # is is possible methodSettings is empty indicating metrics are not enabled + try: + metricsCheck = str(apistages["methodSettings"]["*/*"]["metricsEnabled"]) + except KeyError: + metricsCheck = "False" + if metricsCheck == "False": + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-metrics-enabled-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "LOW"}, + "Confidence": 99, + "Title": "[APIGateway.1] API Gateway Rest API Stages should have CloudWatch Metrics enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " does not have CloudWatch metrics enabled. You can monitor API execution by using CloudWatch, which collects and processes raw data from API Gateway into readable, near-real-time metrics. These statistics are recorded for a period of 15 months so you can access historical information and gain a better perspective on how your web application or service is performing. Refer to the remediation instructions if this configuration is not intended", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have CloudWatch Metrics enabled refer to the Monitor API Execution with Amazon CloudWatch section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/monitoring-cloudwatch.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF DE.AE-3", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 IR-5", + "NIST SP 800-53 IR-8", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.7", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + except Exception as e: + print(e) + else: + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-metrics-enabled-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[APIGateway.1] API Gateway Rest API Stages should have CloudWatch Metrics enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " has CloudWatch metrics enabled.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have CloudWatch Metrics enabled refer to the Monitor API Execution with Amazon CloudWatch section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/monitoring-cloudwatch.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF DE.AE-3", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 IR-5", + "NIST SP 800-53 IR-8", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.7", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except Exception as e: + print(e) + + +@registry.register_check("apigateway") +def api_gateway_stage_logging_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for restapi in get_rest_apis(cache)["items"]: + apiGwApiId = str(restapi["id"]) + apiGwApiName = str(restapi["name"]) + response = apigateway.get_stages(restApiId=apiGwApiId) + for apistages in response["item"]: + apiStageName = str(apistages["stageName"]) + apiStageDeploymentId = str(apistages["deploymentId"]) + apiStageArn = ( + "arn:aws:apigateway:" + + awsRegion + + "::/restapis/" + + apiGwApiId + + "/stages/" + + apiStageName + ) + # it is possible for methodSettings to be empty indicating logging is Off + try: + loggingCheck = str(apistages["methodSettings"]["*/*"]["loggingLevel"]) + except KeyError: + loggingCheck = "OFF" + if loggingCheck == "OFF": + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-api-logging-enabled-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "LOW"}, + "Confidence": 99, + "Title": "[APIGateway.2] API Gateway Rest API Stages should have CloudWatch API Logging enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " does not have CloudWatch API Logging enabled. To help debug issues related to request execution or client access to your API, you can enable Amazon CloudWatch Logs to log API calls. The logged data includes errors or execution traces (such as request or response parameter values or payloads), data used by Lambda authorizers (formerly known as custom authorizers), whether API keys are required, whether usage plans are enabled, and so on. Refer to the remediation instructions if this configuration is not intended.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have CloudWatch API Logging enabled refer to the Set Up CloudWatch API Logging in API Gateway section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF DE.AE-3", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 IR-5", + "NIST SP 800-53 IR-8", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.7", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + except Exception as e: + print(e) + else: + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-api-logging-enabled-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[APIGateway.2] API Gateway Rest API Stages should have CloudWatch API Logging enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " has CloudWatch API Logging enabled.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have CloudWatch API Logging enabled refer to the Set Up CloudWatch API Logging in API Gateway section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF DE.AE-3", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 IR-5", + "NIST SP 800-53 IR-8", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.7", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except Exception as e: + print(e) + + +@registry.register_check("apigateway") +def api_gateway_stage_cacheing_enabled_check( + cache: dict, awsAccountId: str, awsRegion: str +) -> dict: + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for restapi in get_rest_apis(cache)["items"]: + apiGwApiId = str(restapi["id"]) + apiGwApiName = str(restapi["name"]) + response = apigateway.get_stages(restApiId=apiGwApiId) + for apistages in response["item"]: + apiStageName = str(apistages["stageName"]) + apiStageDeploymentId = str(apistages["deploymentId"]) + apiStageArn = ( + "arn:aws:apigateway:" + + awsRegion + + "::/restapis/" + + apiGwApiId + + "/stages/" + + apiStageName + ) + # it is possible for methodSettings to be empty which indicated caching is not enabled + try: + cachingCheck = str(apistages["methodSettings"]["*/*"]["cachingEnabled"]) + except KeyError: + cachingCheck = "False" + if cachingCheck == "False": + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-caching-enabled-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "LOW"}, + "Confidence": 99, + "Title": "[APIGateway.3] API Gateway Rest API Stages should have Caching enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " does not have Caching enabled. You can enable API caching in Amazon API Gateway to cache your endpoints responses. With caching, you can reduce the number of calls made to your endpoint and also improve the latency of requests to your API. Refer to the remediation instructions if this configuration is not intended", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have caching enabled refer to the Enable API Caching to Enhance Responsiveness section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF ID.BE-5", + "NIST CSF PR.PT-5", + "NIST SP 800-53 CP-2", + "NIST SP 800-53 CP-11", + "NIST SP 800-53 SA-13", + "NIST SP 800-53 SA14", + "AICPA TSC CC3.1", + "AICPA TSC A1.2", + "ISO 27001:2013 A.11.1.4", + "ISO 27001:2013 A.17.1.1", + "ISO 27001:2013 A.17.1.2", + "ISO 27001:2013 A.17.2.1", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + except Exception as e: + print(e) + else: + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-caching-enabled-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[APIGateway.3] API Gateway Rest API Stages should have Caching enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " has Caching enabled.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have caching enabled refer to the Enable API Caching to Enhance Responsiveness section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF ID.BE-5", + "NIST CSF PR.PT-5", + "NIST SP 800-53 CP-2", + "NIST SP 800-53 CP-11", + "NIST SP 800-53 SA-13", + "NIST SP 800-53 SA14", + "AICPA TSC CC3.1", + "AICPA TSC A1.2", + "ISO 27001:2013 A.11.1.4", + "ISO 27001:2013 A.17.1.1", + "ISO 27001:2013 A.17.1.2", + "ISO 27001:2013 A.17.2.1", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except Exception as e: + print(e) + + +@registry.register_check("apigateway") +def api_gateway_stage_cache_encryption_check( + cache: dict, awsAccountId: str, awsRegion: str +) -> dict: + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for restapi in get_rest_apis(cache)["items"]: + apiGwApiId = str(restapi["id"]) + apiGwApiName = str(restapi["name"]) + response = apigateway.get_stages(restApiId=apiGwApiId) + for apistages in response["item"]: + apiStageName = str(apistages["stageName"]) + apiStageDeploymentId = str(apistages["deploymentId"]) + apiStageArn = ( + "arn:aws:apigateway:" + + awsRegion + + "::/restapis/" + + apiGwApiId + + "/stages/" + + apiStageName + ) + try: + cachingEncryptionCheck = str( + apistages["methodSettings"]["*/*"]["cacheDataEncrypted"] + ) + except KeyError: + cachingEncryptionCheck = "False" + if cachingEncryptionCheck == "False": + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-cache-encryption-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "HIGH"}, + "Confidence": 99, + "Title": "[APIGateway.4] API Gateway Rest API Stages should have cache encryption enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " does not have cache encryption enabled. If you choose to enable caching for a REST API, you can enable cache encryption. Refer to the remediation instructions if this configuration is not intended", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have caching encryption enabled refer to the Override API Gateway Stage-Level Caching for Method Caching section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html#override-api-gateway-stage-cache-for-method-cache", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF PR.DS-1", + "NIST SP 800-53 MP-8", + "NIST SP 800-53 SC-12", + "NIST SP 800-53 SC-28", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + except Exception as e: + print(e) + else: + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-cache-encryption-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[APIGateway.4] API Gateway Rest API Stages should have cache encryption enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " has cache encryption enabled.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have caching encryption enabled refer to the Override API Gateway Stage-Level Caching for Method Caching section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html#override-api-gateway-stage-cache-for-method-cache", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF PR.DS-1", + "NIST SP 800-53 MP-8", + "NIST SP 800-53 SC-12", + "NIST SP 800-53 SC-28", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except Exception as e: + print(e) + + +@registry.register_check("apigateway") +def api_gateway_stage_xray_tracking_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for restapi in get_rest_apis(cache)["items"]: + apiGwApiId = str(restapi["id"]) + apiGwApiName = str(restapi["name"]) + response = apigateway.get_stages(restApiId=apiGwApiId) + for apistages in response["item"]: + apiStageName = str(apistages["stageName"]) + apiStageDeploymentId = str(apistages["deploymentId"]) + apiStageArn = ( + "arn:aws:apigateway:" + + awsRegion + + "::/restapis/" + + apiGwApiId + + "/stages/" + + apiStageName + ) + xrayTracingCheck = str(apistages["tracingEnabled"]) + if xrayTracingCheck == "False": + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-xray-tracing-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "LOW"}, + "Confidence": 99, + "Title": "[APIGateway.5] API Gateway Rest API Stages should have tracing enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " does not have tracing enabled. Because X-Ray gives you an end-to-end view of an entire request, you can analyze latencies in your APIs and their backend services. You can use an X-Ray service map to view the latency of an entire request and that of the downstream services that are integrated with X-Ray. Refer to the remediation instructions if this configuration is not intended", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have tracing enabled refer to the Set Up X-Ray Tracing in API Gateway section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-set-up-tracing.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF DE.AE-3", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 IR-5", + "NIST SP 800-53 IR-8", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.7", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + except Exception as e: + print(e) + else: + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-xray-tracing-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[APIGateway.5] API Gateway Rest API Stages should have tracing enabled", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " has tracing enabled.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should have tracing enabled refer to the Set Up X-Ray Tracing in API Gateway section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-set-up-tracing.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF DE.AE-3", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 IR-5", + "NIST SP 800-53 IR-8", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.7", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except Exception as e: + print(e) + + +@registry.register_check("apigateway") +def api_gateway_stage_waf_check_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for restapi in get_rest_apis(cache)["items"]: + apiGwApiId = str(restapi["id"]) + apiGwApiName = str(restapi["name"]) + response = apigateway.get_stages(restApiId=apiGwApiId) + for apistages in response["item"]: + apiStageName = str(apistages["stageName"]) + apiStageDeploymentId = str(apistages["deploymentId"]) + apiStageArn = ( + "arn:aws:apigateway:" + + awsRegion + + "::/restapis/" + + apiGwApiId + + "/stages/" + + apiStageName + ) + try: + wafCheck = str(apistages["webAclArn"]) + # this is a passing check + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-waf-protection-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[APIGateway.6] API Gateway Rest API Stages should be protected by an AWS WAF Web ACL", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " is protected by an AWS WAF Web ACL.", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should be protected by WAF refer to the Set Up AWS WAF in API Gateway section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-setup-waf.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF DE.AE-2", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.1", + "ISO 27001:2013 A.16.1.4", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except Exception as e: + print(e) + except Exception as e: + if str(e) == "'webAclArn'": + try: + finding = { + "SchemaVersion": "2018-10-08", + "Id": apiStageArn + "/apigateway-stage-waf-protection-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": apiStageArn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "HIGH"}, + "Confidence": 99, + "Title": "[APIGateway.6] API Gateway Rest API Stages should be protected by an AWS WAF Web ACL", + "Description": "API Gateway stage " + + apiStageName + + " for Rest API " + + apiGwApiName + + " is not protected by an AWS WAF Web ACL. You can use AWS WAF to protect your API Gateway API from common web exploits, such as SQL injection and cross-site scripting (XSS) attacks. These could affect API availability and performance, compromise security, or consume excessive resources. Refer to the remediation instructions if this configuration is not intended", + "Remediation": { + "Recommendation": { + "Text": "If your API Gateway stage should be protected by WAF refer to the Set Up AWS WAF in API Gateway section of the Amazon API Gateway Developer Guide", + "Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-setup-waf.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsApiGatewayRestApi", + "Id": apiStageArn, + "Partition": "aws", + "Region": awsRegion, + "Details": { + "Other": { + "deploymentId": apiStageDeploymentId, + "stageName": apiStageName, + } + }, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF DE.AE-2", + "NIST SP 800-53 AU-6", + "NIST SP 800-53 CA-7", + "NIST SP 800-53 IR-4", + "NIST SP 800-53 SI-4", + "AICPA TSC CC7.2", + "ISO 27001:2013 A.12.4.1", + "ISO 27001:2013 A.16.1.1", + "ISO 27001:2013 A.16.1.4", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + except Exception as e: + print(e) + else: + print(e) diff --git a/eeauditor/auditors/aws/Amazon_SNS_Auditor.py b/eeauditor/auditors/aws/Amazon_SNS_Auditor.py new file mode 100644 index 00000000..ee0ad74f --- /dev/null +++ b/eeauditor/auditors/aws/Amazon_SNS_Auditor.py @@ -0,0 +1,587 @@ +# This file is part of ElectricEye. + +# ElectricEye is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# ElectricEye is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with ElectricEye. +# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. + +import datetime +import json +import boto3 +from check_register import CheckRegister + +registry = CheckRegister() + +# import boto3 clients +sns = boto3.client("sns") + + +def list_topics(cache): + response = cache.get("list_topics") + if response: + return response + cache["list_topics"] = sns.list_topics() + return cache["list_topics"] + + +@registry.register_check("sns") +def sns_topic_encryption_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + # loop through SNS topics + response = list_topics(cache) + mySnsTopics = response["Topics"] + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for topic in mySnsTopics: + topicarn = str(topic["TopicArn"]) + topicName = topicarn.replace("arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "") + response = sns.get_topic_attributes(TopicArn=topicarn) + try: + # this is a passing check + encryptionCheck = str(response["Attributes"]["KmsMasterKeyId"]) + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-topic-encryption-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[SNS.1] SNS topics should be encrypted", + "Description": "SNS topic " + topicName + " is encrypted.", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS encryption at rest and how to configure it refer to the Encryption at Rest section of the Amazon Simple Notification Service Developer Guide.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF PR.DS-1", + "NIST SP 800-53 MP-8", + "NIST SP 800-53 SC-12", + "NIST SP 800-53 SC-28", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + except: + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-topic-encryption-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "HIGH"}, + "Confidence": 99, + "Title": "[SNS.1] SNS topics should be encrypted", + "Description": "SNS topic " + + topicName + + " is not encrypted. Refer to the remediation instructions to remediate this behavior", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS encryption at rest and how to configure it refer to the Encryption at Rest section of the Amazon Simple Notification Service Developer Guide.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF PR.DS-1", + "NIST SP 800-53 MP-8", + "NIST SP 800-53 SC-12", + "NIST SP 800-53 SC-28", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + + +@registry.register_check("sns") +def sns_http_encryption_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + # loop through SNS topics + response = list_topics(cache) + mySnsTopics = response["Topics"] + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for topic in mySnsTopics: + topicarn = str(topic["TopicArn"]) + topicName = topicarn.replace("arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "") + response = sns.list_subscriptions_by_topic(TopicArn=topicarn) + mySubs = response["Subscriptions"] + for subscriptions in mySubs: + subProtocol = str(subscriptions["Protocol"]) + if subProtocol == "http": + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-http-subscription-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "HIGH"}, + "Confidence": 99, + "Title": "[SNS.2] SNS topics should not use HTTP subscriptions", + "Description": "SNS topic " + + topicName + + " has a HTTP subscriber. Refer to the remediation instructions to remediate this behavior", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS encryption in transit refer to the Enforce Encryption of Data in Transit section of the Amazon Simple Notification Service Developer Guide.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF ID.AM-2", + "NIST SP 800-53 CM-8", + "NIST SP 800-53 PM-5", + "AICPA TSC CC3.2", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.1.1", + "ISO 27001:2013 A.8.1.2", + "ISO 27001:2013 A.12.5.1", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-http-subscription-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[SNS.2] SNS topics should not use HTTP subscriptions", + "Description": "SNS topic " + topicName + " does not have a HTTP subscriber.", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS encryption in transit refer to the Enforce Encryption of Data in Transit section of the Amazon Simple Notification Service Developer Guide.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF ID.AM-2", + "NIST SP 800-53 CM-8", + "NIST SP 800-53 PM-5", + "AICPA TSC CC3.2", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.1.1", + "ISO 27001:2013 A.8.1.2", + "ISO 27001:2013 A.12.5.1", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + + +@registry.register_check("sns") +def sns_public_access_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + # loop through SNS topics + response = list_topics(cache) + mySnsTopics = response["Topics"] + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for topic in mySnsTopics: + topicarn = str(topic["TopicArn"]) + topicName = topicarn.replace("arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "") + response = sns.get_topic_attributes(TopicArn=topicarn) + statement_json = response["Attributes"]["Policy"] + statement = json.loads(statement_json) + fail = False + # this results in one finding per topic instead of one finding per statement + for sid in statement["Statement"]: + access = sid["Principal"].get("AWS", None) + if access != "*" or (access == "*" and "Condition" in sid): + continue + else: + fail = True + break + if not fail: + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-public-access-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 75, # The Condition may not effectively limit access + "Title": "[SNS.3] SNS topics should not have public access", + "Description": "SNS topic " + + topicName + + " does not have public access or limited by a Condition. Refer to the remediation instructions to review sns access policy", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS Access Policy Best Practices refer to Amazons Best Practice rules for Amazon SNS.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#ensure-topics-not-publicly-accessible", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF PR.AC-3", + "NIST SP 800-53 AC-1", + "NIST SP 800-53 AC-17", + "NIST SP 800-53 AC-19", + "NIST SP 800-53 AC-20", + "NIST SP 800-53 SC-15", + "AICPA TSC CC6.6", + "ISO 27001:2013 A.6.2.1", + "ISO 27001:2013 A.6.2.2", + "ISO 27001:2013 A.11.2.6", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-public-access-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "HIGH"}, + "Confidence": 99, + "Title": "[SNS.3] SNS topics should not have public access", + "Description": "SNS topic " + + topicName + + " has public access. Refer to the remediation instructions to remediate this behavior", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS Access Policy Best Practices refer to Amazons Best Practice rules for Amazon SNS.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#ensure-topics-not-publicly-accessible", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF PR.AC-3", + "NIST SP 800-53 AC-1", + "NIST SP 800-53 AC-17", + "NIST SP 800-53 AC-19", + "NIST SP 800-53 AC-20", + "NIST SP 800-53 SC-15", + "AICPA TSC CC6.6", + "ISO 27001:2013 A.6.2.1", + "ISO 27001:2013 A.6.2.2", + "ISO 27001:2013 A.11.2.6", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding + + +@registry.register_check("sns") +def sns_cross_account_check(cache: dict, awsAccountId: str, awsRegion: str) -> dict: + # loop through SNS topics + response = list_topics(cache) + mySnsTopics = response["Topics"] + iso8601Time = datetime.datetime.now(datetime.timezone.utc).isoformat() + for topic in mySnsTopics: + topicarn = str(topic["TopicArn"]) + topicName = topicarn.replace("arn:aws:sns:" + awsRegion + ":" + awsAccountId + ":", "") + response = sns.get_topic_attributes(TopicArn=topicarn) + myPolicy_json = str(response["Attributes"]["Policy"]) + myPolicy = json.loads(myPolicy_json) + fail = False + for statement in myPolicy["Statement"]: + principal = statement["Principal"].get("AWS", None) + if principal and principal != "*": + if not principal.isdigit(): + # This assumes if it is not a digit that it must be an arn. + # not sure if this is a safe assumption. + principal = principal.split(":")[4] + if principal == awsAccountId: + continue + else: + fail = True + break + if not fail: + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-cross-account-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[SNS.4] SNS topics should not allow cross-account access", + "Description": "SNS topic " + topicName + " does not have cross-account access.", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS best practices refer to the Amazon SNS security best practices section of the Amazon Simple Notification Service Developer Guide.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF PR.AC-3", + "NIST SP 800-53 AC-1", + "NIST SP 800-53 AC-17", + "NIST SP 800-53 AC-19", + "NIST SP 800-53 AC-20", + "NIST SP 800-53 SC-15", + "AICPA TSC CC6.6", + "ISO 27001:2013 A.6.2.1", + "ISO 27001:2013 A.6.2.2", + "ISO 27001:2013 A.11.2.6", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + ], + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED", + } + yield finding + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": topicarn + "/sns-cross-account-check", + "ProductArn": "arn:aws:securityhub:" + + awsRegion + + ":" + + awsAccountId + + ":product/" + + awsAccountId + + "/default", + "GeneratorId": topicarn, + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "Low"}, + "Confidence": 99, + "Title": "[SNS.4] SNS topics should not allow cross-account access", + "Description": "SNS topic " + topicName + " has cross-account access.", + "Remediation": { + "Recommendation": { + "Text": "For more information on SNS best practices refer to the Amazon SNS security best practices section of the Amazon Simple Notification Service Developer Guide.", + "Url": "https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit", + } + }, + "ProductFields": {"Product Name": "ElectricEye"}, + "Resources": [ + { + "Type": "AwsSnsTopic", + "Id": topicarn, + "Partition": "aws", + "Region": awsRegion, + "Details": {"AwsSnsTopic": {"TopicName": topicName}}, + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF PR.AC-3", + "NIST SP 800-53 AC-1", + "NIST SP 800-53 AC-17", + "NIST SP 800-53 AC-19", + "NIST SP 800-53 AC-20", + "NIST SP 800-53 SC-15", + "AICPA TSC CC6.6", + "ISO 27001:2013 A.6.2.1", + "ISO 27001:2013 A.6.2.2", + "ISO 27001:2013 A.11.2.6", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + ], + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE", + } + yield finding diff --git a/eeauditor/check_register.py b/eeauditor/check_register.py new file mode 100644 index 00000000..31a42955 --- /dev/null +++ b/eeauditor/check_register.py @@ -0,0 +1,27 @@ +from functools import wraps + + +class CheckRegister(object): + checks = {} + + def register_check(self, cache_name="GLOBAL"): + """Decorator registers event handlers + + Args: + event_type: A string that matches the event type the wrapped function + will process. + """ + + def decorator_register(func): + if cache_name not in self.checks: + self.checks[cache_name] = {func.__name__: func} + else: + self.checks[cache_name].update({func.__name__: func}) + + @wraps(func) + def func_wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return func_wrapper + + return decorator_register diff --git a/eeauditor/controller.py b/eeauditor/controller.py new file mode 100644 index 00000000..dd334999 --- /dev/null +++ b/eeauditor/controller.py @@ -0,0 +1,153 @@ +# This file is part of ElectricEye. + +# ElectricEye is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# ElectricEye is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with ElectricEye. +# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. + +from functools import partial +import getopt +import json +import os +import report +import sys +import boto3 +from pluginbase import PluginBase +from check_register import CheckRegister + + +sts = boto3.client("sts") + +here = os.path.abspath(os.path.dirname(__file__)) +get_path = partial(os.path.join, here) + + +class EEAuditor(object): + """ElectricEye controller + + Load and execute all auditor plugins. + """ + + def __init__(self, name): + self.name = name + self.plugin_base = PluginBase(package="electriceye") + # each check must be decorated with the @registry.register_check("cache_name") + # to be discovered during plugin loading. + self.registry = CheckRegister() + # vendor specific credentials dictionary + self.awsAccountId = sts.get_caller_identity()["Account"] + self.awsRegion = os.environ["AWS_REGION"] + # If there is a desire to add support for multiple clouds, this would be + # a great place to implement it. + self.source = self.plugin_base.make_plugin_source( + searchpath=[get_path("./auditors/aws")], identifier=self.name + ) + + def load_plugins(self, plugin_name): + if plugin_name: + try: + plugin = self.source.load_plugin(plugin_name) + except Exception as e: + print(f"Failed to load plugin {plugin_name} with exception {e}") + else: + for plugin_name in self.source.list_plugins(): + try: + plugin = self.source.load_plugin(plugin_name) + except Exception as e: + print(f"Failed to load plugin {plugin_name} with exception {e}") + + def run_checks(self, requested_check_name): + for cache_name, cache in self.registry.checks.items(): + # a dictionary to be used by checks that share a common cache + auditor_cache = {} + for check_name, check in cache.items(): + # if a specific check is requested, only run that one check + if ( + not requested_check_name + or requested_check_name + and requested_check_name == check_name + ): + try: + print(f"Executing check {self.name}.{check_name}") + for finding in check( + cache=auditor_cache, + awsAccountId=self.awsAccountId, + awsRegion=self.awsRegion, + ): + yield finding + except Exception as e: + print(f"Failed to execute check {check_name} with exception {e}") + + +def main(argv): + findings_list = [] # used if --output is specified + profile_name = "" + auditor_name = "" + check_name = "" + output = False + output_file = "" + help_text = ( + "auditor.py [-p -a -c -o ]" + ) + try: + opts, args = getopt.getopt( + argv, "ho:p:a:c:", ["help", "output=", "profile=", "auditor=", "check="] + ) + except getopt.GetoptError: + print(help_text) + sys.exit(2) + for opt, arg in opts: + if opt in ("-h", "--help"): + print(help_text) + sys.exit(2) + if opt in ("-o", "--output"): + output = True + output_file = arg + if opt in ("-p", "--profile"): + profile_name = arg.strip() + if opt in ("-a", "--auditor"): + auditor_name = arg + if opt in ("-c", "--check"): + check_name = arg + if profile_name: + boto3.setup_default_session(profile_name=profile_name) + + app = EEAuditor(name="AWS Auditor") + app.load_plugins(plugin_name=auditor_name) + first = True + file_location = "" + with open("findings.json", "w") as f: + print('{"Findings":[', file=f) + file_location = os.path.abspath(f.name) + for result in app.run_checks(requested_check_name=check_name): + # print a comma separation between findings except before first finding + if first: + first = False + else: + print(",", file=f) + json.dump(result, f, indent=2) + print("]}", file=f) + f.close() + securityhub = boto3.client("securityhub") + with open(file_location) as f: + findings = json.load(f) + securityhub.batch_import_findings(Findings=findings["Findings"]) + f.close() + if output: + report.csv_output(input_file=file_location, output_file=output_file) + print("Done") + + +if __name__ == "__main__": + # this is for local testing where the AWS_REGION is not liekly set + if not os.environ.get("AWS_REGION", None): + os.environ["AWS_REGION"] = "us-east-1" + main(sys.argv[1:]) diff --git a/eeauditor/report.py b/eeauditor/report.py new file mode 100644 index 00000000..cd67478e --- /dev/null +++ b/eeauditor/report.py @@ -0,0 +1,57 @@ +# This file is part of ElectricEye. + +# ElectricEye is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# ElectricEye is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with ElectricEye. +# If not, see https://github.com/jonrau1/ElectricEye/blob/master/LICENSE. + +import csv +import json +from functools import reduce + +# Return nested dictionary values by passing in dictionary and keys separated by "." +def deep_get(dictionary, keys): + return reduce( + lambda d, key: d.get(key) if isinstance(d, dict) else None, keys.split("."), dictionary, + ) + + +def csv_output(input_file, output_file): + csv_columns = [ + {"name": "Id", "path": "Id"}, + {"name": "Title", "path": "Title"}, + {"name": "ProductArn", "path": "ProductArn"}, + {"name": "AwsAccountId", "path": "AwsAccountId"}, + {"name": "Severity", "path": "Severity.Label"}, + {"name": "Confidence", "path": "Confidence"}, + {"name": "Description", "path": "Description"}, + {"name": "RecordState", "path": "RecordState"}, + {"name": "Compliance Status", "path": "Compliance.Status"}, + {"name": "Remediation Recommendation", "path": "Remediation.Recommendation.Text",}, + {"name": "Remediation Recommendation Link", "path": "Remediation.Recommendation.Url",}, + ] + csv_file = output_file + try: + with open(input_file) as f: + findings_file = json.load(f) + findings = findings_file["Findings"] + with open(csv_file, "w") as csvfile: + writer = csv.writer(csvfile, dialect="excel") + writer.writerow(item["name"] for item in csv_columns) + for finding in findings: + row_data = [] + for column_dict in csv_columns: + row_data.append(deep_get(finding, column_dict["path"])) + writer.writerow(row_data) + csvfile.close() + f.close() + except IOError: + print("I/O error") diff --git a/requirements.txt b/requirements.txt index 2cbde825..32fcf7c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ awscli boto3 requests +pluginbase diff --git a/auditors/tests/test_AWS_KMS_Auditor.py b/tests/test_AWS_KMS_Auditor.py similarity index 100% rename from auditors/tests/test_AWS_KMS_Auditor.py rename to tests/test_AWS_KMS_Auditor.py diff --git a/auditors/tests/test_AWS_Lambda_Auditor.py b/tests/test_AWS_Lambda_Auditor.py similarity index 100% rename from auditors/tests/test_AWS_Lambda_Auditor.py rename to tests/test_AWS_Lambda_Auditor.py diff --git a/auditors/tests/test_Amazon_SNS_Auditor.py b/tests/test_Amazon_SNS_Auditor.py similarity index 100% rename from auditors/tests/test_Amazon_SNS_Auditor.py rename to tests/test_Amazon_SNS_Auditor.py