bp-check/services/dynamodb.py

162 lines
5.6 KiB
Python
Raw Normal View History

2024-08-14 01:05:06 +00:00
from models import RuleCheckResult, RuleChecker
from functools import cached_property
from datetime import datetime, timedelta
2024-08-09 06:35:52 +00:00
from dateutil.tz import tzlocal
2024-08-05 02:30:34 +00:00
import boto3
2024-08-14 01:05:06 +00:00
class DynamoDBRuleChecker(RuleChecker):
def __init__(self):
self.client = boto3.client("dynamodb")
self.backup_client = boto3.client("backup")
self.autoscaling_client = boto3.client("application-autoscaling")
2024-08-05 02:30:34 +00:00
2024-08-14 01:05:06 +00:00
@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
2024-08-09 06:35:52 +00:00
]
2024-08-14 01:05:06 +00:00
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