diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93e57ddd0e..90c52211a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -105,4 +105,4 @@ repos: description: "Vulture finds unused code in Python programs." entry: bash -c 'vulture --exclude "contrib" --min-confidence 100 .' language: system - files: '.*\.py' + files: '.*\.py' \ No newline at end of file diff --git a/docs/tutorials/configuration_file.md b/docs/tutorials/configuration_file.md index 87872af14e..6975c9a31d 100644 --- a/docs/tutorials/configuration_file.md +++ b/docs/tutorials/configuration_file.md @@ -39,6 +39,7 @@ The following list includes all the AWS checks with configurable variables that | `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_entropy` | Integer | | `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_minutes` | Integer | | `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_actions` | List of Strings | +| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean | ## Azure ### Configurable Checks @@ -209,7 +210,7 @@ aws: "UpdateFunctionCode", "UpdateJob", "UpdateLoginProfile", -] + ] # aws.cloudtrail_threat_detection_enumeration threat_detection_enumeration_entropy: 0.7 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.7 (70%) threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours) @@ -304,7 +305,11 @@ aws: "ListUsers", "LookupEvents", "Search", -] + ] + + # aws.rds_instance_backup_enabled + # Whether to check RDS instance replicas or not + check_rds_instance_replicas: False # Azure Configuration azure: diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index a93e466321..5b71ce8a5c 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -100,154 +100,159 @@ aws: # aws.cloudtrail_threat_detection_privilege_escalation threat_detection_privilege_escalation_threshold: 0.1 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.1 (10%) threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours) - threat_detection_privilege_escalation_actions: [ - "AddPermission", - "AddRoleToInstanceProfile", - "AddUserToGroup", - "AssociateAccessPolicy", - "AssumeRole", - "AttachGroupPolicy", - "AttachRolePolicy", - "AttachUserPolicy", - "ChangePassword", - "CreateAccessEntry", - "CreateAccessKey", - "CreateDevEndpoint", - "CreateEventSourceMapping", - "CreateFunction", - "CreateGroup", - "CreateJob", - "CreateKeyPair", - "CreateLoginProfile", - "CreatePipeline", - "CreatePolicyVersion", - "CreateRole", - "CreateStack", - "DeleteRolePermissionsBoundary", - "DeleteRolePolicy", - "DeleteUserPermissionsBoundary", - "DeleteUserPolicy", - "DetachRolePolicy", - "DetachUserPolicy", - "GetCredentialsForIdentity", - "GetId", - "GetPolicyVersion", - "GetUserPolicy", - "Invoke", - "ModifyInstanceAttribute", - "PassRole", - "PutGroupPolicy", - "PutPipelineDefinition", - "PutRolePermissionsBoundary", - "PutRolePolicy", - "PutUserPermissionsBoundary", - "PutUserPolicy", - "ReplaceIamInstanceProfileAssociation", - "RunInstances", - "SetDefaultPolicyVersion", - "UpdateAccessKey", - "UpdateAssumeRolePolicy", - "UpdateDevEndpoint", - "UpdateEventSourceMapping", - "UpdateFunctionCode", - "UpdateJob", - "UpdateLoginProfile", -] + threat_detection_privilege_escalation_actions: + [ + "AddPermission", + "AddRoleToInstanceProfile", + "AddUserToGroup", + "AssociateAccessPolicy", + "AssumeRole", + "AttachGroupPolicy", + "AttachRolePolicy", + "AttachUserPolicy", + "ChangePassword", + "CreateAccessEntry", + "CreateAccessKey", + "CreateDevEndpoint", + "CreateEventSourceMapping", + "CreateFunction", + "CreateGroup", + "CreateJob", + "CreateKeyPair", + "CreateLoginProfile", + "CreatePipeline", + "CreatePolicyVersion", + "CreateRole", + "CreateStack", + "DeleteRolePermissionsBoundary", + "DeleteRolePolicy", + "DeleteUserPermissionsBoundary", + "DeleteUserPolicy", + "DetachRolePolicy", + "DetachUserPolicy", + "GetCredentialsForIdentity", + "GetId", + "GetPolicyVersion", + "GetUserPolicy", + "Invoke", + "ModifyInstanceAttribute", + "PassRole", + "PutGroupPolicy", + "PutPipelineDefinition", + "PutRolePermissionsBoundary", + "PutRolePolicy", + "PutUserPermissionsBoundary", + "PutUserPolicy", + "ReplaceIamInstanceProfileAssociation", + "RunInstances", + "SetDefaultPolicyVersion", + "UpdateAccessKey", + "UpdateAssumeRolePolicy", + "UpdateDevEndpoint", + "UpdateEventSourceMapping", + "UpdateFunctionCode", + "UpdateJob", + "UpdateLoginProfile", + ] # aws.cloudtrail_threat_detection_enumeration threat_detection_enumeration_threshold: 0.1 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%) threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours) - threat_detection_enumeration_actions: [ - "DescribeAccessEntry", - "DescribeAccountAttributes", - "DescribeAvailabilityZones", - "DescribeBundleTasks", - "DescribeCarrierGateways", - "DescribeClientVpnRoutes", - "DescribeCluster", - "DescribeDhcpOptions", - "DescribeFlowLogs", - "DescribeImages", - "DescribeInstanceAttribute", - "DescribeInstanceInformation", - "DescribeInstanceTypes", - "DescribeInstances", - "DescribeInstances", - "DescribeKeyPairs", - "DescribeLogGroups", - "DescribeLogStreams", - "DescribeOrganization", - "DescribeRegions", - "DescribeSecurityGroups", - "DescribeSnapshotAttribute", - "DescribeSnapshotTierStatus", - "DescribeSubscriptionFilters", - "DescribeTransitGatewayMulticastDomains", - "DescribeVolumes", - "DescribeVolumesModifications", - "DescribeVpcEndpointConnectionNotifications", - "DescribeVpcs", - "GetAccount", - "GetAccountAuthorizationDetails", - "GetAccountSendingEnabled", - "GetBucketAcl", - "GetBucketLogging", - "GetBucketPolicy", - "GetBucketReplication", - "GetBucketVersioning", - "GetCallerIdentity", - "GetCertificate", - "GetConsoleScreenshot", - "GetCostAndUsage", - "GetDetector", - "GetEbsDefaultKmsKeyId", - "GetEbsEncryptionByDefault", - "GetFindings", - "GetFlowLogsIntegrationTemplate", - "GetIdentityVerificationAttributes", - "GetInstances", - "GetIntrospectionSchema", - "GetLaunchTemplateData", - "GetLaunchTemplateData", - "GetLogRecord", - "GetParameters", - "GetPolicyVersion", - "GetPublicAccessBlock", - "GetQueryResults", - "GetRegions", - "GetSMSAttributes", - "GetSMSSandboxAccountStatus", - "GetSendQuota", - "GetTransitGatewayRouteTableAssociations", - "GetUserPolicy", - "HeadObject", - "ListAccessKeys", - "ListAccounts", - "ListAllMyBuckets", - "ListAssociatedAccessPolicies", - "ListAttachedUserPolicies", - "ListClusters", - "ListDetectors", - "ListDomains", - "ListFindings", - "ListHostedZones", - "ListIPSets", - "ListIdentities", - "ListInstanceProfiles", - "ListObjects", - "ListOrganizationalUnitsForParent", - "ListOriginationNumbers", - "ListPolicyVersions", - "ListRoles", - "ListRoles", - "ListRules", - "ListServiceQuotas", - "ListSubscriptions", - "ListTargetsByRule", - "ListTopics", - "ListUsers", - "LookupEvents", - "Search", -] + threat_detection_enumeration_actions: + [ + "DescribeAccessEntry", + "DescribeAccountAttributes", + "DescribeAvailabilityZones", + "DescribeBundleTasks", + "DescribeCarrierGateways", + "DescribeClientVpnRoutes", + "DescribeCluster", + "DescribeDhcpOptions", + "DescribeFlowLogs", + "DescribeImages", + "DescribeInstanceAttribute", + "DescribeInstanceInformation", + "DescribeInstanceTypes", + "DescribeInstances", + "DescribeInstances", + "DescribeKeyPairs", + "DescribeLogGroups", + "DescribeLogStreams", + "DescribeOrganization", + "DescribeRegions", + "DescribeSecurityGroups", + "DescribeSnapshotAttribute", + "DescribeSnapshotTierStatus", + "DescribeSubscriptionFilters", + "DescribeTransitGatewayMulticastDomains", + "DescribeVolumes", + "DescribeVolumesModifications", + "DescribeVpcEndpointConnectionNotifications", + "DescribeVpcs", + "GetAccount", + "GetAccountAuthorizationDetails", + "GetAccountSendingEnabled", + "GetBucketAcl", + "GetBucketLogging", + "GetBucketPolicy", + "GetBucketReplication", + "GetBucketVersioning", + "GetCallerIdentity", + "GetCertificate", + "GetConsoleScreenshot", + "GetCostAndUsage", + "GetDetector", + "GetEbsDefaultKmsKeyId", + "GetEbsEncryptionByDefault", + "GetFindings", + "GetFlowLogsIntegrationTemplate", + "GetIdentityVerificationAttributes", + "GetInstances", + "GetIntrospectionSchema", + "GetLaunchTemplateData", + "GetLaunchTemplateData", + "GetLogRecord", + "GetParameters", + "GetPolicyVersion", + "GetPublicAccessBlock", + "GetQueryResults", + "GetRegions", + "GetSMSAttributes", + "GetSMSSandboxAccountStatus", + "GetSendQuota", + "GetTransitGatewayRouteTableAssociations", + "GetUserPolicy", + "HeadObject", + "ListAccessKeys", + "ListAccounts", + "ListAllMyBuckets", + "ListAssociatedAccessPolicies", + "ListAttachedUserPolicies", + "ListClusters", + "ListDetectors", + "ListDomains", + "ListFindings", + "ListHostedZones", + "ListIPSets", + "ListIdentities", + "ListInstanceProfiles", + "ListObjects", + "ListOrganizationalUnitsForParent", + "ListOriginationNumbers", + "ListPolicyVersions", + "ListRoles", + "ListRoles", + "ListRules", + "ListServiceQuotas", + "ListSubscriptions", + "ListTargetsByRule", + "ListTopics", + "ListUsers", + "LookupEvents", + "Search", + ] + # aws.rds_instance_backup_enabled + # Whether to check RDS instance replicas or not + check_rds_instance_replicas: False # Azure Configuration azure: diff --git a/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py b/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py index 39f63d0bdc..a884dab116 100644 --- a/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py +++ b/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py @@ -20,6 +20,10 @@ class rds_instance_backup_enabled(Check): f"RDS Instance {db_instance.id} does not have backup enabled." ) + if db_instance.replica_source and not rds_client.audit_config.get( + "check_rds_instance_replicas", False + ): + continue findings.append(report) return findings diff --git a/prowler/providers/aws/services/rds/rds_service.py b/prowler/providers/aws/services/rds/rds_service.py index e2ac030a65..05632fa200 100644 --- a/prowler/providers/aws/services/rds/rds_service.py +++ b/prowler/providers/aws/services/rds/rds_service.py @@ -77,6 +77,9 @@ class RDS(AWSService): cluster_arn=f"arn:{self.audited_partition}:rds:{regional_client.region}:{self.audited_account}:cluster:{instance.get('DBClusterIdentifier')}", region=regional_client.region, tags=instance.get("TagList", []), + replica_source=instance.get( + "ReadReplicaSourceDBInstanceIdentifier" + ), ) ) except Exception as error: @@ -305,6 +308,7 @@ class DBInstance(BaseModel): cluster_arn: Optional[str] region: str tags: Optional[list] = [] + replica_source: Optional[str] class DBCluster(BaseModel): diff --git a/tests/config/config_test.py b/tests/config/config_test.py index bf292174e1..5f0ead8a2f 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -60,6 +60,7 @@ config_aws = { ], "organizations_enabled_regions": [], "organizations_trusted_delegated_administrators": [], + "check_rds_instance_replicas": False, } config_azure = {"shodan_api_key": None} diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index a3a450ada3..a4d9590eb4 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -65,6 +65,10 @@ aws: organizations_enabled_regions: [] organizations_trusted_delegated_administrators: [] + # aws.rds_instance_backup_enabled + # Whether to check RDS instance replicas or not + check_rds_instance_replicas: False + # Azure Configuration azure: # Azure Network Configuration diff --git a/tests/config/fixtures/config_old.yaml b/tests/config/fixtures/config_old.yaml index c43a488add..11f17af538 100644 --- a/tests/config/fixtures/config_old.yaml +++ b/tests/config/fixtures/config_old.yaml @@ -60,3 +60,7 @@ obsolete_lambda_runtimes: # ] organizations_enabled_regions: [] organizations_trusted_delegated_administrators: [] + +# aws.rds_instance_backup_enabled +# Whether to check RDS instance replicas or not +check_rds_instance_replicas: False diff --git a/tests/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled_test.py b/tests/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled_test.py index 653fa8872c..3aa09e5c1a 100644 --- a/tests/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled_test.py +++ b/tests/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled_test.py @@ -1,4 +1,3 @@ -from re import search from unittest import mock import botocore @@ -58,8 +57,9 @@ class Test_rds_instance_backup_enabled: @mock_aws def test_rds_instance_no_backup(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) + instance_id = "db-master-1" conn.create_db_instance( - DBInstanceIdentifier="db-master-1", + DBInstanceIdentifier=instance_id, AllocatedStorage=10, Engine="postgres", DBName="staging-postgres", @@ -90,9 +90,9 @@ class Test_rds_instance_backup_enabled: assert len(result) == 1 assert result[0].status == "FAIL" - assert search( - "does not have backup enabled", - result[0].status_extended, + assert ( + result[0].status_extended + == f"RDS Instance {instance_id} does not have backup enabled." ) assert result[0].resource_id == "db-master-1" assert result[0].region == AWS_REGION_US_EAST_1 @@ -105,13 +105,15 @@ class Test_rds_instance_backup_enabled: @mock_aws def test_rds_instance_with_backup(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) + instance_id = "db-master-1" + retention_period = 10 conn.create_db_instance( - DBInstanceIdentifier="db-master-1", + DBInstanceIdentifier=instance_id, AllocatedStorage=10, Engine="postgres", DBName="staging-postgres", DBInstanceClass="db.m1.small", - BackupRetentionPeriod=10, + BackupRetentionPeriod=retention_period, ) from prowler.providers.aws.services.rds.rds_service import RDS @@ -135,9 +137,9 @@ class Test_rds_instance_backup_enabled: assert len(result) == 1 assert result[0].status == "PASS" - assert search( - "has backup enabled", - result[0].status_extended, + assert ( + result[0].status_extended + == f"RDS Instance {instance_id} has backup enabled with retention period {retention_period} days." ) assert result[0].resource_id == "db-master-1" assert result[0].region == AWS_REGION_US_EAST_1 @@ -146,3 +148,133 @@ class Test_rds_instance_backup_enabled: == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-1" ) assert result[0].resource_tags == [] + + @mock_aws + def test_rds_instance_replica_with_backup_checking_replicas(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + instance_id = "db-master-1" + retention_period = 10 + conn.create_db_instance( + DBInstanceIdentifier=instance_id, + AllocatedStorage=10, + Engine="postgres", + DBName="staging-postgres", + DBInstanceClass="db.m1.small", + BackupRetentionPeriod=retention_period, + ) + replica_id = "db-replica-1" + conn.create_db_instance_read_replica( + DBInstanceIdentifier=replica_id, + SourceDBInstanceIdentifier="db-master-1", + DBInstanceClass="db.m1.small", + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], audit_config={"check_rds_instance_replicas": True} + ) + + with mock.patch( + "prowler.providers.common.common.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled.rds_client", + new=RDS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled import ( + rds_instance_backup_enabled, + ) + + check = rds_instance_backup_enabled() + result = check.execute() + + assert len(result) == 2 + + for finding in result: + if finding.resource_id == instance_id: + assert finding.status == "PASS" + assert ( + finding.status_extended + == f"RDS Instance {instance_id} has backup enabled with retention period {retention_period} days." + ) + + assert finding.resource_id == instance_id + assert finding.region == AWS_REGION_US_EAST_1 + assert ( + finding.resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:{instance_id}" + ) + assert finding.resource_tags == [] + if finding.resource_id == replica_id: + assert finding.status == "PASS" + assert ( + finding.status_extended + == f"RDS Instance {replica_id} has backup enabled with retention period {retention_period} days." + ) + + assert finding.resource_id == replica_id + assert finding.region == AWS_REGION_US_EAST_1 + assert ( + finding.resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:{replica_id}" + ) + assert finding.resource_tags == [] + + @mock_aws + def test_rds_instance_replica_with_backup_default_config(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + instance_id = "db-master-1" + retention_period = 10 + conn.create_db_instance( + DBInstanceIdentifier=instance_id, + AllocatedStorage=10, + Engine="postgres", + DBName="staging-postgres", + DBInstanceClass="db.m1.small", + BackupRetentionPeriod=retention_period, + ) + replica_id = "db-replica-1" + conn.create_db_instance_read_replica( + DBInstanceIdentifier=replica_id, + SourceDBInstanceIdentifier=instance_id, + DBInstanceClass="db.m1.small", + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], + ) + + with mock.patch( + "prowler.providers.common.common.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled.rds_client", + new=RDS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled import ( + rds_instance_backup_enabled, + ) + + check = rds_instance_backup_enabled() + result = check.execute() + + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"RDS Instance {instance_id} has backup enabled with retention period {retention_period} days." + ) + + assert result[0].resource_id == instance_id + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:{instance_id}" + ) + assert result[0].resource_tags == []