Skip to content

Commit b4958f2

Browse files
authored
Adding support for new resource types in check-no-public-access api. (#49)
1 parent bc7b762 commit b4958f2

27 files changed

+2739
-19
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ Parses IAM identity-based and resource-based policies from AWS CloudFormation te
185185
| AWS::SNS::TopicPolicy | x | | x |
186186
| AWS::SecretsManager::ResourcePolicy | x | | x |
187187
| AWS::IAM::Role (trust policy) | x | x | x |
188+
| AWS::S3Tables::TableBucket | | | x |
189+
| AWS::ApiGateway::RestApi | | | x |
190+
| AWS::CodeArtifact::Domain | | | x |
191+
| AWS::Backup::BackupVault | | | x |
192+
| AWS::CloudTrail::Dashboard | | | x |
193+
| AWS::CloudTrail::EventDataStore | | | x |
194+
| AWS::S3Express::AccessPoint | | | x |
188195

189196
### Intrinsic function and Pseudo parameter support
190197

cfn_policy_validator/canonical_user_id.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# Resolution of the canonical user in an account which is a possible principal value for a policy and also
1313
# used when evaluating S3 bucket ACLs.
14-
def get_canonical_user(region):
14+
def get_canonical_user(region, logical_name_of_resource=None, resource=None):
1515
global canonical_user_id
1616
if canonical_user_id is not None:
1717
return canonical_user_id
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from cfn_policy_validator import client
7+
from cfn_policy_validator.application_error import ApplicationError
8+
9+
def get_dashboard_created_time(region, resource_name, resource=None):
10+
return get_dashboard_attribute(region, resource_name, 'CreatedTimestamp')
11+
12+
def get_dashboard_status(region, resource_name, resource=None):
13+
return get_dashboard_attribute(region, resource_name, 'Status')
14+
15+
def get_dashboard_type(region, resource_name, resource=None):
16+
return get_dashboard_attribute(region, resource_name, 'Type')
17+
18+
def get_dashboard_updated_time(region, resource_name, resource=None):
19+
return get_dashboard_attribute(region, resource_name, 'UpdatedTimestamp')
20+
21+
def get_eventdatastore_arn(arn_pattern, resource_name, resource, visited_nodes, region):
22+
return get_eventdatastore_arn_from_client(region, resource_name)
23+
24+
def get_eventdatastore_created_time(region, resource_name, resource=None):
25+
return get_eventdatastore_attribute(region, resource_name, 'CreatedTimestamp')
26+
27+
def get_eventdatastore_status(region, resource_name, resource=None):
28+
return get_eventdatastore_attribute(region, resource_name, 'Status')
29+
30+
def get_eventdatastore_updated_time(region, resource_name, resource=None):
31+
return get_eventdatastore_attribute(region, resource_name, 'UpdatedTimestamp')
32+
33+
34+
def get_dashboard_attribute(region, resource_name, attribute):
35+
supported_attributes = ['Type', 'CreatedTimestamp', 'Status', 'UpdatedTimestamp']
36+
cloudtrail_client = client.build('cloudtrail', region)
37+
try:
38+
if attribute not in supported_attributes:
39+
raise ApplicationError(f"Attribute {attribute} is not supported. Supported attributes are {supported_attributes}")
40+
response = cloudtrail_client.get_dashboard(
41+
DashboardId=resource_name
42+
)
43+
return response[attribute]
44+
except Exception as e:
45+
raise ApplicationError(f"Error: {e}")
46+
47+
def get_eventdatastore_arn_from_client(region, resource_name):
48+
cloudtrail_client = client.build('cloudtrail', region)
49+
next_token = None
50+
client_config = {
51+
'MaxResults': 25
52+
}
53+
54+
while True:
55+
if next_token:
56+
client_config['NextToken'] = nextToken
57+
response = cloudtrail_client.list_event_data_stores(**client_config)
58+
for eventdatastore in response['EventDataStores']:
59+
if eventdatastore['Name'] == resource_name:
60+
return eventdatastore['EventDataStoreArn']
61+
nextToken = response.get('NextToken')
62+
if not nextToken:
63+
break
64+
raise ApplicationError(f"CloudTrail Event Data Store {resource_name} not found")
65+
66+
67+
def get_eventdatastore_attribute(region, resource_name, attribute):
68+
cloudtrail_client = client.build('cloudtrail', region)
69+
supported_attributes = ['CreatedTimestamp', 'Status', 'UpdatedTimestamp']
70+
71+
if attribute not in supported_attributes:
72+
raise ApplicationError(f"Attribute {attribute} is not supported. Supported attributes are {supported_attributes}")
73+
74+
arn=get_eventdatastore_arn_from_client(region, resource_name)
75+
event_data_store_response = cloudtrail_client.get_event_data_store(EventDataStore=arn)
76+
ret = event_data_store_response[attribute]
77+
return ret
78+
79+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from cfn_policy_validator import ApplicationError
7+
from cfn_policy_validator.parsers.output import Policy, Resource
8+
9+
class ApiGatewayRestApiPolicyParser:
10+
""" AWS::ApiGateway::RestApi
11+
"""
12+
13+
def __init__(self):
14+
self.rest_api_policies = []
15+
16+
def parse(self, _, resource):
17+
evaluated_resource = resource.eval(rest_api_policy_schema)
18+
properties = evaluated_resource['Properties']
19+
20+
policy_document = properties.get('Policy')
21+
if policy_document is None:
22+
# we don't need to parse resources that don't have policies and policy is optional
23+
return
24+
name = properties['Name']
25+
26+
policy = Policy('Policy', policy_document)
27+
resource = Resource(name, 'AWS::ApiGateway::RestApi', policy)
28+
29+
self.rest_api_policies.append(resource)
30+
31+
def get_policies(self):
32+
return self.rest_api_policies
33+
34+
rest_api_policy_schema = {
35+
'type': 'object',
36+
'properties': {
37+
'Properties': {
38+
'type': 'object',
39+
'properties': {
40+
'Policy': {
41+
'type': 'object'
42+
},
43+
'Name': {
44+
'type': 'string'
45+
}
46+
},
47+
'required': ['Name']
48+
}
49+
},
50+
'required': ['Properties']
51+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from cfn_policy_validator import ApplicationError
7+
from cfn_policy_validator.parsers.output import Policy, Resource
8+
9+
class BackupBackupVaultPolicyParser:
10+
""" AWS::Backup::BackupVault
11+
"""
12+
13+
def __init__(self):
14+
self.backup_vault_policies = []
15+
16+
def parse(self, _, resource):
17+
evaluated_resource = resource.eval(backup_vault_policy_schema)
18+
properties = evaluated_resource['Properties']
19+
20+
policy_document = properties.get('AccessPolicy')
21+
if policy_document is None:
22+
# we don't need to parse resources that don't have policies and policy is optional
23+
return
24+
name = properties['BackupVaultName']
25+
26+
policy = Policy('AccessPolicy', policy_document)
27+
resource = Resource(name, 'AWS::Backup::BackupVault', policy)
28+
self.backup_vault_policies.append(resource)
29+
30+
def get_policies(self):
31+
return self.backup_vault_policies
32+
33+
backup_vault_policy_schema = {
34+
'type': 'object',
35+
'properties': {
36+
'Properties': {
37+
'type': 'object',
38+
'properties': {
39+
'AccessPolicy': {
40+
'type': 'object'
41+
},
42+
'BackupVaultName': {
43+
'type': 'string'
44+
}
45+
},
46+
'required': ['BackupVaultName']
47+
}
48+
},
49+
'required': ['Properties']
50+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from cfn_policy_validator import ApplicationError
7+
from cfn_policy_validator.parsers.output import Policy, Resource
8+
import re
9+
from typing import Tuple, Optional
10+
11+
class CloudTrailResourcePolicyParser:
12+
""" AWS::CloudTrail::ResourcePolicy
13+
"""
14+
15+
def __init__(self):
16+
self.resource_policies = []
17+
18+
@staticmethod
19+
def extract_cloudtrail_resource_info(arn) -> Optional[Tuple[str, str]]:
20+
"""
21+
Extract both the resource type and resource name from a CloudTrail ARN.
22+
23+
Args:
24+
arn (str): The CloudTrail ARN to parse
25+
26+
Returns:
27+
Tuple[str, str] or None: A tuple containing (resource_type, resource_name) if match found,
28+
or None if no match
29+
"""
30+
# Pattern captures both resource type and resource name
31+
pattern = r'arn:aws:cloudtrail:[^:]*:[^:]*:([^/]+)/([^/]+)'
32+
match = re.match(pattern, arn)
33+
34+
if match:
35+
resource_type = match.group(1) # Extract resource type
36+
resource_name = match.group(2) # Extract resource name
37+
return resource_type, resource_name
38+
39+
return None
40+
41+
def parse(self, _, resource):
42+
evaluated_resource = resource.eval(resource_policy_schema)
43+
properties = evaluated_resource['Properties']
44+
45+
policy_document = properties['ResourcePolicy']
46+
resource, name = self.extract_cloudtrail_resource_info(properties['ResourceArn'])
47+
supported_resource_types = {'dashboard': 'AWS::CloudTrail::Dashboard', 'eventdatastore':'AWS::CloudTrail::EventDataStore'}
48+
resource_type = supported_resource_types.get(resource)
49+
if resource_type is None:
50+
raise ApplicationError(f"Unsupported resource type {resource}")
51+
policy = Policy('ResourcePolicy', policy_document)
52+
resource = Resource(name, resource_type, policy)
53+
54+
self.resource_policies.append(resource)
55+
56+
def get_policies(self):
57+
return self.resource_policies
58+
59+
resource_policy_schema = {
60+
'type': 'object',
61+
'properties': {
62+
'Properties': {
63+
'type': 'object',
64+
'properties': {
65+
'ResourcePolicy': {
66+
'type': 'object'
67+
},
68+
'ResourceArn': {
69+
'type': 'string'
70+
}
71+
},
72+
'required': ['ResourcePolicy', 'ResourceArn']
73+
}
74+
},
75+
'required': ['Properties']
76+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from cfn_policy_validator import ApplicationError
7+
from cfn_policy_validator.parsers.output import Policy, Resource
8+
9+
class CodeArtifactDomainPolicyParser:
10+
""" AWS::CodeArtifact::Domain
11+
"""
12+
13+
def __init__(self):
14+
self.code_artifact_policies = []
15+
16+
def parse(self, _, resource):
17+
evaluated_resource = resource.eval(code_artifact_policy_schema)
18+
properties = evaluated_resource['Properties']
19+
20+
policy_document = properties.get('PermissionsPolicyDocument')
21+
if policy_document is None:
22+
# we don't need to parse resources that don't have policies and policy is optional
23+
return
24+
name = properties['DomainName']
25+
26+
policy = Policy('PermissionsPolicyDocument', policy_document)
27+
resource = Resource(name, 'AWS::CodeArtifact::Domain', policy)
28+
29+
self.code_artifact_policies.append(resource)
30+
31+
def get_policies(self):
32+
return self.code_artifact_policies
33+
34+
code_artifact_policy_schema = {
35+
'type': 'object',
36+
'properties': {
37+
'Properties': {
38+
'type': 'object',
39+
'properties': {
40+
'PermissionsPolicyDocument': {
41+
'type': 'object'
42+
},
43+
'DomainName': {
44+
'type': 'string'
45+
}
46+
},
47+
'required': ['DomainName']
48+
}
49+
},
50+
'required': ['Properties']
51+
}

cfn_policy_validator/parsers/resource/parser.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66

77
from cfn_policy_validator.parsers.resource.kms import KmsKeyPolicyParser
88
from cfn_policy_validator.parsers.resource.s3 import S3BucketPolicyParser, S3AccessPointPolicyParser, \
9-
S3MultiRegionAccessPointPolicyParser, S3BucketAclParser
9+
S3MultiRegionAccessPointPolicyParser, S3BucketAclParser, S3TableBucketPolicyParser
1010
from cfn_policy_validator.parsers.resource.sns import SnsTopicPolicyParser
1111
from cfn_policy_validator.parsers.resource.sqs import SqsQueuePolicyParser
1212
from cfn_policy_validator.parsers.resource.lambda_aws import LambdaPermissionPolicyParser, LambdaLayerVersionPermissionParser
1313
from cfn_policy_validator.parsers.resource.secrets_manager import SecretsManagerPolicyParser
14+
from cfn_policy_validator.parsers.resource.api_gateway import ApiGatewayRestApiPolicyParser
15+
from cfn_policy_validator.parsers.resource.code_artifact import CodeArtifactDomainPolicyParser
16+
from cfn_policy_validator.parsers.resource.cloud_trail import CloudTrailResourcePolicyParser
17+
from cfn_policy_validator.parsers.resource.s3_express import S3ExpressAccessPointPolicyParser
18+
from cfn_policy_validator.parsers.resource.backup import BackupBackupVaultPolicyParser
1419

1520
from cfn_policy_validator.parsers.utils.topological_sorter import TopologicalSorter
1621

@@ -35,12 +40,18 @@ def parse(cls, template, account_config, excluded_resource_types={}):
3540
'AWS::S3::MultiRegionAccessPointPolicy': S3MultiRegionAccessPointPolicyParser(),
3641
'AWS::S3::Bucket': S3BucketAclParser(),
3742
'AWS::S3::BucketPolicy': S3BucketPolicyParser(),
43+
'AWS::S3Tables::TableBucketPolicy': S3TableBucketPolicyParser(),
3844
'AWS::SQS::QueuePolicy': SqsQueuePolicyParser(),
3945
'AWS::SNS::TopicPolicy': SnsTopicPolicyParser(),
4046
'AWS::KMS::Key': KmsKeyPolicyParser(),
4147
'AWS::Lambda::Permission': LambdaPermissionPolicyParser(account_config),
4248
'AWS::Lambda::LayerVersionPermission': LambdaLayerVersionPermissionParser(account_config.partition),
43-
'AWS::SecretsManager::ResourcePolicy': SecretsManagerPolicyParser()
49+
'AWS::SecretsManager::ResourcePolicy': SecretsManagerPolicyParser(),
50+
'AWS::ApiGateway::RestApi': ApiGatewayRestApiPolicyParser(),
51+
'AWS::CodeArtifact::Domain' : CodeArtifactDomainPolicyParser(),
52+
'AWS::CloudTrail::ResourcePolicy' : CloudTrailResourcePolicyParser(),
53+
'AWS::S3Express::AccessPoint' : S3ExpressAccessPointPolicyParser(),
54+
'AWS::Backup::BackupVault' : BackupBackupVaultPolicyParser()
4455
}
4556

4657
invoked_parsers = set()

0 commit comments

Comments
 (0)