162 lines
5.6 KiB
Python
162 lines
5.6 KiB
Python
from models import RuleCheckResult, RuleChecker
|
|
from functools import cached_property
|
|
from datetime import datetime, timedelta
|
|
from dateutil.tz import tzlocal
|
|
import boto3
|
|
|
|
|
|
class DynamoDBRuleChecker(RuleChecker):
|
|
def __init__(self):
|
|
self.client = boto3.client("dynamodb")
|
|
self.backup_client = boto3.client("backup")
|
|
self.autoscaling_client = boto3.client("application-autoscaling")
|
|
|
|
@cached_property
|
|
def tables(self):
|
|
table_names = self.client.list_tables()["TableNames"]
|
|
return [
|
|
self.client.describe_table(TableName=table_name)["Table"]
|
|
for table_name in table_names
|
|
]
|
|
|
|
def dynamodb_autoscaling_enabled(self):
|
|
compliant_resources = []
|
|
non_compliant_resources = []
|
|
|
|
for table in self.tables:
|
|
if (
|
|
table.get("BillingModeSummary", {}).get("BillingMode")
|
|
== "PAY_PER_REQUEST"
|
|
):
|
|
compliant_resources.append(table["TableArn"])
|
|
continue
|
|
|
|
scaling_policies = self.autoscaling_client.describe_scaling_policies(
|
|
ServiceNamespace="dynamodb", ResourceId=f"table/{table['TableName']}"
|
|
)["ScalingPolicies"]
|
|
scaling_policy_dimensions = [
|
|
policy["ScalableDimension"] for policy in scaling_policies
|
|
]
|
|
|
|
if (
|
|
"dynamodb:table:ReadCapacityUnits" in scaling_policy_dimensions
|
|
and "dynamodb:table:WriteCapacityUnits" in scaling_policy_dimensions
|
|
):
|
|
compliant_resources.append(table["TableArn"])
|
|
else:
|
|
non_compliant_resources.append(table["TableArn"])
|
|
|
|
return RuleCheckResult(
|
|
passed=not non_compliant_resources,
|
|
compliant_resources=compliant_resources,
|
|
non_compliant_resources=non_compliant_resources,
|
|
)
|
|
|
|
def dynamodb_last_backup_recovery_point_created(self):
|
|
compliant_resources = []
|
|
non_compliant_resources = []
|
|
|
|
for table in self.tables:
|
|
recovery_points = self.backup_client.list_recovery_points_by_resource(
|
|
ResourceArn=table["TableArn"]
|
|
)["RecoveryPoints"]
|
|
if not recovery_points:
|
|
non_compliant_resources.append(table["TableArn"])
|
|
continue
|
|
|
|
latest_recovery_point = sorted(
|
|
[recovery_point["CreationDate"] for recovery_point in recovery_points]
|
|
)[-1]
|
|
|
|
if datetime.now(tz=tzlocal()) - latest_recovery_point > timedelta(days=1):
|
|
non_compliant_resources.append(table["TableArn"])
|
|
else:
|
|
compliant_resources.append(table["TableArn"])
|
|
|
|
return RuleCheckResult(
|
|
passed=not non_compliant_resources,
|
|
compliant_resources=compliant_resources,
|
|
non_compliant_resources=non_compliant_resources,
|
|
)
|
|
|
|
def dynamodb_pitr_enabled(self):
|
|
compliant_resources = []
|
|
non_compliant_resources = []
|
|
|
|
for table in self.tables:
|
|
backup = self.client.describe_continuous_backups(
|
|
TableName=table["TableName"]
|
|
)["ContinuousBackupsDescription"]
|
|
|
|
if (
|
|
backup["PointInTimeRecoveryDescription"]["PointInTimeRecoveryStatus"]
|
|
== "ENABLED"
|
|
):
|
|
compliant_resources.append(table["TableArn"])
|
|
else:
|
|
non_compliant_resources.append(table["TableArn"])
|
|
|
|
return RuleCheckResult(
|
|
passed=not non_compliant_resources,
|
|
compliant_resources=compliant_resources,
|
|
non_compliant_resources=non_compliant_resources,
|
|
)
|
|
|
|
def dynamodb_table_deletion_protection_enabled(self):
|
|
compliant_resources = []
|
|
non_compliant_resources = []
|
|
|
|
for table in self.tables:
|
|
if table["DeletionProtectionEnabled"] == True:
|
|
compliant_resources.append(table["TableArn"])
|
|
else:
|
|
non_compliant_resources.append(table["TableArn"])
|
|
|
|
return RuleCheckResult(
|
|
passed=not non_compliant_resources,
|
|
compliant_resources=compliant_resources,
|
|
non_compliant_resources=non_compliant_resources,
|
|
)
|
|
|
|
def dynamodb_table_encrypted_kms(self):
|
|
compliant_resources = []
|
|
non_compliant_resources = []
|
|
|
|
for table in self.tables:
|
|
if (
|
|
"SSEDescription" in table
|
|
and table["SSEDescription"]["Status"] == "ENABLED"
|
|
and table["SSEDescription"]["SSEType"] == "KMS"
|
|
):
|
|
compliant_resources.append(table["TableArn"])
|
|
else:
|
|
non_compliant_resources.append(table["TableArn"])
|
|
|
|
return RuleCheckResult(
|
|
passed=not non_compliant_resources,
|
|
compliant_resources=compliant_resources,
|
|
non_compliant_resources=non_compliant_resources,
|
|
)
|
|
|
|
def dynamodb_table_encryption_enabled(self):
|
|
compliant_resources = []
|
|
non_compliant_resources = []
|
|
|
|
for table in self.tables:
|
|
if (
|
|
"SSEDescription" in table
|
|
and table["SSEDescription"]["Status"] == "ENABLED"
|
|
):
|
|
compliant_resources.append(table["TableArn"])
|
|
else:
|
|
non_compliant_resources.append(table["TableArn"])
|
|
|
|
return RuleCheckResult(
|
|
passed=not non_compliant_resources,
|
|
compliant_resources=compliant_resources,
|
|
non_compliant_resources=non_compliant_resources,
|
|
)
|
|
|
|
|
|
rule_checker = DynamoDBRuleChecker
|