Skip to content

Commit 58c0fe2

Browse files
authored
Add CheckNoPublicAccess command and update CheckAccessNotGranted with resource support (#41)
1 parent 1280a24 commit 58c0fe2

File tree

8 files changed

+556
-45
lines changed

8 files changed

+556
-45
lines changed

README.md

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Parses IAM identity-based and resource-based policies from AWS CloudFormation te
115115
cfn-policy-validator check-access-not-granted --template-path ./my-template.json --region us-east-1 --actions "secretsmanager:DeleteSecret"
116116
```
117117

118-
Parses IAM identity-based and resource-based policies from AWS CloudFormation templates and evaluated CloudFormation intrinsic functions and pseudo parameters. Then runs the policies through IAM Access Analyzer for a custom check against a list of IAM actions. Returns the findings from the custom check in JSON format. Exits with a non-zero error code if any findings categorized as blocking, based on access granted to at least one of the listed IAM actions, are found in your template. Exits with an error code of zero if all findings are non-blocking or there are no findings.
118+
Parses IAM identity-based and resource-based policies from AWS CloudFormation templates. Then runs the policies through IAM Access Analyzer for a custom check against a list of IAM actions and/or resource ARNs. If both actions and resources are provided, a custom check will be run to determine whether access is granted to allow the specified actions on the specified resources. Returns the findings from the custom check in JSON format. Exits with a non-zero error code if any findings categorized as blocking, based on access granted to at least one of the listed IAM actions and/or resources, are found in your template. Exits with an error code of zero if all findings are non-blocking or there are no findings.
119119

120120
| Arguments | Required | Options | Description |
121121
| --------- | -------- | ---------| ----------- |
@@ -127,6 +127,27 @@ Parses IAM identity-based and resource-based policies from AWS CloudFormation te
127127
| --enable-logging | | | Enables log output to stdout |
128128
| --ignore-finding | | FINDING_CODE,RESOURCE_NAME,RESOURCE_NAME.FINDING_CODE | Allow validation failures to be ignored. Specify as a comma separated list of findings to be ignored. Can be individual finding codes (e.g. "PASS_ROLE_WITH_STAR_IN_RESOURCE"), a specific resource name (e.g. "MyResource"), or a combination of both separated by a period.(e.g. "MyResource.PASS_ROLE_WITH_STAR_IN_RESOURCE"). Names of finding codes may change in IAM Access Analyzer over time.
129129
| --actions | Yes | ACTION,ACTION,ACTION | List of comma-separated actions. |
130+
| -- resources | At least one of actions or resources is required. | RESOURCE,RESOURCE,RESOURCE | List of comma-separated resource ARNs, maximum 100 resources ARNs. |
131+
| --treat-findings-as-non-blocking | | | When not specified, the tool detects any findings, it will exit with a non-zero exit code. When specified, the tool exits with an exit code of 0. |
132+
| --allow-dynamic-ref-without-version | | | Override the default behavior and allow dynamic SSM references without version numbers. The version number ensures that the SSM parameter value that was validated is the one that is deployed. |
133+
| --exclude-resource-types | | AWS::SERVICE::RESOURCE, AWS::SERVICE::RESOURCE | List of comma-separated resource types. Resource types should be the same as Cloudformation template resource names such as AWS::IAM::Role, AWS::S3::Bucket |
134+
135+
**check-no-public-access**
136+
```bash
137+
cfn-policy-validator check-no-public-access --template-path ./my-template.json --region us-east-1
138+
```
139+
140+
Parses resource-based policies from AWS CloudFormation templates. Then runs the policies through IAM Access Analyzer for a custom check for public access to resources. Returns the findings from the custom check in JSON format. Exits with a non-zero error code if any findings categorized as blocking, based on whether public access is granted to at least one of the resources, are found in your template. Exits with an error code of zero if all findings are non-blocking or there are no findings.
141+
142+
| Arguments | Required | Options | Description |
143+
| --------- | -------- | ---------| ----------- |
144+
| --template-path | Yes | FILE_NAME | The path to the CloudFormation template. |
145+
| --region | Yes | REGION | The destination region the resources will be deployed to. |
146+
| --parameters | | KEY=VALUE [KEY=VALUE ...] | Keys and values for CloudFormation template parameters. Only parameters that are referenced by IAM policies in the template are required. |
147+
| --template-configuration-file | | FILE_PATH.json | A JSON formatted file that specifies template parameter values, a stack policy, and tags. Only parameters are used from this file. Everything else is ignored. Identical values passed in the --parameters flag override parameters in this file. See CloudFormation documentation for file format: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-cfn-artifacts.html#w2ab1c21c15c15
148+
| --profile | | PROFILE | The named profile to use for AWS API calls. |
149+
| --enable-logging | | | Enables log output to stdout |
150+
| --ignore-finding | | FINDING_CODE,RESOURCE_NAME,RESOURCE_NAME.FINDING_CODE | Allow validation failures to be ignored. Specify as a comma separated list of findings to be ignored. Can be individual finding codes (e.g. "PASS_ROLE_WITH_STAR_IN_RESOURCE"), a specific resource name (e.g. "MyResource"), or a combination of both separated by a period.(e.g. "MyResource.PASS_ROLE_WITH_STAR_IN_RESOURCE"). Names of finding codes may change in IAM Access Analyzer over time.
130151
| --treat-findings-as-non-blocking | | | When not specified, the tool detects any findings, it will exit with a non-zero exit code. When specified, the tool exits with an exit code of 0. |
131152
| --allow-dynamic-ref-without-version | | | Override the default behavior and allow dynamic SSM references without version numbers. The version number ensures that the SSM parameter value that was validated is the one that is deployed. |
132153
| --exclude-resource-types | | AWS::SERVICE::RESOURCE, AWS::SERVICE::RESOURCE | List of comma-separated resource types. Resource types should be the same as Cloudformation template resource names such as AWS::IAM::Role, AWS::S3::Bucket |
@@ -152,18 +173,18 @@ Parses IAM identity-based and resource-based policies from AWS CloudFormation te
152173

153174
### Supported resource-based policies
154175

155-
| CloudFormation Resource Type | Policy best practice checks | Access previews (check for external access) |
156-
| ---------------------------- | :----------------: | :-----------------------------------------: |
157-
| AWS::KMS::Key | x | x |
158-
| AWS::Lambda::Permission | x | |
159-
| AWS::Lambda::LayerVersionPermission | x | |
160-
| AWS::S3::BucketPolicy | x | x |
161-
| AWS::S3::AccessPoint | x | x |
176+
| CloudFormation Resource Type | Policy best practice checks | Access previews (check for external access) | Custom Policy Check (public access)
177+
| ---------------------------- | :----------------: | :-----------------------------------------: | :-----------------------------------------:
178+
| AWS::KMS::Key | x | x | x |
179+
| AWS::Lambda::Permission | x | | x |
180+
| AWS::Lambda::LayerVersionPermission | x | | x |
181+
| AWS::S3::BucketPolicy | x | x | x |
182+
| AWS::S3::AccessPoint | x | x | x |
162183
| AWS::S3::MultiRegionAccessPoint | x | x |
163-
| AWS::SQS::QueuePolicy | x | x |
164-
| AWS::SNS::TopicPolicy | x | |
165-
| AWS::SecretsManager::ResourcePolicy | x | |
166-
| AWS::IAM::Role (trust policy) | x | x |
184+
| AWS::SQS::QueuePolicy | x | x | x |
185+
| AWS::SNS::TopicPolicy | x | | x |
186+
| AWS::SecretsManager::ResourcePolicy | x | | x |
187+
| AWS::IAM::Role (trust policy) | x | x | x |
167188

168189
### Intrinsic function and Pseudo parameter support
169190

@@ -236,6 +257,7 @@ The principal used to execute the cfn-policy-validator requires the following pe
236257
"access-analyzer:CreateAnalyzer",
237258
"access-analyzer:CheckNoNewAccess",
238259
"access-analyzer:CheckAccessNotGranted",
260+
"access-analyzer:CheckNoPublicAccess",
239261
"s3:ListAllMyBuckets",
240262
"cloudformation:ListExports",
241263
"ssm:GetParameter"
@@ -268,6 +290,7 @@ The principal used to execute the cfn-policy-validator requires the following pe
268290
| access-analyzer:CreateAnalyzer | (Optional) Create an analyzer if one does not already exist in the account. Optional if account has analyzer already. |
269291
| access-analyzer:CheckNoNewAccess | Called for each policy to validate against a reference policy to compare permissions. |
270292
| access-analyzer:CheckAccessNotGranted | Called for each policy to validate that it does not grant access to a list of IAM actions, considered as critical permissions, provided as input. |
293+
| access-analyzer:CheckNoPublicAccess | Called for each policy to validate that it does not grant public access to supported resource types. |
271294
| iam:CreateServiceLinkedRole | (Optional) Create a service linked role if an analyzer must be created in account. Optional if account has analyzer already. |
272295
| s3:ListAllMyBuckets | Retrieve the canonical ID of the account. |
273296
| cloudformation:ListExports | List CloudFormation exports to be used with Fn::ImportValue |
@@ -295,6 +318,10 @@ Access previews take in the entire context of your AWS account, not just the S3
295318

296319
Creating an access preview for a SecretsManager Secret requires a KMSKeyId. The cfn-policy-validator does not yet support parsing the KMS Key from the environment. When no KMSKeyId is supplied, the CreateAccessPreview API uses the default CMK in the account which is not externally accessible.
297320

321+
### What is the distinction between Access Previews and CheckNoPublicAccess?
322+
323+
CheckNoPublicAccess custom policy checks differ from Access Previews because CheckNoPublicAccess checks do not require any account or external access analyzer context. Note that a charge is associated with each custom policy check.
324+
298325

299326
### Examples
300327

@@ -346,6 +373,12 @@ cfn-policy-validator check-no-new-access --template-path ./my-template.json --re
346373
cfn-policy-validator check-access-not-granted --template-path ./my-template.json --region us-east-1 --actions "secretsmanager:DeleteSecret" --ignore-findings MyResource1
347374
```
348375

376+
Custom policy check and ignore findings for a specific resource
377+
```bash
378+
cfn-policy-validator check-no-new-access --template-path ./my-template.json --region us-east-1 --reference-policy ./my-reference-policy.json --reference-policy-type identity --ignore-findings MyResource1
379+
cfn-policy-validator check-access-not-granted --template-path ./my-template.json --region us-east-1 --actions "secretsmanager:DeleteSecret" --ignore-findings MyResource1
380+
```
381+
349382
Validate and ignore all findings for `MyResource1` and finding code `PASS_ROLE_WITH_STAR_IN_RESOURCE` for all resources
350383
```bash
351384
cfn-policy-validator validate --template-path ./my-template.json --region us-east-1 --ignore-finding MyResource1,PASS_ROLE_WITH_STAR_IN_RESOURCE

cfn_policy_validator/main.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,29 @@ def compare_from_cli(arguments):
5959

6060
# consumable when running as CLI
6161
def check_access_from_cli(arguments):
62-
LOGGER.info(f'Checking that template {arguments.template_path} is not granting actions: {arguments.actions}')
63-
report = policy_analysis.check_access(get_parser_output(arguments), arguments.actions, arguments.ignore_finding, arguments.findings_are_blocking)
64-
report.print()
62+
if not arguments.actions and not arguments.resources:
63+
raise ApplicationError("At least one of --actions or --resources must be specified.")
64+
else:
65+
report = policy_analysis.check_access(get_parser_output(arguments), arguments.ignore_finding, arguments.findings_are_blocking, arguments.actions, arguments.resources)
66+
report.print()
67+
if report.has_blocking_findings():
68+
exit(2)
69+
else:
70+
exit(0)
71+
72+
73+
# consumable when running as CLI
74+
def check_no_public_access_from_cli(arguments):
75+
LOGGER.info(f'Checking that template {arguments.template_path} does not grant public access to resources.')
76+
report = policy_analysis.check_no_public_access(get_parser_output(arguments), arguments.ignore_finding, arguments.findings_are_blocking)
6577

78+
report.print()
6679
if report.has_blocking_findings():
6780
exit(2)
6881
else:
6982
exit(0)
7083

7184

72-
7385
# consumable when running as CLI
7486
def parse_from_cli(arguments):
7587
LOGGER.info(f'Parsing template {arguments.template_path}')
@@ -121,7 +133,7 @@ def main(args=None):
121133
parser = argparse.ArgumentParser(description='Parses IAM identity-based and resource-based policies from AWS CloudFormation templates.')
122134
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
123135

124-
subparsers = parser.add_subparsers(dest='{parse,validate,check-no-new-access,check-access-not-granted}')
136+
subparsers = parser.add_subparsers(dest='{parse,validate,check-no-new-access,check-access-not-granted,check-no-public-access}')
125137
subparsers.required = True
126138

127139
# parse command
@@ -184,16 +196,27 @@ def add_policy_analysis_subparsers():
184196
default=True, action='store_false')
185197

186198
# check-access-not-granted command
187-
check_access_parser = subparsers.add_parser('check-access-not-granted', help='Parses IAM identity-based and resource-based policies from AWS CloudFormation templates '
188-
'and runs them through IAM Access Analyzer to check that access to a list of actions is not granted. Returns the response '
189-
'in JSON format.', parents=[parent_parser])
199+
check_access_parser = subparsers.add_parser('check-access-not-granted', help='Parses IAM identity-based and resource-based policies from'
200+
'AWS CloudFormation templates and runs them through IAM Access Analyzer to check '
201+
'that access to a list of actions and/or resources is not granted. Returns the '
202+
'response in JSON format.', parents=[parent_parser])
190203
check_access_parser.set_defaults(func=check_access_from_cli)
191204

192-
check_access_parser.add_argument('--actions', dest="actions", required=True,
193-
help= 'Actions that policies should not grant.'
194-
'Specify as a comma separated list of actions to be checked.'
195-
'The tool will make multiple requests if you provide more actions than the allowed quota.', action=ParseListFromCLI)
196-
205+
206+
check_access_parser.add_argument('--resources', dest="resources",
207+
help= 'Resources that policies should not grant access to. '
208+
'Specify as a comma-separated list of resource ARNs to be checked. '
209+
'A maximum of 100 resources can be specified for a single request. '
210+
'The tool will not make multiple requests if you provide more resources than the allowed quota. '
211+
'At least one of --actions or --resources must be specified.', action=ParseListFromCLI)
212+
213+
check_access_parser.add_argument('--actions', dest="actions",
214+
help= 'Actions that policies should not grant. '
215+
'Specify as a comma separated list of actions to be checked. '
216+
'A maximum of 100 actions can be specified for a single request. '
217+
'The tool will make multiple requests if you provide more actions than the allowed quota. '
218+
'At least one of --actions or --resources must be specified.', action=ParseListFromCLI)
219+
197220
check_access_parser.add_argument('--ignore-finding', dest="ignore_finding", metavar='FINDING_CODE,RESOURCE_NAME,RESOURCE_NAME.FINDING_CODE',
198221
help='Allow findings to be ignored.\n'
199222
'Specify as a comma separated list of findings to be ignored. Can be individual '
@@ -204,6 +227,22 @@ def add_policy_analysis_subparsers():
204227
check_access_parser.add_argument('--treat-findings-as-non-blocking', dest="findings_are_blocking",
205228
help='If set, all findings will be treated as non-blocking',
206229
default=True, action='store_false')
230+
# check-no-public-access command
231+
check_no_public_access_parser = subparsers.add_parser('check-no-public-access', help='Parses resource-based policies from AWS CloudFormation templates '
232+
'and runs them through IAM Access Analyzer to check that public access to resources of supported types is not granted. Returns the response '
233+
'in JSON format.', parents=[parent_parser])
234+
check_no_public_access_parser.set_defaults(func=check_no_public_access_from_cli)
235+
236+
check_no_public_access_parser.add_argument('--ignore-finding', dest="ignore_finding", metavar='FINDING_CODE,RESOURCE_NAME,RESOURCE_NAME.FINDING_CODE',
237+
help='Allow findings to be ignored.\n'
238+
'Specify as a comma separated list of findings to be ignored. Can be individual '
239+
'finding codes (e.g. "PASS_ROLE_WITH_STAR_IN_RESOURCE"), a specific resource name '
240+
'(e.g. "MyResource"), or a combination of both separated by a period.'
241+
'(e.g. "MyResource.PASS_ROLE_WITH_STAR_IN_RESOURCE").',
242+
action=ParseFindingsToIgnoreFromCLI)
243+
check_no_public_access_parser.add_argument('--treat-findings-as-non-blocking', dest="findings_are_blocking",
244+
help='If set, all findings will be treated as non-blocking',
245+
default=True, action='store_false')
207246
add_policy_analysis_subparsers()
208247

209248
args = parser.parse_args(args)

0 commit comments

Comments
 (0)