feat(dynamodb): add new check dynamodb_table_deletion_protection_enabled (#5148)

This commit is contained in:
Daniel Barranquero
2024-09-26 17:19:57 +02:00
committed by GitHub
parent 67bf89537a
commit 0b566f9666
6 changed files with 197 additions and 0 deletions
@@ -54,6 +54,10 @@ class DynamoDB(AWSService):
table.encryption_type = properties["SSEDescription"]["SSEType"]
if table.encryption_type == "KMS":
table.kms_arn = properties["SSEDescription"]["KMSMasterKeyArn"]
table.deletion_protection = properties.get(
"DeletionProtectionEnabled", False
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}"
@@ -219,6 +223,7 @@ class Table(BaseModel):
policy: Optional[dict] = None
region: str
tags: Optional[list] = []
deletion_protection: bool = False
class Cluster(BaseModel):
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "dynamodb_table_deletion_protection_enabled",
"CheckTitle": "Check if DynamoDB tables have deletion protection enabled.",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "dynamodb",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:dynamodb:region:account-id:table/table-name",
"Severity": "medium",
"ResourceType": "AwsDynamoDbTable",
"Description": "This control checks whether an Amazon DynamoDB table has deletion protection enabled to prevent accidental deletion during regular table management operations.",
"Risk": "If deletion protection is not enabled, a DynamoDB table could be accidentally deleted, leading to data loss and potential disruption of business operations.",
"RelatedUrl": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.Basics.html#WorkingWithTables.Basics.DeletionProtection",
"Remediation": {
"Code": {
"CLI": "aws dynamodb update-table --table-name <table-name> --deletion-protection-enabled",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dynamodb-controls.html#dynamodb-6",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable deletion protection for your DynamoDB tables to prevent accidental deletion.",
"Url": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.Basics.html#WorkingWithTables.Basics.DeletionProtection"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.dynamodb.dynamodb_client import dynamodb_client
class dynamodb_table_deletion_protection_enabled(Check):
def execute(self):
findings = []
for table_arn, table in dynamodb_client.tables.items():
report = Check_Report_AWS(self.metadata())
report.region = table.region
report.resource_id = table.name
report.resource_arn = table_arn
report.resource_tags = table.tags
report.status = "FAIL"
report.status_extended = f"DynamoDB table {table.name} does not have deletion protection enabled."
if table.deletion_protection:
report.status = "PASS"
report.status_extended = (
f"DynamoDB table {table.name} has deletion protection enabled."
)
findings.append(report)
return findings
@@ -103,6 +103,7 @@ class Test_DynamoDB_Service:
Tags=[
{"Key": "test", "Value": "test"},
],
DeletionProtectionEnabled=True,
)["TableDescription"]
# DynamoDB client for this test class
aws_provider = set_mocked_aws_provider()
@@ -115,6 +116,7 @@ class Test_DynamoDB_Service:
assert tables.tags == [
{"Key": "test", "Value": "test"},
]
assert tables.deletion_protection
# Test DynamoDB Describe Continuous Backups
@mock_aws
@@ -0,0 +1,133 @@
from unittest import mock
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_REGION_EU_WEST_1,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
class Test_dynamodb_table_deletion_protection_enabled:
@mock_aws
def test_dynamodb_no_tables(self):
from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.dynamodb.dynamodb_table_deletion_protection_enabled.dynamodb_table_deletion_protection_enabled.dynamodb_client",
new=DynamoDB(aws_provider),
):
# Test Check
from prowler.providers.aws.services.dynamodb.dynamodb_table_deletion_protection_enabled.dynamodb_table_deletion_protection_enabled import (
dynamodb_table_deletion_protection_enabled,
)
check = dynamodb_table_deletion_protection_enabled()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_dynamodb_table_with_deletion_protection_enabled(self):
dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1)
table = dynamodb_client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
DeletionProtectionEnabled=True,
BillingMode="PAY_PER_REQUEST",
)["TableDescription"]
from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.dynamodb.dynamodb_table_deletion_protection_enabled.dynamodb_table_deletion_protection_enabled.dynamodb_client",
new=DynamoDB(aws_provider),
):
# Test Check
from prowler.providers.aws.services.dynamodb.dynamodb_table_deletion_protection_enabled.dynamodb_table_deletion_protection_enabled import (
dynamodb_table_deletion_protection_enabled,
)
check = dynamodb_table_deletion_protection_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"DynamoDB table test1 has deletion protection enabled."
)
assert result[0].resource_id == table["TableName"]
assert result[0].resource_arn == table["TableArn"]
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == []
@mock_aws
def test_dynamodb_table_without_deletion_protection_enabled(self):
dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1)
table = dynamodb_client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
DeletionProtectionEnabled=False,
BillingMode="PAY_PER_REQUEST",
)["TableDescription"]
from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.dynamodb.dynamodb_table_deletion_protection_enabled.dynamodb_table_deletion_protection_enabled.dynamodb_client",
new=DynamoDB(aws_provider),
):
# Test Check
from prowler.providers.aws.services.dynamodb.dynamodb_table_deletion_protection_enabled.dynamodb_table_deletion_protection_enabled import (
dynamodb_table_deletion_protection_enabled,
)
check = dynamodb_table_deletion_protection_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"DynamoDB table test1 does not have deletion protection enabled."
)
assert result[0].resource_id == table["TableName"]
assert result[0].resource_arn == table["TableArn"]
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == []