diff --git a/arch/lza_extensions/customizations/GCGuardrailsRoles.yaml b/arch/lza_extensions/customizations/GCGuardrailsRoles.yaml index 1a56c75d..ae66faaa 100644 --- a/arch/lza_extensions/customizations/GCGuardrailsRoles.yaml +++ b/arch/lza_extensions/customizations/GCGuardrailsRoles.yaml @@ -54,6 +54,7 @@ Resources: "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc03_check_endpoint_access_config", "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc03_check_iam_cloudwatch_alarms", "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc03_check_trusted_devices_admin_access", + "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc04_check_alerts_flag_misuse", "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc04_check_enterprise_monitoring", "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc05_check_data_location", "arn:aws:sts::${AuditAccountID}:assumed-role/${RolePrefix}default_assessment_role/${OrganizationName}gc07_check_secure_network_transmission_policy", diff --git a/arch/templates/AuditAccountPreRequisitesPart5.yaml b/arch/templates/AuditAccountPreRequisitesPart5.yaml index 6e52e484..208a0d0e 100644 --- a/arch/templates/AuditAccountPreRequisitesPart5.yaml +++ b/arch/templates/AuditAccountPreRequisitesPart5.yaml @@ -40,6 +40,20 @@ Resources: LoggingConfig: LogGroup: !Sub "${OrganizationName}gc_guardrails" LogFormat: "JSON" + + GC04CheckAlertsFlagMisuseLambda: + Condition: IsAuditAccount + Type: AWS::Lambda::Function + Properties: + FunctionName: !Sub "${OrganizationName}gc04_check_alerts_flag_misuse" + Code: "../../src/lambda/gc04_check_alerts_flag_misuse/build/GC04CheckAlertsFlagMisuseLambda/" + Handler: app.lambda_handler + Role: !Sub "arn:aws:iam::${AuditAccountID}:role/${RolePrefix}default_assessment_role" + Runtime: !Ref PythonRuntime + Timeout: 90 + LoggingConfig: + LogGroup: !Sub "${OrganizationName}gc_guardrails" + LogFormat: "JSON" ## GC05 GC05CheckDataLocationLambda: diff --git a/arch/templates/ConformancePack.yaml b/arch/templates/ConformancePack.yaml index 481ff20c..5d3da9e1 100644 --- a/arch/templates/ConformancePack.yaml +++ b/arch/templates/ConformancePack.yaml @@ -719,6 +719,42 @@ Resources: SourceDetails: - EventSource: "aws.config" MessageType: "ScheduledNotification" + GC04CheckAlertsFlagMisuseConfigRule: + Type: "AWS::Config::ConfigRule" + Properties: + ConfigRuleName: gc04_check_alerts_flag_misuse + Description: Confirms that alerts to authorized personnel have been implemented to flag misuse, suspicious sign-in attempts, or when changes are made to the cloud broker account. + InputParameters: + IAM_Role_Name: + Fn::If: + - enterpriseMonitoringIAMRoleName + - Ref: EnterpriseMonitoringIAMRoleName + - Ref: AWS::NoValue + ExecutionRoleName: + Fn::If: + - GCLambdaExecutionRoleName + - Ref: GCLambdaExecutionRoleName + - Ref: AWS::NoValue + AuditAccountID: + Fn::If: + - auditAccountID + - Ref: AuditAccountID + - Ref: AWS::NoValue + Scope: + ComplianceResourceTypes: + - AWS::Account + MaximumExecutionFrequency: TwentyFour_Hours + Source: + Owner: CUSTOM_LAMBDA + SourceIdentifier: + Fn::Join: + - "" + - - "arn:aws:lambda:ca-central-1:" + - Ref: AuditAccountID + - !Sub ":function:${OrganizationName}gc04_check_alerts_flag_misuse" + SourceDetails: + - EventSource: "aws.config" + MessageType: "ScheduledNotification" # GC05 GC05CheckDataLocationConfigRule: Type: "AWS::Config::ConfigRule" diff --git a/src/lambda/aws_lambda_permissions_setup/app.py b/src/lambda/aws_lambda_permissions_setup/app.py index 7819f551..8b43a7be 100644 --- a/src/lambda/aws_lambda_permissions_setup/app.py +++ b/src/lambda/aws_lambda_permissions_setup/app.py @@ -86,6 +86,7 @@ def apply_lambda_permissions(): f"{organization_name}gc03_check_endpoint_access_config": ["GC03CheckEndpointAccessConfigLambda"], f"{organization_name}gc03_check_iam_cloudwatch_alarms": ["GC03CheckIAMCloudWatchAlarmsLambda"], f"{organization_name}gc03_check_trusted_devices_admin_access": ["GC03CheckTrustedDevicesAdminAccessLambda"], + f"{organization_name}gc04_check_alerts_flag_misuse": ["GC04CheckAlertsFlagMisuseLambda"], f"{organization_name}gc04_check_enterprise_monitoring": ["GC04CheckEnterpriseMonitoringLambda"], f"{organization_name}gc05_check_data_location": ["GC05CheckDataLocationLambda"], f"{organization_name}gc06_check_encryption_at_rest_part1": ["GC06CheckEncryptionAtRestPart1Lambda"], diff --git a/src/lambda/gc04_check_alerts_flag_misuse/.gitignore b/src/lambda/gc04_check_alerts_flag_misuse/.gitignore new file mode 100644 index 00000000..4808264d --- /dev/null +++ b/src/lambda/gc04_check_alerts_flag_misuse/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/src/lambda/gc04_check_alerts_flag_misuse/README.md b/src/lambda/gc04_check_alerts_flag_misuse/README.md new file mode 100644 index 00000000..53d4b35d --- /dev/null +++ b/src/lambda/gc04_check_alerts_flag_misuse/README.md @@ -0,0 +1,37 @@ +*This readme file was created by AWS Bedrock: anthropic.claude-v2* + +# ./src/lambda/gc04_check_alerts_flag_misuse/app.py + +## Overview + +This Lambda function checks alerts to authorized personnel have been implemented to flag misuse, suspicious sign-in attempts, or when changes are made to the cloud broker account. + +## Functions + +### lambda_handler + +Main entry point for the Lambda function. + +- Checks if this is a scheduled invocation and if we're in the Management Account +- Gets the required AWS clients +- Calls `check_enterprise_monitoring_accounts` to validate the IAM role +- Builds an evaluation with the result and puts it via AWS Config + +### build_evaluation + +Helper to build an evaluation object for AWS Config. + +### Other functions + +- `get_client`: Helper to get boto3 clients, supporting assume role +- `get_assume_role_credentials`: Get temporary credentials via assume role +- `is_scheduled_notification`: Check if the event is a scheduled notification +- `evaluate_parameters`: Validate input parameters + +## Testing + +No automated tests are included. + +## Logging + +Uses Python's standard logging library to log information. diff --git a/src/lambda/gc04_check_alerts_flag_misuse/__init__.py b/src/lambda/gc04_check_alerts_flag_misuse/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/lambda/gc04_check_alerts_flag_misuse/app.py b/src/lambda/gc04_check_alerts_flag_misuse/app.py new file mode 100644 index 00000000..eb97fd11 --- /dev/null +++ b/src/lambda/gc04_check_alerts_flag_misuse/app.py @@ -0,0 +1,384 @@ +""" GC04 - Check Alerts Flag Misuse + https://canada-ca.github.io/cloud-guardrails/EN/04_Enterprise-Monitoring-Accounts.html +""" +import json +import logging +import time + +import boto3 +import botocore + +logger = logging.getLogger() +logger.setLevel(logging.INFO) +ASSUME_ROLE_MODE = True +DEFAULT_RESOURCE_TYPE = "AWS::::Account" + + +def get_client(service, event): + """Return the service boto client. It should be used instead of directly calling the client. + Keyword arguments: + service -- the service name used for calling the boto.client() + event -- the event variable given in the lambda handler + """ + if not ASSUME_ROLE_MODE: + return boto3.client(service) + execution_role_arn = f"arn:aws:iam::{AWS_ACCOUNT_ID}:role/{EXECUTION_ROLE_NAME}" + credentials = get_assume_role_credentials(execution_role_arn) + return boto3.client( + service, + aws_access_key_id=credentials["AccessKeyId"], + aws_secret_access_key=credentials["SecretAccessKey"], + aws_session_token=credentials["SessionToken"], + ) + + +def get_assume_role_credentials(role_arn): + """Return the service boto client. It should be used instead of directly calling the client. + Keyword arguments: + service -- the service name used for calling the boto.client() + event -- the event variable given in the lambda handler + """ + sts_client = boto3.client("sts") + try: + assume_role_response = sts_client.assume_role( + RoleArn=role_arn, + RoleSessionName="configLambdaExecution" + ) + return assume_role_response["Credentials"] + except botocore.exceptions.ClientError as ex: + if "AccessDenied" in ex.response["Error"]["Code"]: + ex.response["Error"]["Message"] = "AWS Config does not have permission to assume the IAM role." + else: + ex.response["Error"]["Message"] = "InternalError" + ex.response["Error"]["Code"] = "InternalError" + raise ex + + +def is_scheduled_notification(message_type): + """Check whether the message is a ScheduledNotification or not. + Keyword arguments: + message_type -- the message type + """ + return message_type == "ScheduledNotification" + + +def evaluate_parameters(rule_parameters): + """Evaluate the rule parameters dictionary. + Keyword arguments: + rule_parameters -- the Key/Value dictionary of the Config rule parameters + """ + if "IAM_Role_Name" not in rule_parameters: + raise ValueError('The parameter with "IAM_Role_Name" as key must be defined.') + if not rule_parameters["IAM_Role_Name"]: + raise ValueError('The parameter "IAM_Role_Name" must have a defined value.') + return rule_parameters + + +# This generate an evaluation for config +def build_evaluation( + resource_id, + compliance_type, + event, + resource_type=DEFAULT_RESOURCE_TYPE, + annotation=None, +): + """Form an evaluation as a dictionary. Usually suited to report on scheduled rules. + Keyword arguments: + resource_id -- the unique id of the resource to report + compliance_type -- either COMPLIANT, NON_COMPLIANT or NOT_APPLICABLE + event -- the event variable given in the lambda handler + resource_type -- the CloudFormation resource type (or AWS::::Account) + to report on the rule (default DEFAULT_RESOURCE_TYPE) + annotation -- an annotation to be added to the evaluation (default None). + It will be truncated to 255 if longer. + """ + eval_cc = {} + if annotation: + eval_cc["Annotation"] = annotation + eval_cc["ComplianceResourceType"] = resource_type + eval_cc["ComplianceResourceId"] = resource_id + eval_cc["ComplianceType"] = compliance_type + eval_cc["OrderingTimestamp"] = str( + json.loads(event["invokingEvent"])["notificationCreationTime"] + ) + return eval_cc + + +def get_event_bridge_rules(): + rules = [] + try: + # Assuming we only need to check the default event bus + response = AWS_EVENT_BRIDGE_CLIENT.list_rules() + rules = rules + response.get("Rules") + next_token = response.get("NextToken") + + while next_token != None: + response = AWS_EVENT_BRIDGE_CLIENT.list_rules(NextToken=next_token) + rules = rules + response.get("Rules") + next_token = response.get("NextToken") + + return rules + except botocore.exceptions.ClientError as ex: + if "ResourceNotFound" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to list rules for EventBridge. Resource not found." + else: + ex.response["Error"]["Message"] = "InternalError" + ex.response["Error"]["Code"] = "InternalError" + raise ex + +def get_topic_subscriptions(topic_arn): + try: + response = AWS_SNS_CLIENT.list_subscriptions_by_topic(TopicArn=topic_arn) + subscriptions = response.get("Subscriptions", []) + next_token = response.get("NextToken") + + while next_token != None: + response = AWS_SNS_CLIENT.list_subscriptions_by_topic(TopicArn=topic_arn, NextToken=next_token) + subscriptions = subscriptions + response.get("Subscriptions", []) + next_token = response.get("NextToken") + + return subscriptions + except botocore.exceptions.ClientError as ex: + if "NotFound" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to get topic subscriptions. Resource not found." + elif "InvalidParameter" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to get topic subscriptions. Invalid parameter." + elif "AuthorizationError" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to get topic subscriptions. User is unauthorized." + else: + ex.response["Error"]["Message"] = "InternalError" + ex.response["Error"]["Code"] = "InternalError" + raise ex + +def subscription_is_confirmed(subscription_arn): + try: + response = AWS_SNS_CLIENT.get_subscription_attributes(SubscriptionArn=subscription_arn) + attributes = response.get("Attributes") + logger.info("Subscription attributes: %s", attributes) + + if attributes == None: + return False + + return attributes.get("PendingConfirmation") == "false" + except botocore.exceptions.ClientError as ex: + if "NotFound" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to get subscription attributes. Resource not found." + elif "InvalidParameter" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to get subscription attributes. Invalid parameter." + elif "AuthorizationError" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to get subscription attributes. User is unauthorized." + else: + ex.response["Error"]["Message"] = "InternalError" + ex.response["Error"]["Code"] = "InternalError" + raise ex + +def fetch_rule_targets(rule_name): + try: + response = AWS_EVENT_BRIDGE_CLIENT.list_targets_by_rule(Rule=rule_name) + targets = response.get("Targets", []) + next_token = response.get("NextToken") + + while next_token != None: + response = AWS_EVENT_BRIDGE_CLIENT.list_targets_by_rule(Rule=rule_name, NextToken=next_token) + targets = targets + response.get("Targets", []) + next_token = response.get("NextToken") + return targets + except botocore.exceptions.ClientError as ex: + if "NotFound" in ex.response['Error']['Code']: + ex.response['Error']['Message'] = "Failed to fetch all targets for a rule. Resource not found." + else: + ex.response["Error"]["Message"] = "InternalError" + ex.response["Error"]["Code"] = "InternalError" + raise ex + +def flatten_dict(d, parent_key='', sep='.'): + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + +def rule_matches_against_cb_role_identity(rule_event_pattern, cb_role_arn): + logger.info("rule_event_pattern: %s", rule_event_pattern) + if rule_event_pattern == None: + return False + + event_pattern_dict = json.loads(rule_event_pattern) + logger.info("event_pattern_dict: %s", event_pattern_dict) + + ep_detail = flatten_dict(event_pattern_dict.get("detail", {})) + return cb_role_arn in ep_detail.get("userIdentity.sessionContext.sessionIssuer.arn", []) and "Role" in ep_detail.get("userIdentity.sessionContext.sessionIssuer.type", []) + +def get_role_arn(cb_role): + response = AWS_IAM_CLIENT.get_role(RoleName=cb_role) + return response.get("Role").get("Arn") + +def check_cb_role(cb_role, event): + """Checks cloudtrail events to see if the CloudBroker role has been changed. Build evaluation based on discovery.""" + + role_change_events = [ + "DeleteRolePolicy", + "AttachRolePolicy", + "DeleteRole", + "DetachRolePolicy", + "PutRolePolicy", + "UpdateAssumeRolePolicy" + ] + next_token = None + while True: + response = AWS_CLOUD_TRAIL_CLIENT.lookup_events( + LookupAttributes = [ { "AttributeKey": "ResourceName", "AttributeValue": cb_role } ] + ) + next_token = response.get("NextToken") + + for e in response.get("Events", []): + event_name = e.get("EventName", "") + if event_name in role_change_events: + return build_evaluation( + AWS_ACCOUNT_ID, + "NON_COMPLIANT", + event, + DEFAULT_RESOURCE_TYPE, + f"Event \"{e.get("EventId")}\" was found performing action \"{event_name}\" on Cloud Broker role." + ) + + if not next_token: + break + + return build_evaluation( + AWS_ACCOUNT_ID, + "COMPLIANT", + event, + DEFAULT_RESOURCE_TYPE + ) + +def check_rule_sns_target_is_setup(rule, event): + logger.info("Checking rule: %s", rule) + if rule.get("State") == "DISABLED": + return build_evaluation( + rule.get("Name"), + "NON_COMPLIANT", + event, + resource_type="AWS::Events::Rule", + annotation="Rule is disabled.", + ) + + rule_name = rule.get("Name") + targets = fetch_rule_targets(rule_name) + + for target in targets: + logger.info("Checking rule target: %s", target) + # is target an SNS input transformer? + target_arn: str = target.get("Arn") + if target_arn.startswith("arn:aws:sns:") : + # yes, get a list of topic subscriptions + subscriptions = get_topic_subscriptions(target_arn) + # then search topic for a subscription with "email" protocol and is confirmed + for subscription in subscriptions: + logger.info("Checking target subscriptions: %s", subscription) + if subscription.get("Protocol") == "email": + subscription_arn = subscription.get("SubscriptionArn") + if subscription_is_confirmed(subscription_arn): + return build_evaluation( + rule.get("Name"), + "COMPLIANT", + event, + resource_type="AWS::Events::Rule", + annotation="An Event rule that has a SNS topic and subscription to send notification emails is setup and confirmed.", + ) + + return build_evaluation( + rule.get("Name"), + "NON_COMPLIANT", + event, + resource_type="AWS::Events::Rule", + annotation="An Event rule that has a SNS topic and subscription to send notification emails is not setup or confirmed.", + ) + +def lambda_handler(event, context): + """Lambda handler to check CloudTrail trails are logging. + Keyword arguments: + event -- the event variable given in the lambda handler + context -- the context variable given in the lambda handler + """ + global AWS_CONFIG_CLIENT + global AWS_SNS_CLIENT + global AWS_IAM_CLIENT + global AWS_EVENT_BRIDGE_CLIENT + global AWS_CLOUD_TRAIL_CLIENT + global AWS_ACCOUNT_ID + global EXECUTION_ROLE_NAME + global AUDIT_ACCOUNT_ID + + + evaluations = [] + rule_parameters = {} + + invoking_event = json.loads(event["invokingEvent"]) + + # parse parameters + AWS_ACCOUNT_ID = event["accountId"] + + if "ruleParameters" in event: + rule_parameters = json.loads(event["ruleParameters"]) + + valid_rule_parameters = evaluate_parameters(rule_parameters) + + if "ExecutionRoleName" in valid_rule_parameters: + EXECUTION_ROLE_NAME = valid_rule_parameters["ExecutionRoleName"] + else: + EXECUTION_ROLE_NAME = "AWSA-GCLambdaExecutionRole" + + if "AuditAccountID" in valid_rule_parameters: + AUDIT_ACCOUNT_ID = valid_rule_parameters["AuditAccountID"] + else: + AUDIT_ACCOUNT_ID = "" + + AWS_CONFIG_CLIENT = get_client("config", event) + AWS_EVENT_BRIDGE_CLIENT = get_client("events", event) + AWS_SNS_CLIENT = get_client("sns", event) + AWS_CLOUD_TRAIL_CLIENT = get_client("cloudtrail", event) + AWS_IAM_CLIENT = get_client("iam", event) + + + # is this a scheduled invokation? + if is_scheduled_notification(invoking_event["messageType"]): + rules_are_compliant = False + rules = get_event_bridge_rules() + cb_role = valid_rule_parameters["IAM_Role_Name"] + + cb_role_arn = get_role_arn(cb_role) + + cb_rules = [rule for rule in rules if rule_matches_against_cb_role_identity(rule.get("EventPattern"), cb_role_arn)] + + if len(cb_rules) == 0: + evaluations.append(build_evaluation( + AWS_ACCOUNT_ID, + "NON_COMPLIANT", + event, + DEFAULT_RESOURCE_TYPE, + "No event bridge rule found that alerts authorized personnel of misuse, suspicious sign-in attempts, or when changes are made to the cloud broker account." + )) + else: + for rule in cb_rules: + logger.info(f"Checking rule: {rule}") + rule_evaluation = check_rule_sns_target_is_setup(rule, event) + if rule_evaluation.get("ComplianceType", "COMPLIANT") == "COMPLIANT": + rules_are_compliant = True + evaluations.append(rule_evaluation) + + if rules_are_compliant: + evaluations.append(check_cb_role(cb_role, event)) + else: + evaluations.append(build_evaluation( + AWS_ACCOUNT_ID, + "NON_COMPLIANT", + event, + DEFAULT_RESOURCE_TYPE, + "One or more event bridge rules are not compliant." + )) + + AWS_CONFIG_CLIENT.put_evaluations(Evaluations=evaluations, ResultToken=event["resultToken"]) \ No newline at end of file diff --git a/src/lambda/gc04_check_alerts_flag_misuse/events/event.json b/src/lambda/gc04_check_alerts_flag_misuse/events/event.json new file mode 100644 index 00000000..a6197dea --- /dev/null +++ b/src/lambda/gc04_check_alerts_flag_misuse/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/hello", + "path": "/hello", + "httpMethod": "GET", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/hello", + "resourcePath": "/hello", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/src/lambda/gc04_check_alerts_flag_misuse/requirements.txt b/src/lambda/gc04_check_alerts_flag_misuse/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/lambda/gc04_check_alerts_flag_misuse/template.yaml b/src/lambda/gc04_check_alerts_flag_misuse/template.yaml new file mode 100644 index 00000000..04d16681 --- /dev/null +++ b/src/lambda/gc04_check_alerts_flag_misuse/template.yaml @@ -0,0 +1,19 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + gc04_check_alerts_flag_misuse + +Globals: + Function: + Timeout: 90 + MemorySize: 128 + +Resources: + GC04CheckAlertsFlagMisuseLambda: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: app.lambda_handler + Runtime: python3.9 + Architectures: + - x86_64