diff --git a/deployment/cf-templates/ddb.json b/deployment/cf-templates/ddb.json index 9b55f4d5..7554212b 100755 --- a/deployment/cf-templates/ddb.json +++ b/deployment/cf-templates/ddb.json @@ -393,6 +393,36 @@ }, "TableName": {"Fn::Join" : ["", [ { "Ref": "ResourcesPrefix" }, "s3-unencrypted" ] ]} } + }, + "DynamoDBWhitelist": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "account_id", + "AttributeType": "S" + }, + { + "AttributeName": "issue_id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "account_id", + "KeyType": "HASH" + }, + { + "AttributeName": "issue_id", + "KeyType": "RANGE" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + }, + "TableName": {"Fn::Join" : ["", [ { "Ref": "ResourcesPrefix" }, "whitelist" ] ]} + } }, "DynamoDBRDSUnencrypted": { "Type": "AWS::DynamoDB::Table", diff --git a/deployment/configs/config.json b/deployment/configs/config.json index 68bb3bef..deb8936f 100755 --- a/deployment/configs/config.json +++ b/deployment/configs/config.json @@ -161,5 +161,9 @@ "ddb.table_name": "hammer-rds-unencrypted", "topic_name": "hammer-describe-rds-encryption-lambda", "reporting": true - } + }, + "whitelist_ddb": { + "enabled":true, + "ddb.table_name": "prod-hammer-whitelist" + } } diff --git a/hammer/identification/lambdas/cloudtrails-issues-identification/describe_cloudtrails.py b/hammer/identification/lambdas/cloudtrails-issues-identification/describe_cloudtrails.py index b02ea0ec..c2a3f1fa 100755 --- a/hammer/identification/lambdas/cloudtrails-issues-identification/describe_cloudtrails.py +++ b/hammer/identification/lambdas/cloudtrails-issues-identification/describe_cloudtrails.py @@ -9,6 +9,7 @@ from library.ddb_issues import IssueStatus, CloudTrailIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -56,10 +57,7 @@ def lambda_handler(event, context): issue.issue_details.disabled = checker.disabled issue.issue_details.delivery_errors = checker.delivery_errors issue.add_trails(checker.trails) - if config.cloudtrails.in_whitelist(account_id, region): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "cloudtrails", region).is_whitelisted() logging.debug(f"Setting {region} status {issue.status}") IssueOperations.update(ddb_table, issue) # issue exists in ddb and was fixed diff --git a/hammer/identification/lambdas/ebs-public-snapshots-identification/describe_ebs_public_snapshots.py b/hammer/identification/lambdas/ebs-public-snapshots-identification/describe_ebs_public_snapshots.py index dee609e9..982715a2 100755 --- a/hammer/identification/lambdas/ebs-public-snapshots-identification/describe_ebs_public_snapshots.py +++ b/hammer/identification/lambdas/ebs-public-snapshots-identification/describe_ebs_public_snapshots.py @@ -9,6 +9,7 @@ from library.ddb_issues import IssueStatus, EBSPublicSnapshotIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -57,10 +58,7 @@ def lambda_handler(event, context): issue.issue_details.region = snapshot.account.region issue.issue_details.volume_id = snapshot.volume_id issue.issue_details.tags = snapshot.tags - if config.ebsSnapshot.in_whitelist(account_id, snapshot.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "ebsSnapshot", snapshot.id).is_whitelisted() logging.debug(f"Setting {snapshot.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/ebs-unencrypted-volume-identification/describe_ebs_unencrypted_volumes.py b/hammer/identification/lambdas/ebs-unencrypted-volume-identification/describe_ebs_unencrypted_volumes.py index 6c295aff..5de8a343 100755 --- a/hammer/identification/lambdas/ebs-unencrypted-volume-identification/describe_ebs_unencrypted_volumes.py +++ b/hammer/identification/lambdas/ebs-unencrypted-volume-identification/describe_ebs_unencrypted_volumes.py @@ -9,6 +9,7 @@ from library.ddb_issues import IssueStatus, EBSUnencryptedVolumeIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -59,10 +60,7 @@ def lambda_handler(event, context): issue.issue_details.state = volume.state issue.issue_details.attachments = volume.attachments issue.issue_details.tags = volume.tags - if config.ebsVolume.in_whitelist(account_id, volume.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "ebsVolume", volume.id).is_whitelisted() logging.debug(f"Setting {volume.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/iam-keyrotation-issues-identification/describe_iam_key_rotation.py b/hammer/identification/lambdas/iam-keyrotation-issues-identification/describe_iam_key_rotation.py index b85e0bc2..a73caacc 100755 --- a/hammer/identification/lambdas/iam-keyrotation-issues-identification/describe_iam_key_rotation.py +++ b/hammer/identification/lambdas/iam-keyrotation-issues-identification/describe_iam_key_rotation.py @@ -8,6 +8,7 @@ from library.aws.utility import Account, DDB from library.ddb_issues import IssueStatus, IAMKeyRotationIssue from library.ddb_issues import Operations as IssueOperations +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -56,10 +57,7 @@ def lambda_handler(event, context): issue = IAMKeyRotationIssue(account_id, key.id) issue.issue_details.username = user.id issue.issue_details.create_date = key.create_date.isoformat() - if config.iamUserKeysRotation.in_whitelist(account_id, key.id) or config.iamUserKeysRotation.in_whitelist(account_id, user.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "iamUserKeysRotation", key.id, user.id).is_whitelisted() logging.debug(f"Setting {key.id}/{user.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/iam-user-inactive-keys-identification/describe_iam_accesskey_details.py b/hammer/identification/lambdas/iam-user-inactive-keys-identification/describe_iam_accesskey_details.py index c1db9fac..d87ce85b 100755 --- a/hammer/identification/lambdas/iam-user-inactive-keys-identification/describe_iam_accesskey_details.py +++ b/hammer/identification/lambdas/iam-user-inactive-keys-identification/describe_iam_accesskey_details.py @@ -8,6 +8,7 @@ from library.aws.utility import Account, DDB from library.ddb_issues import IssueStatus, IAMKeyInactiveIssue from library.ddb_issues import Operations as IssueOperations +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -57,10 +58,7 @@ def lambda_handler(event, context): issue.issue_details.username = user.id issue.issue_details.last_used = key.last_used.isoformat() issue.issue_details.create_date = key.create_date.isoformat() - if config.iamUserInactiveKeys.in_whitelist(account_id, key.id) or config.iamUserInactiveKeys.in_whitelist(account_id, user.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "iamUserInactiveKeys", key.id, user.id).is_whitelisted() logging.debug(f"Setting {key.id}/{user.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from open_issues (if exists) diff --git a/hammer/identification/lambdas/rds-public-snapshots-identification/describe_rds_public_snapshots.py b/hammer/identification/lambdas/rds-public-snapshots-identification/describe_rds_public_snapshots.py index 6d155389..f9b20163 100755 --- a/hammer/identification/lambdas/rds-public-snapshots-identification/describe_rds_public_snapshots.py +++ b/hammer/identification/lambdas/rds-public-snapshots-identification/describe_rds_public_snapshots.py @@ -9,6 +9,7 @@ from library.ddb_issues import IssueStatus, RdsPublicSnapshotIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -59,10 +60,7 @@ def lambda_handler(event, context): issue.issue_details.region = snapshot.account.region issue.issue_details.engine = snapshot.engine issue.issue_details.tags = snapshot.tags - if config.rdsSnapshot.in_whitelist(account_id, snapshot.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "rdsSnapshot", snapshot.id).is_whitelisted() logging.debug(f"Setting {snapshot.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/rds-unencrypted-instance-identification/describe_rds_instance_encryption.py b/hammer/identification/lambdas/rds-unencrypted-instance-identification/describe_rds_instance_encryption.py index bc84e972..7d60621a 100644 --- a/hammer/identification/lambdas/rds-unencrypted-instance-identification/describe_rds_instance_encryption.py +++ b/hammer/identification/lambdas/rds-unencrypted-instance-identification/describe_rds_instance_encryption.py @@ -9,6 +9,7 @@ from library.ddb_issues import IssueStatus, RdsEncryptionIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -59,10 +60,7 @@ def lambda_handler(event, context): issue.issue_details.region = instance.account.region issue.issue_details.engine = instance.engine issue.issue_details.tags = instance.tags - if config.rdsEncrypt.in_whitelist(account_id, instance.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "rdsEncrypt", instance.id).is_whitelisted() logging.debug(f"Setting {instance.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/s3-acl-issues-identification/describe_s3_bucket_acl.py b/hammer/identification/lambdas/s3-acl-issues-identification/describe_s3_bucket_acl.py index 6f8f20fa..124a39ee 100755 --- a/hammer/identification/lambdas/s3-acl-issues-identification/describe_s3_bucket_acl.py +++ b/hammer/identification/lambdas/s3-acl-issues-identification/describe_s3_bucket_acl.py @@ -7,6 +7,7 @@ from library.aws.utility import Account, DDB from library.ddb_issues import IssueStatus, S3AclIssue from library.ddb_issues import Operations as IssueOperations +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -55,10 +56,7 @@ def lambda_handler(event, context): issue.issue_details.owner = bucket.owner issue.issue_details.public_acls = bucket.get_public_acls() issue.issue_details.tags = bucket.tags - if config.s3acl.in_whitelist(account_id, bucket.name): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "s3acl", bucket.name).is_whitelisted() logging.debug(f"Setting {bucket.name} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/s3-policy-issues-identification/describe_s3_bucket_policy.py b/hammer/identification/lambdas/s3-policy-issues-identification/describe_s3_bucket_policy.py index 2ac13ae0..b6823d28 100755 --- a/hammer/identification/lambdas/s3-policy-issues-identification/describe_s3_bucket_policy.py +++ b/hammer/identification/lambdas/s3-policy-issues-identification/describe_s3_bucket_policy.py @@ -7,6 +7,7 @@ from library.aws.utility import Account, DDB from library.ddb_issues import IssueStatus, S3PolicyIssue from library.ddb_issues import Operations as IssueOperations +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -54,11 +55,8 @@ def lambda_handler(event, context): issue = S3PolicyIssue(account_id, bucket.name) issue.issue_details.owner = bucket.owner issue.issue_details.tags = bucket.tags - issue.issue_details.policy = bucket.policy - if config.s3policy.in_whitelist(account_id, bucket.name): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.issue_details.policy = bucket. + issue.status = whitelist(account_id, "s3policy", bucket.name).is_whitelisted() logging.debug(f"Setting {bucket.name} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/s3-unencrypted-bucket-issues-identification/describe_s3_encryption.py b/hammer/identification/lambdas/s3-unencrypted-bucket-issues-identification/describe_s3_encryption.py index ecf8e766..89b8f576 100644 --- a/hammer/identification/lambdas/s3-unencrypted-bucket-issues-identification/describe_s3_encryption.py +++ b/hammer/identification/lambdas/s3-unencrypted-bucket-issues-identification/describe_s3_encryption.py @@ -7,6 +7,7 @@ from library.aws.utility import Account, DDB from library.ddb_issues import IssueStatus, S3EncryptionIssue from library.ddb_issues import Operations as IssueOperations +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -54,10 +55,7 @@ def lambda_handler(event, context): issue = S3EncryptionIssue(account_id, bucket.name) issue.issue_details.owner = bucket.owner issue.issue_details.tags = bucket.tags - if config.s3Encrypt.in_whitelist(account_id, bucket.name): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "s3Encrypt", bucket.name).is_whitelisted() logging.debug(f"Setting {bucket.name} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/sg-issues-identification/describe_sec_grps_unrestricted_access.py b/hammer/identification/lambdas/sg-issues-identification/describe_sec_grps_unrestricted_access.py index 5228a266..4cebc997 100755 --- a/hammer/identification/lambdas/sg-issues-identification/describe_sec_grps_unrestricted_access.py +++ b/hammer/identification/lambdas/sg-issues-identification/describe_sec_grps_unrestricted_access.py @@ -9,6 +9,7 @@ from library.ddb_issues import IssueStatus, SecurityGroupIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -68,11 +69,7 @@ def lambda_handler(event, context): for ip_range in perm.ip_ranges: if not ip_range.restricted: issue.add_perm(perm.protocol, perm.from_port, perm.to_port, ip_range.cidr, ip_range.status) - if config.sg.in_whitelist(account_id, f"{sg.vpc_id}:{sg.name}") or \ - config.sg.in_whitelist(account_id, sg.id): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "sg", sg.id, f"{sg.vpc_id}:{sg.name}").is_whitelisted() logging.debug(f"Setting {sg.id} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/identification/lambdas/sqs-public-policy-identification/describe_sqs_public_policy.py b/hammer/identification/lambdas/sqs-public-policy-identification/describe_sqs_public_policy.py index 63a02b12..06f038df 100644 --- a/hammer/identification/lambdas/sqs-public-policy-identification/describe_sqs_public_policy.py +++ b/hammer/identification/lambdas/sqs-public-policy-identification/describe_sqs_public_policy.py @@ -8,6 +8,7 @@ from library.ddb_issues import IssueStatus, SQSPolicyIssue from library.ddb_issues import Operations as IssueOperations from library.aws.utility import DDB, Sns +from library.aws.whitelist import ddb_whitelist as whitelist def lambda_handler(event, context): @@ -59,10 +60,7 @@ def lambda_handler(event, context): issue.issue_details.name = queue.name issue.issue_details.region = queue.account.region issue.issue_details.policy = queue.policy - if config.sqspolicy.in_whitelist(account_id, queue.url): - issue.status = IssueStatus.Whitelisted - else: - issue.status = IssueStatus.Open + issue.status = whitelist(account_id, "sqspolicy", queue.url).is_whitelisted() logging.debug(f"Setting {queue.name} status {issue.status}") IssueOperations.update(ddb_table, issue) # remove issue id from issues_list_from_db (if exists) diff --git a/hammer/library/aws/whitelist.py b/hammer/library/aws/whitelist.py new file mode 100644 index 00000000..be93bcee --- /dev/null +++ b/hammer/library/aws/whitelist.py @@ -0,0 +1,107 @@ +import json +import logging +import os + +from boto3 import resource +from boto3.dynamodb.conditions import Key, Attr +from enum import Enum +from library.aws.utility import Account +from library.config import Config +from library.ddb_issues import Operations, Issue, IssueStatus + +class Issue_type(Enum): + """ + enum class to map issue type to rule names + provides a cleaner way to create unique issue ids + for storage and query in whitelist database + """ + def __str__(self): + return str(self.value) + + cloudtrails = "cloudtrails" + ebsSnapshot = "ebs_public_snapshot" + ebsVolume = "ebs_unencrypted_volume" + iamUserKeysRotation = "user_keysrotation" + iamUserIpRestriction = "user_iprestriction" + iamUserInactiveKeys = "user_inactivekeys" + sg = "secgrp_unrestricted_access" + s3policy = "s3_bucket_policy" + s3acl = "s3_bucket_acl" + iamUserWithPassword = "user_withpassword" + rdsSnapshot = "rds_public_snapshot" + sqspolicy = "sqs_public_access" + s3Encrypt = "s3_encryption" + rdsEncrypt = "rds_encryption" + +class ddb_whitelist(object): + + def __init__(self, account_id, rule_name, issue_primary, *args): + self.account_id = account_id + #rule name maps to unique identifier in each dedscribe function + self.rule_name = rule_name + #Issues for json whitelist can take more than 1 Issues + self.issue_id_primary = issue_primary + self.issue_id_secondary = None + self.issue_id_for_ddb_primary = self.issue_id_primary+"::"+Issue_type[self.rule_name].value + self.whitelist_issue_first = Issue(self.account_id,self.issue_id_for_ddb_primary) + + #if more than one issue id types exist, check both + if len(args)==1: + self.issue_id_secondary = args[0] + self.issue_id_for_ddb_secondary = self.issue_id_secondary+"::"+Issue_type[self.rule_name].value + self.whitelist_issue_second = Issue(self.account_id,self.issue_id_for_ddb_secondary) + + self.config=Config() + + main_account = Account(region=self.config.aws.region) + self.ddb_table = main_account.resource("dynamodb").Table(self.config.whitelistDDB.ddb_table_name) + + def is_whitelisted(self): + """ + Query both json and ddb function + return if issue present in either + """ + if self.check_issue_in_whitelist_json() or self.check_issue_in_whitelist_ddb(): + return IssueStatus.Whitelisted + else: + return IssueStatus.Open + + def merge_whitelist_json_in_ddb(self): + """ + Method to merge Whitelisted Json file entry with Dynamo db + To have one source of truth for all whitelisted data + Not used with current functionality + """ + Operations.update(ddb_table,self.whitelist_issue) + logging.debug(f"Adding json whitelist to ddb: {self.whitelist_issue}") + return + + def check_issue_in_whitelist_json(self): + """ + Check if issue exists on legagcy whitelist json file + return Issue status if Existing + else return nothing + """ + try: + if (getattr(self.config,self.rule_name).in_whitelist(self.account_id, self.issue_id_primary)) or \ + (getattr(self.config,self.rule_name).in_whitelist(self.account_id, self.issue_id_secondary)): + return IssueStatus.Whitelisted + except: + logging.debug(f"Secondary Issue probably not present: {self.account_id, self.issue_id_secondary}") + pass + + def check_issue_in_whitelist_ddb(self): + """ + Check if issue exists in the whitelist ddb + corresponds to user selecting won't fix + + check for existence of both forms of issue ids and query ddb accordingly + eg: sg.id or sg.vpc_id:sg.name + """ + if self.ddb_table.query(KeyConditionExpression=Key('account_id').eq(self.whitelist_issue_first.account_id) & \ + Key('issue_id').eq(self.whitelist_issue_first.issue_id))['Items']: + return IssueStatus.Whitelisted + + elif self.issue_id_secondary and (self.ddb_table.query(KeyConditionExpression=Key('account_id').eq(self.whitelist_issue_second.account_id) & \ + Key('issue_id').eq(self.whitelist_issue_second.issue_id))['Items']): + return IssueStatus.Whitelisted diff --git a/hammer/library/config.py b/hammer/library/config.py index 504f1a1d..ab9b8361 100755 --- a/hammer/library/config.py +++ b/hammer/library/config.py @@ -62,7 +62,8 @@ def __init__(self, self.s3Encrypt = ModuleConfig(self._config, "s3_encryption") # RDS encryption issue config self.rdsEncrypt = ModuleConfig(self._config, "rds_encryption") - + # Whitelist DDB config + self.whitelistDDB = ModuleConfig(self._config, "whitelist_ddb") # AMI public access issue config self.publicAMIs = ModuleConfig(self._config, "ec2_public_ami")