-
Notifications
You must be signed in to change notification settings - Fork 592
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule I3042 to check for hardcoded partitions, account IDs, and re…
…gions in an ARN (#1805) * New rule to check resources if ARNs use correctly placed Pseudo Parameters instead of hardcoded Partition, Region, and Account Number * Updating HardCodedArnProperties rule to allow for parameterized Region and AccountId and updated related test templates Co-authored-by: Arun Donti <dontirun@gmail.com>
- Loading branch information
Showing
9 changed files
with
780 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
""" | ||
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
SPDX-License-Identifier: MIT-0 | ||
""" | ||
import re | ||
import six | ||
from cfnlint.rules import CloudFormationLintRule | ||
from cfnlint.rules import RuleMatch | ||
|
||
|
||
class HardCodedArnProperties(CloudFormationLintRule): | ||
"""Checks Resources if ARNs use correctly placed Pseudo Parameters instead of hardcoded Partition, Region, and Account Number""" | ||
id = 'I3042' | ||
shortdesc = 'ARNs should use correctly placed Pseudo Parameters' | ||
description = 'Checks Resources if ARNs use correctly placed Pseudo Parameters instead of hardcoded Partition, Region, and Account Number' | ||
source_url = '' | ||
tags = ['resources'] | ||
regex = re.compile(r'arn:(\$\{[^:]*::[^:]*}|[^:]*):[^:]+:(\$\{[^:]*::[^:]*}|[^:]*):(\$\{[^:]*::[^:]*}|[^:]*)') | ||
|
||
def __init__(self): | ||
"""Init""" | ||
super(HardCodedArnProperties, self).__init__() | ||
self.config_definition = { | ||
'partition': { | ||
'default': True, | ||
'type': 'boolean', | ||
}, | ||
'region': { | ||
'default': False, | ||
'type': 'boolean', | ||
}, | ||
'accountId': { | ||
'default': False, | ||
'type': 'boolean', | ||
}, | ||
} | ||
self.configure() | ||
|
||
def _match_values(self, cfnelem, path): | ||
"""Recursively search for values matching the searchRegex""" | ||
values = [] | ||
if isinstance(cfnelem, dict): | ||
for key in cfnelem: | ||
pathprop = path[:] | ||
pathprop.append(key) | ||
values.extend(self._match_values(cfnelem[key], pathprop)) | ||
elif isinstance(cfnelem, list): | ||
for index, item in enumerate(cfnelem): | ||
pathprop = path[:] | ||
pathprop.append(index) | ||
values.extend(self._match_values(item, pathprop)) | ||
else: | ||
# Leaf node | ||
if isinstance(cfnelem, six.string_types): # and re.match(searchRegex, cfnelem): | ||
for variable in re.findall(self.regex, cfnelem): | ||
values.append(path + [variable]) | ||
|
||
return values | ||
|
||
def match_values(self, cfn): | ||
""" | ||
Search for values in all parts of the templates that match the searchRegex | ||
""" | ||
results = [] | ||
results.extend(self._match_values(cfn.template.get('Resources', {}), [])) | ||
# Globals are removed during a transform. They need to be checked manually | ||
results.extend(self._match_values(cfn.template.get('Globals', {}), [])) | ||
return results | ||
|
||
def match(self, cfn): | ||
"""Check CloudFormation Resources""" | ||
matches = [] | ||
|
||
# Get a list of paths to every leaf node string containing at least one ${parameter} | ||
parameter_string_paths = self.match_values(cfn) | ||
# We want to search all of the paths to check if each one contains an 'Fn::Sub' | ||
for parameter_string_path in parameter_string_paths: | ||
path = ['Resources'] + parameter_string_path[:-1] | ||
candidate = parameter_string_path[-1] | ||
|
||
# !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole | ||
# is valid even with aws as the account #. This handles empty string | ||
if self.config['partition'] and not re.match(r'^\$\{\w+}|\$\{AWS::Partition}|$', candidate[0]): | ||
# or not re.match(r'^(\$\{\w+}|\$\{AWS::Region}|)$', candidate[1]) or not re.match(r'^\$\{\w+}|\$\{AWS::AccountId}|aws|$', candidate[2]): | ||
message = 'ARN in Resource {0} contains hardcoded Partition in ARN or incorrectly placed Pseudo Parameters' | ||
matches.append(RuleMatch( | ||
path, | ||
message.format(path[1]) | ||
)) | ||
if self.config['region'] and not re.match(r'^(\$\{\w+}|\$\{AWS::Region}|)$', candidate[1]): | ||
# or or not re.match(r'^\$\{\w+}|\$\{AWS::AccountId}|aws|$', candidate[2]): | ||
message = 'ARN in Resource {0} contains hardcoded Region in ARN or incorrectly placed Pseudo Parameters' | ||
matches.append(RuleMatch( | ||
path, | ||
message.format(path[1]) | ||
)) | ||
if self.config['accountId'] and not re.match(r'^\$\{\w+}|\$\{AWS::AccountId}|aws|$', candidate[2]): | ||
message = 'ARN in Resource {0} contains hardcoded AccountId in ARN or incorrectly placed Pseudo Parameters' | ||
matches.append(RuleMatch( | ||
path, | ||
message.format(path[1]) | ||
)) | ||
|
||
return matches |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.