Skip to content

Commit

Permalink
aws - dynamodb cross-account and has-statement (cloud-custodian#9731)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattheidelbaugh authored Oct 10, 2024
1 parent 192b067 commit 6ba8855
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 0 deletions.
49 changes: 49 additions & 0 deletions c7n/resources/dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from c7n.actions import BaseAction, ModifyVpcSecurityGroupsAction
from c7n.filters.kms import KmsRelatedFilter
from c7n.filters.iamaccess import CrossAccountAccessFilter
from c7n import query
from c7n.manager import resources
from c7n.tags import (
Expand All @@ -18,6 +19,7 @@
from c7n.filters import ValueFilter
from c7n.query import RetryPageIterator
from c7n.filters.backup import ConsecutiveAwsBackupsFilter
from c7n.filters.policystatement import HasStatementFilter


class ConfigTable(query.ConfigSource):
Expand Down Expand Up @@ -120,6 +122,53 @@ def __call__(self, r):
return super().__call__(r.get(self.annotation_key, {}))


@Table.filter_registry.register('cross-account')
class CrossAccountTable(CrossAccountAccessFilter):

permissions = ('dynamodb:GetResourcePolicy',)
policy_attribute = 'c7n:Policy'

def process(self, resources, event=None):
client = local_session(self.manager.session_factory).client('dynamodb')
for r in resources:
if self.policy_attribute in r:
continue
result = self.manager.retry(
client.get_resource_policy,
ResourceArn=r['TableArn'],
ignore_err_codes=('ResourceNotFoundException', 'PolicyNotFoundException'))
if result is not None:
r[self.policy_attribute] = result['Policy']
return super().process(resources)


@Table.filter_registry.register('has-statement')
class HasStatementTable(HasStatementFilter):

permissions = ('dynamodb:GetResourcePolicy',)
policy_attribute = 'c7n:Policy'

def get_std_format_args(self, table):
return {
'table_arn': table[self.manager.resource_type.arn],
'account_id': self.manager.config.account_id,
'region': self.manager.config.region,
}

def process(self, resources, event=None):
client = local_session(self.manager.session_factory).client('dynamodb')
for r in resources:
if self.policy_attribute in r:
continue
result = self.manager.retry(
client.get_resource_policy,
ResourceArn=r['TableArn'],
ignore_err_codes=('ResourceNotFoundException', 'PolicyNotFoundException'))
if result is not None:
r[self.policy_attribute] = result['Policy']
return super().process(resources)


@Table.action_registry.register('set-continuous-backup')
class TableContinuousBackupAction(BaseAction):
"""Set continuous backups and point in time recovery (PITR) on a dynamodb table.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"status_code": 200,
"data": {
"Table": {
"AttributeDefinitions": [
{
"AttributeName": "test",
"AttributeType": "S"
}
],
"TableName": "c7n-cross-account",
"KeySchema": [
{
"AttributeName": "test",
"KeyType": "HASH"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": {
"__class__": "datetime",
"year": 2024,
"month": 9,
"day": 18,
"hour": 10,
"minute": 25,
"second": 35,
"microsecond": 60000
},
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/c7n-cross-account",
"TableId": "491eb9f0-b783-491a-a832-9733abc3417e",
"TableClassSummary": {
"TableClass": "STANDARD"
},
"DeletionProtectionEnabled": false
},
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"status_code": 200,
"data": {
"Table": {
"AttributeDefinitions": [
{
"AttributeName": "name",
"AttributeType": "S"
}
],
"TableName": "test-1",
"KeySchema": [
{
"AttributeName": "name",
"KeyType": "HASH"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": {
"__class__": "datetime",
"year": 2024,
"month": 2,
"day": 8,
"hour": 16,
"minute": 20,
"second": 50,
"microsecond": 43000
},
"ProvisionedThroughput": {
"LastDecreaseDateTime": {
"__class__": "datetime",
"year": 2024,
"month": 2,
"day": 8,
"hour": 16,
"minute": 32,
"second": 49,
"microsecond": 707000
},
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/test-1",
"TableId": "ce83b159-c233-4fc7-8617-9d544a0ad076",
"TableClassSummary": {
"TableClass": "STANDARD"
},
"DeletionProtectionEnabled": false
},
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"status_code": 200,
"data": {
"Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Statement1\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::210987654321:root\"},\"Action\":\"dynamodb:BatchGetItem\",\"Resource\":\"arn:aws:dynamodb:us-east-1:123456789012:table/c7n-cross-account\"},{\"Sid\":\"Statement2\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:user/TestUser\"},\"Action\":\"dynamodb:*\",\"Resource\":\"arn:aws:dynamodb:us-east-1:644160558196:table/c7n-cross-account\"}]}",
"RevisionId": "1726669535060",
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"status_code": 400,
"data": {
"Error": {
"Message": "Resource-based policy not found for the provided ResourceArn: arn:aws:dynamodb:us-east-1:123456789012:table/test-1",
"Code": "PolicyNotFoundException"
},
"ResponseMetadata": {},
"message": "Resource-based policy not found for the provided ResourceArn: arn:aws:dynamodb:us-east-1:123456789012:table/test-1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"status_code": 200,
"data": {
"TableNames": [
"c7n-cross-account",
"test-1"
],
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"status_code": 200,
"data": {
"PaginationToken": "",
"ResourceTagMappingList": [],
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"status_code": 200,
"data": {
"Table": {
"AttributeDefinitions": [
{
"AttributeName": "c7n",
"AttributeType": "S"
}
],
"TableName": "c7n-test",
"KeySchema": [
{
"AttributeName": "c7n",
"KeyType": "HASH"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": {
"__class__": "datetime",
"year": 2024,
"month": 10,
"day": 6,
"hour": 12,
"minute": 8,
"second": 33,
"microsecond": 300000
},
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/c7n-test",
"TableId": "ea7da970-2519-441a-a7ec-d15c58f1ffef",
"TableClassSummary": {
"TableClass": "STANDARD"
},
"DeletionProtectionEnabled": false
},
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"status_code": 200,
"data": {
"Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Statement1\",\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"dynamodb:*\",\"Resource\":\"arn:aws:dynamodb:us-east-1:123456789012:table/c7n-test\",\"Condition\":{\"Bool\":{\"aws:SecureTransport\":\"false\"}}}]}",
"RevisionId": "1728230913300",
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"status_code": 200,
"data": {
"TableNames": [
"c7n-test"
],
"ResponseMetadata": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"status_code": 200,
"data": {
"PaginationToken": "",
"ResourceTagMappingList": [],
"ResponseMetadata": {}
}
}
42 changes: 42 additions & 0 deletions tests/test_dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,48 @@ def test_continuous_backup_filter(self):
resources[0]["c7n:continuous-backup"]["PointInTimeRecoveryDescription"]["PointInTimeRecoveryStatus"], # noqa
"DISABLED")

def test_dynamodb_cross_account_filter(self):
session_factory = self.replay_flight_data("test_dynamodb_cross_account_filter")
p = self.load_policy(
{
"name": "dynamodb-cross-account",
"resource": "dynamodb-table",
"filters": ['cross-account'],
},
session_factory=session_factory,
)
resources = p.run()
self.assertEqual(len(resources), 1)
self.assertTrue("c7n:Policy" in resources[0])

def test_dynamodb_has_statement_filter(self):
session_factory = self.replay_flight_data("test_dynamodb_has_statement_filter")
p = self.load_policy(
{
"name": "dynamodb-has-statement",
"resource": "dynamodb-table",
"filters": [
{
"type": "has-statement",
"statements": [
{
"Effect": "Deny",
"Action": "dynamodb:*",
"Principal": "*",
"Condition":
{"Bool": {"aws:SecureTransport": "false"}},
"Resource": "{table_arn}"
}
]
}
],
},
session_factory=session_factory,
)
resources = p.run()
self.assertEqual(len(resources), 1)
self.assertTrue("c7n:Policy" in resources[0])

def test_continuous_backup_action(self):
session_factory = self.replay_flight_data("test_dynamodb_continuous_backup_action")
client = session_factory().client("dynamodb")
Expand Down

0 comments on commit 6ba8855

Please sign in to comment.