├── access_analyzer_blog ├── __init__.py ├── __pycache__ │ ├── infra.cpython-37.pyc │ ├── infra.cpython-39.pyc │ ├── tasks.cpython-37.pyc │ ├── tasks.cpython-39.pyc │ ├── __init__.cpython-37.pyc │ ├── __init__.cpython-39.pyc │ ├── devtools.cpython-37.pyc │ ├── devtools.cpython-39.pyc │ ├── pipeline.cpython-37.pyc │ ├── pipeline.cpython-39.pyc │ ├── security.cpython-37.pyc │ ├── security.cpython-39.pyc │ ├── access_analyzer_stack.cpython-37.pyc │ ├── appsec_workshop_stack.cpython-37.pyc │ └── appsec_workshop_stack.cpython-39.pyc ├── devtools.py ├── access_analyzer_stack.py └── pipeline.py ├── .gitignore ├── requirements.txt ├── static ├── my-iam-policy │ └── README.md ├── policy_analysis_ref_arch.jpg ├── policy_analysis_ref_arch.png ├── policy-check.sh ├── cang_buildspec.yaml ├── cnna_buildspec.yaml ├── cnpa_buildspec.yaml └── lambda_function │ └── lambda_function.py ├── config.yaml ├── CODE_OF_CONDUCT.md ├── source.bat ├── app.py ├── LICENSE ├── cdk.json ├── CONTRIBUTING.md └── README.md /access_analyzer_blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .venv/ 3 | cdk.out/* 4 | static/.DS_Store 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib==2.99.1 2 | constructs==10.1.162 3 | PyYAML==6.0.1 4 | cdk-nag==2.27.178 -------------------------------------------------------------------------------- /static/my-iam-policy/README.md: -------------------------------------------------------------------------------- 1 | ## Sample repository for IAM Access Analyzer Custom policy checks blog post -------------------------------------------------------------------------------- /static/policy_analysis_ref_arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/static/policy_analysis_ref_arch.jpg -------------------------------------------------------------------------------- /static/policy_analysis_ref_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/static/policy_analysis_ref_arch.png -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/infra.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/infra.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/infra.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/infra.cpython-39.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/tasks.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/tasks.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/tasks.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/tasks.cpython-39.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/devtools.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/devtools.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/devtools.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/devtools.cpython-39.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/pipeline.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/pipeline.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/pipeline.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/pipeline.cpython-39.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/security.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/security.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/security.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/security.cpython-39.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/access_analyzer_stack.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/access_analyzer_stack.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/appsec_workshop_stack.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/appsec_workshop_stack.cpython-37.pyc -------------------------------------------------------------------------------- /access_analyzer_blog/__pycache__/appsec_workshop_stack.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/access-analyzer-automated-policy-analysis-blog/main/access_analyzer_blog/__pycache__/appsec_workshop_stack.cpython-39.pyc -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | ### Check no new access (CNNA) Step 2 | cnna: 3 | enabled: True 4 | 5 | ### Check access not granted (CANG) Step 6 | cang: 7 | enabled: True 8 | 9 | ### Check if pipeline should support CDK workflows 10 | cdkstg: 11 | enabled: True -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .venv/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .venv\Scripts\activate.bat for you 13 | .venv\Scripts\activate.bat 14 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import yaml 4 | import aws_cdk as cdk 5 | from aws_cdk import Aspects 6 | from cdk_nag import AwsSolutionsChecks 7 | 8 | from access_analyzer_blog.access_analyzer_stack import AccessAnalyzerStack 9 | 10 | with open("./config.yaml") as stream: 11 | config = yaml.safe_load(stream) 12 | 13 | app = cdk.App() 14 | AccessAnalyzerStack(app, "AccessAnalyzerStack", config) 15 | 16 | Aspects.of(app).add(AwsSolutionsChecks(verbose=False)) 17 | 18 | app.synth() 19 | -------------------------------------------------------------------------------- /static/policy-check.sh: -------------------------------------------------------------------------------- 1 | cnnaReviewFindings=$(cfn-policy-validator check-if-less-permissive --template-path *.yaml --reference-policy control-policy.json --reference-policy-type identity --region us-west-2) 2 | cnnaResult=$(echo $cnnaReviewFindings | jq -r '.BlockingFindings[].details.result') 3 | if [ $cnnaResult == 'PASS' ]; 4 | then 5 | echo "Policy validation completed successfully" 6 | echo ------------- 7 | echo ------------- 8 | elif [ $cnnaResult == 'FAIL' ]; 9 | then 10 | cnnaReason=$(echo $cnnaReviewFindings | jq -r '.BlockingFindings[].details.result'') 11 | echo "Policy validation failed: $cnnaReason" 12 | echo ------------- 13 | echo ------------- 14 | exit 1 15 | fi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /static/cang_buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | commands: 6 | - sudo mkdir /usr/local/awscliv2 > /dev/null 7 | - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" > /dev/null 8 | - unzip awscliv2.zip > /dev/null 9 | - sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/awscliv2 --update > /dev/null 10 | - export PATH="/usr/local/bin:$PATH" > /dev/null 11 | - aws --version > /dev/null 12 | - python -m pip install --upgrade boto3 > /dev/null 13 | - pip install cfn-policy-validator==0.0.34 > /dev/null 14 | build: 15 | commands: 16 | - aws s3 cp $CANG_PERIMETER ./sensitive-actions.file 17 | - actions=$(awk -vORS=, '{ print $1 }' sensitive-actions.file | sed 's/,$/\n/' | sed '$ s/[[:punct:]]$//') 18 | - output=$(cfn-policy-validator check-access-not-granted --template-path ec2-instance-role.yaml --actions $actions --region $AWS_DEFAULT_REGION) 19 | finally: 20 | - echo "$output" 21 | - if [ $pullRequestId ]; then aws codecommit post-comment-for-pull-request --pull-request-id $pullRequestId --repository-name $repositoryName --before-commit-id $destinationCommit --after-commit-id $sourceCommit --content "$output";fi 22 | - blocking=$(echo $output | jq '.BlockingFindings[]') 23 | - if [ "$blocking" ]; then aws codecommit update-pull-request-status --pull-request-id $pullRequestId --pull-request-status "Closed";fi -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "requirements*.txt", 11 | "source.bat", 12 | "**/__init__.py", 13 | "python/__pycache__", 14 | "tests" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 19 | "@aws-cdk/core:stackRelativeExports": true, 20 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 21 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 22 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 23 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 24 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 25 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 26 | "@aws-cdk/core:checkSecretUsage": true, 27 | "@aws-cdk/aws-iam:minimizePolicies": true, 28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 29 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 30 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 31 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 32 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 33 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 34 | "@aws-cdk/core:enablePartitionLiterals": true, 35 | "@aws-cdk/core:target-partitions": [ 36 | "aws", 37 | "aws-cn" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /static/cnna_buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | commands: 6 | - sudo mkdir /usr/local/awscliv2 > /dev/null 7 | - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" > /dev/null 8 | - unzip awscliv2.zip > /dev/null 9 | - sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/awscliv2 --update > /dev/null 10 | - export PATH="/usr/local/bin:$PATH" > /dev/null 11 | - aws --version > /dev/null 12 | - python -m pip install --upgrade boto3 > /dev/null 13 | - pip install cfn-policy-validator==0.0.34 > /dev/null 14 | build: 15 | commands: 16 | - aws s3 cp $CNNA_PERIMETER ./cnna-reference-policy.json 17 | - output=$(cfn-policy-validator check-no-new-access --template-path ec2-instance-role.yaml --reference-policy cnna-reference-policy.json --reference-policy-type identity --region $AWS_DEFAULT_REGION) 18 | finally: 19 | - echo "$output" 20 | - if [ $pullRequestId ]; then aws codecommit post-comment-for-pull-request --pull-request-id $pullRequestId --repository-name $repositoryName --before-commit-id $destinationCommit --after-commit-id $sourceCommit --content "$output";fi 21 | - blocking=$(echo $output | jq '.BlockingFindings[]') 22 | - echo $blocking 23 | - echo "aws codecommit update-pull-request-status --pull-request-id $pullRequestId --pull-request-status Closed" 24 | - if [ "$blocking" ]; then aws codecommit update-pull-request-status --pull-request-id $pullRequestId --pull-request-status "Closed";fi -------------------------------------------------------------------------------- /static/cnpa_buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | env: 3 | exported-variables: 4 | - TemplateFileName 5 | 6 | phases: 7 | install: 8 | commands: 9 | - sudo mkdir /usr/local/awscliv2 > /dev/null 10 | - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" > /dev/null 11 | - unzip awscliv2.zip > /dev/null 12 | - sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/awscliv2 --update > /dev/null 13 | - export PATH="/usr/local/bin:$PATH" > /dev/null 14 | - aws --version > /dev/null 15 | - python -m pip install --upgrade boto3 > /dev/null 16 | - pip install cfn-policy-validator==0.0.34 > /dev/null 17 | build: 18 | commands: 19 | - echo "Checking for public access..." 20 | - output=$(cfn-policy-validator check-no-public-access --template-path ./*.yaml --region $AWS_DEFAULT_REGION) 21 | finally: 22 | - echo "$output" 23 | - TemplateFileName=$(ls *.yaml) 24 | - if [ $pullRequestId ]; then aws codecommit post-comment-for-pull-request --pull-request-id $pullRequestId --repository-name $repositoryName --before-commit-id $destinationCommit --after-commit-id $sourceCommit --content "$output";fi 25 | - blocking=$(echo $output | jq '.BlockingFindings[]') 26 | - echo $blocking 27 | - echo "aws codecommit update-pull-request-status --pull-request-id $pullRequestId --pull-request-status Closed" 28 | - if [ "$blocking" ]; then aws codecommit update-pull-request-status --pull-request-id $pullRequestId --pull-request-status "Closed";fi -------------------------------------------------------------------------------- /access_analyzer_blog/devtools.py: -------------------------------------------------------------------------------- 1 | from constructs import Construct 2 | import aws_cdk as cdk 3 | from aws_cdk import aws_codecommit as codecommit 4 | from aws_cdk import aws_s3 as s3 5 | 6 | class DevTools(Construct): 7 | 8 | @property 9 | def code_repo(self): 10 | return self._code_repo 11 | 12 | @property 13 | def config_bucket(self): 14 | return self._config_bucket 15 | 16 | def __init__(self, scope: Construct, id: str, config: dict, **kwargs): 17 | super().__init__(scope, id, **kwargs) 18 | 19 | ### CodeCommit - code repo 20 | self._code_repo = codecommit.Repository( 21 | self, "Repository", 22 | repository_name="my-iam-policy", 23 | code=codecommit.Code.from_directory("./static/my-iam-policy"), 24 | description="CodeCommit repo for blog post") 25 | 26 | ### S3 Bucket 27 | self._config_bucket = s3.Bucket( 28 | self, "PipelineConfigBucket", 29 | enforce_ssl=True, 30 | auto_delete_objects=True, 31 | removal_policy=cdk.RemovalPolicy.DESTROY 32 | ) 33 | 34 | ### Outputs 35 | self.output_codecommit_repo = cdk.CfnOutput( 36 | self, "CodeCommitRepo", 37 | value=self._code_repo.repository_name, 38 | description="AWS CodeCommit repository for hosting project source code" 39 | ) 40 | 41 | self.output_s3_bucket = cdk.CfnOutput( 42 | self, "ConfigBucket", 43 | value=self._config_bucket.bucket_name, 44 | description="S3 bucket to store reference policy and actions list", 45 | ) 46 | -------------------------------------------------------------------------------- /access_analyzer_blog/access_analyzer_stack.py: -------------------------------------------------------------------------------- 1 | from constructs import Construct 2 | import aws_cdk as cdk 3 | import cdk_nag 4 | 5 | from .devtools import DevTools 6 | from .pipeline import Pipeline 7 | 8 | 9 | class AccessAnalyzerStack(cdk.Stack): 10 | 11 | def __init__(self, scope: Construct, construct_id: str, config: dict, **kwargs) -> None: 12 | super().__init__(scope, construct_id, **kwargs) 13 | 14 | ### CDK Constructs for the Developer Tools 15 | devtools = DevTools(self, "DevTools", config) 16 | 17 | ### CDK Constructs for the DevSecOps Pipeline 18 | pipeline = Pipeline(self, "Pipeline", devtools, config) 19 | 20 | # Adding CDK Nag stack suppressions 21 | cdk_nag.NagSuppressions.add_stack_suppressions( 22 | self, 23 | suppressions=[ 24 | {"id": "AwsSolutions-IAM5", "reason": "Default CDK permissions"}, 25 | {"id": "AwsSolutions-IAM4", "reason": "Default CDK permissions"}, 26 | {"id": "AwsSolutions-S1", "reason": "This is a non-production stack and uses the default CDK configurations. These configurations are suitable for sample code because this environment should not run for extended periods of time without customer specific configurations applied."}, 27 | {"id": "AwsSolutions-KMS5", "reason": "This is a non-production stack and uses default CDK configurations. These configuraitons are suitable for sample code because this environment should not run for extended periods of time without customer specific configurations applied."} 28 | ]) 29 | 30 | # Adding CDK Nag resource level suppressions 31 | cdk_nag.NagSuppressions.add_resource_suppressions_by_path( 32 | self, 33 | path=f"{self.stack_name}/Pipeline/CANGInlinePolicy/Resource", 34 | suppressions=[ 35 | cdk_nag.NagPackSuppression( 36 | id="AwsSolutions-IAM5", 37 | reason="Required for access analyzer to work", 38 | applies_to=["Resource::*"] 39 | ) 40 | , 41 | cdk_nag.NagPackSuppression( 42 | id="AwsSolutions-IAM5", 43 | reason="Required for CodeBuild to access artifacts at non-deterministic paths in S3", 44 | applies_to=["Resource::/*"] 45 | ), 46 | ] 47 | ) 48 | 49 | cdk_nag.NagSuppressions.add_resource_suppressions_by_path( 50 | self, 51 | path=f"{self.stack_name}/Pipeline/CNNAInlinePolicy/Resource", 52 | suppressions=[ 53 | cdk_nag.NagPackSuppression( 54 | id="AwsSolutions-IAM5", 55 | reason="Required for access analyzer to work", 56 | applies_to=["Resource::*"] 57 | ), 58 | cdk_nag.NagPackSuppression( 59 | id="AwsSolutions-IAM5", 60 | reason="Required for CodeBuild to access artifacts at non-deterministic paths in S3", 61 | applies_to=["Resource::/*"] 62 | ), 63 | ] 64 | ) 65 | 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /static/lambda_function/lambda_function.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | 4 | build = boto3.client('codebuild') 5 | commit = boto3.client('codecommit') 6 | 7 | def lambda_handler(event, context): 8 | 9 | #get details of code build result 10 | if event["detail"]["event"] == "pullRequestCreated" or event["detail"]["event"] == "pullRequestSourceBranchUpdated": 11 | 12 | targetBranch = event["detail"]["destinationReference"].split('/')[2] 13 | sourceBranch = event["detail"]["sourceReference"].split('/')[2] 14 | 15 | build_Var = [ 16 | { 17 | 'name': 'pullRequestId', 18 | 'value': event["detail"]["pullRequestId"], 19 | 'type': 'PLAINTEXT' 20 | }, 21 | { 22 | 'name': 'targetBranch', 23 | 'value': targetBranch, 24 | 'type': 'PLAINTEXT' 25 | }, 26 | { 27 | 'name': 'sourceBranch', 28 | 'value': sourceBranch, 29 | 'type': 'PLAINTEXT' 30 | }, 31 | { 32 | 'name': 'destinationCommit', 33 | 'value': event["detail"]["destinationCommit"], 34 | 'type': 'PLAINTEXT' 35 | }, 36 | { 37 | 'name': 'sourceCommit', 38 | 'value': event["detail"]["sourceCommit"], 39 | 'type': 'PLAINTEXT' 40 | }, 41 | { 42 | 'name': 'repositoryName', 43 | 'value': event["detail"]["repositoryNames"][0], 44 | 'type': 'PLAINTEXT' 45 | }] 46 | 47 | #start code build with updated parameters from the pull request event 48 | startBuild_1 = build.start_build( 49 | projectName=os.environ['CNNA_PROJECT_NAME'], 50 | sourceLocationOverride="https://git-codecommit." + os.environ['AWS_REGION'] + ".amazonaws.com/v1/repos/" + event["detail"]["repositoryNames"][0], 51 | artifactsOverride={'type': 'NO_ARTIFACTS'}, 52 | sourceVersion=sourceBranch, 53 | sourceTypeOverride='CODECOMMIT', 54 | environmentVariablesOverride=build_Var 55 | ) 56 | 57 | print(startBuild_1) 58 | 59 | startBuild_2 = build.start_build( 60 | projectName=os.environ['CANG_PROJECT_NAME'], 61 | sourceLocationOverride="https://git-codecommit." + os.environ['AWS_REGION'] + ".amazonaws.com/v1/repos/" + event["detail"]["repositoryNames"][0], 62 | artifactsOverride={'type': 'NO_ARTIFACTS'}, 63 | sourceVersion=sourceBranch, 64 | sourceTypeOverride='CODECOMMIT', 65 | environmentVariablesOverride=build_Var 66 | ) 67 | 68 | print(startBuild_2) 69 | 70 | startBuild_3 = build.start_build( 71 | projectName=os.environ['CNPA_PROJECT_NAME'], 72 | sourceLocationOverride="https://git-codecommit." + os.environ['AWS_REGION'] + ".amazonaws.com/v1/repos/" + event["detail"]["repositoryNames"][0], 73 | artifactsOverride={'type': 'NO_ARTIFACTS'}, 74 | sourceVersion=sourceBranch, 75 | sourceTypeOverride='CODECOMMIT', 76 | environmentVariablesOverride=build_Var 77 | ) 78 | 79 | print(startBuild_3) 80 | -------------------------------------------------------------------------------- /access_analyzer_blog/pipeline.py: -------------------------------------------------------------------------------- 1 | from constructs import Construct 2 | import aws_cdk as cdk 3 | from cdk_nag import NagSuppressions, NagPackSuppression 4 | from aws_cdk import ( 5 | aws_codepipeline as codepipeline, 6 | aws_codepipeline_actions as codepipeline_actions, 7 | aws_codebuild as codebuild, 8 | aws_lambda as awslambda, 9 | aws_events as events, 10 | aws_events_targets as events_targets, 11 | aws_iam as iam 12 | ) 13 | 14 | class Pipeline(Construct): 15 | 16 | def __init__(self, scope: Construct, id: str, devtools, config: dict, **kwargs): 17 | super().__init__(scope, id, **kwargs) 18 | 19 | ### CodePipeline 20 | pipeline = codepipeline.Pipeline( 21 | self, "Pipeline", 22 | pipeline_name="accessanalyzer-pipeline", 23 | stages=[] 24 | ) 25 | 26 | ### Define source Stage 27 | source_output = codepipeline.Artifact() 28 | pipeline.add_stage( 29 | stage_name="CheckoutSource", 30 | actions=[ 31 | codepipeline_actions.CodeCommitSourceAction( 32 | action_name="CodeCommit", 33 | branch="main", 34 | repository=devtools.code_repo, 35 | output=source_output, 36 | run_order=1 37 | ) 38 | ] 39 | ) 40 | 41 | ### IAM policy analysis stage 42 | security_ci = pipeline.add_stage( 43 | stage_name="IAMPolicyAnalysis" 44 | ) 45 | 46 | ### Define check no new access action 47 | cnna = codebuild.PipelineProject( 48 | self, "CNNA", 49 | project_name="codebuild-cnna-project", 50 | build_spec=codebuild.BuildSpec.from_asset("./static/cnna_buildspec.yaml"), 51 | environment=codebuild.BuildEnvironment( 52 | privileged=False, 53 | build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_3 54 | ), 55 | environment_variables={ 56 | "CNNA_PERIMETER": codebuild.BuildEnvironmentVariable( 57 | value="s3://" + devtools.config_bucket.bucket_name + "/cnna-reference-policy.json" 58 | ) 59 | }, 60 | description="Check No New Access", 61 | timeout=cdk.Duration.minutes(60) 62 | ) 63 | 64 | cnna.role.attach_inline_policy(iam.Policy(self, "CNNAInlinePolicy", 65 | document=iam.PolicyDocument( 66 | statements=[ 67 | iam.PolicyStatement( 68 | actions=[ 69 | "access-analyzer:ListAnalyzers", 70 | "access-analyzer:ValidatePolicy", 71 | "access-analyzer:CreateAccessPreview", 72 | "access-analyzer:GetAccessPreview", 73 | "access-analyzer:ListAccessPreviewFindings", 74 | "access-analyzer:CreateAnalyzer", 75 | "access-analyzer:CheckAccessNotGranted", 76 | "access-analyzer:CheckNoNewAccess" 77 | ], 78 | resources=["*"] 79 | ), 80 | iam.PolicyStatement( 81 | actions=["s3:getObject"], 82 | resources=[ 83 | devtools.config_bucket.bucket_arn, 84 | devtools.config_bucket.bucket_arn+"/*" 85 | ] 86 | ), 87 | iam.PolicyStatement( 88 | actions=[ 89 | "codecommit:PostCommentForPullRequest", 90 | "codecommit:UpdatePullRequestStatus", 91 | "codecommit:GitPull" 92 | ], 93 | resources=[devtools.code_repo.repository_arn] 94 | ), 95 | iam.PolicyStatement( 96 | actions=[ 97 | "iam:GetPolicy", 98 | "iam:GetPolicyVersion" 99 | ], 100 | resources=["*"] 101 | ) 102 | ] 103 | ) 104 | )) 105 | 106 | security_ci.add_action( 107 | codepipeline_actions.CodeBuildAction( 108 | action_name="Check-no-new-access", 109 | input=source_output, 110 | project=cnna, 111 | run_order=2 112 | ) 113 | ) 114 | 115 | ### Define check access not granted action 116 | cang = codebuild.PipelineProject( 117 | self, "CANG", 118 | project_name="codebuild-cang-project", 119 | build_spec=codebuild.BuildSpec.from_asset("./static/cang_buildspec.yaml"), 120 | environment=codebuild.BuildEnvironment( 121 | privileged=False, 122 | build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_3 123 | ), 124 | environment_variables={ 125 | "CANG_PERIMETER": codebuild.BuildEnvironmentVariable( 126 | value="s3://" + devtools.config_bucket.bucket_name + "/sensitive-actions.file" 127 | ) 128 | }, 129 | description="Check Access Not Granted", 130 | timeout=cdk.Duration.minutes(60) 131 | ) 132 | 133 | ### Define role permissions for check access not granted action 134 | cang.role.attach_inline_policy(iam.Policy(self, "CANGInlinePolicy", 135 | document=iam.PolicyDocument( 136 | statements=[ 137 | iam.PolicyStatement( 138 | actions=[ 139 | "access-analyzer:ListAnalyzers", 140 | "access-analyzer:ValidatePolicy", 141 | "access-analyzer:CreateAccessPreview", 142 | "access-analyzer:GetAccessPreview", 143 | "access-analyzer:ListAccessPreviewFindings", 144 | "access-analyzer:CreateAnalyzer", 145 | "access-analyzer:CheckAccessNotGranted", 146 | "access-analyzer:CheckNoNewAccess" 147 | ], 148 | resources=["*"] 149 | ), 150 | iam.PolicyStatement( 151 | actions=["s3:getObject"], 152 | resources=[ 153 | devtools.config_bucket.bucket_arn, 154 | devtools.config_bucket.bucket_arn+"/*" 155 | ] 156 | ), 157 | iam.PolicyStatement( 158 | actions=[ 159 | "codecommit:PostCommentForPullRequest", 160 | "codecommit:UpdatePullRequestStatus", 161 | "codecommit:GitPull" 162 | ], 163 | resources=[devtools.code_repo.repository_arn] 164 | ), 165 | iam.PolicyStatement( 166 | actions=[ 167 | "iam:GetPolicy", 168 | "iam:GetPolicyVersion" 169 | ], 170 | resources=["*"] 171 | ) 172 | ] 173 | ) 174 | )) 175 | 176 | ### Add check access not granted action to pipeline 177 | security_ci.add_action( 178 | codepipeline_actions.CodeBuildAction( 179 | action_name="Check-access-not-granted", 180 | input=source_output, 181 | project=cang, 182 | run_order=2 183 | ) 184 | ) 185 | 186 | ### Define check no public access action 187 | cnpa = codebuild.PipelineProject( 188 | self, "CNPA", 189 | project_name="codebuild-cnpa-project", 190 | build_spec=codebuild.BuildSpec.from_asset("./static/cnpa_buildspec.yaml"), 191 | environment=codebuild.BuildEnvironment( 192 | privileged=False, 193 | build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_3 194 | ), 195 | description="Check No Public Access", 196 | timeout=cdk.Duration.minutes(60) 197 | ) 198 | 199 | ### Define role permissions for check no public access action 200 | cnpa.role.attach_inline_policy(iam.Policy(self, "CNPAInlinePolicy", 201 | document=iam.PolicyDocument( 202 | statements=[ 203 | iam.PolicyStatement( 204 | actions=[ 205 | "access-analyzer:ListAnalyzers", 206 | "access-analyzer:ValidatePolicy", 207 | "access-analyzer:CreateAccessPreview", 208 | "access-analyzer:GetAccessPreview", 209 | "access-analyzer:ListAccessPreviewFindings", 210 | "access-analyzer:CreateAnalyzer", 211 | "access-analyzer:CheckAccessNotGranted", 212 | "access-analyzer:CheckNoPublicAccess" 213 | ], 214 | resources=["*"] 215 | ), 216 | iam.PolicyStatement( 217 | actions=["s3:getObject"], 218 | resources=[ 219 | devtools.config_bucket.bucket_arn, 220 | devtools.config_bucket.bucket_arn+"/*" 221 | ] 222 | ), 223 | iam.PolicyStatement( 224 | actions=[ 225 | "codecommit:PostCommentForPullRequest", 226 | "codecommit:UpdatePullRequestStatus", 227 | "codecommit:GitPull" 228 | ], 229 | resources=[ 230 | devtools.code_repo.repository_arn 231 | ] 232 | ), 233 | iam.PolicyStatement( 234 | actions=[ 235 | "iam:GetPolicy", 236 | "iam:GetPolicyVersion" 237 | ], 238 | resources=["*"] 239 | ) 240 | ] 241 | ) 242 | )) 243 | 244 | ### Add check no public access action to pipeline 245 | security_ci.add_action( 246 | codepipeline_actions.CodeBuildAction( 247 | action_name="Check-no-public-access", 248 | input=source_output, 249 | project=cnpa, 250 | run_order=2 251 | ) 252 | ) 253 | 254 | ## Add deploy stage to Pipeline 255 | pipeline.add_stage( 256 | stage_name="Deploy", 257 | actions=[codepipeline_actions.CloudFormationCreateUpdateStackAction( 258 | action_name="Deploy", 259 | stack_name=devtools.code_repo.repository_name, 260 | template_path=source_output.at_path("ec2-instance-role.yaml"), 261 | admin_permissions=True 262 | )]) 263 | 264 | # Add event bridge rule to trigger codepipline based on pull request 265 | rule = events.Rule( 266 | self, "PullRequestEvent", 267 | description="Trigger Pipeline on Pull Request", 268 | event_pattern=events.EventPattern( 269 | source=["aws.codecommit"], 270 | detail_type=["CodeCommit Pull Request State Change"], 271 | resources=[devtools.code_repo.repository_arn], 272 | detail={ 273 | "destinationReference": ["refs/heads/main"], 274 | "event": ["pullRequestCreated"] 275 | } 276 | ) 277 | ) 278 | 279 | ### Define lambda function to trigger pipeline based on event bridge rule 280 | lambda_function = awslambda.Function( 281 | self, "TargetForPullRequests", 282 | code=awslambda.Code.from_asset("./static/lambda_function"), 283 | handler="lambda_function.lambda_handler", 284 | runtime=awslambda.Runtime.PYTHON_3_11, 285 | environment={ 286 | "CNNA_PROJECT_NAME": cnna.project_name, 287 | "CANG_PROJECT_NAME": cang.project_name, 288 | "CNPA_PROJECT_NAME": cnpa.project_name 289 | } 290 | ) 291 | 292 | ### Define role permissions for Lambda function 293 | lambda_function.add_to_role_policy(iam.PolicyStatement( 294 | actions=["codebuild:StartBuild"], 295 | resources=[cnna.project_arn, cang.project_arn, cnpa.project_arn] 296 | )) 297 | 298 | NagSuppressions.add_resource_suppressions(lambda_function,[{ 299 | 'id': 'AwsSolutions-IAM4', 'reason': 'supressing since it only allows your lambda permissions to write logs' 300 | }],apply_to_children=True,) 301 | 302 | rule.add_target(events_targets.LambdaFunction(lambda_function)) 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automating IAM Access Analyzer custom policy checks 2 | 3 | As covered in the following blog post: https://aws.amazon.com/blogs/security/introducing-iam-access-analyzer-custom-policy-checks/ 4 | 5 | > [!IMPORTANT] 6 | > AWS has extended custom policy checks to include a new check called Check No Public Access. This new check determines whether a resource policy grants public access to a specified resource type. In addition to this new check, there has been an update to the existing Check Access Not Granted check. The Check Access Not Granted check can now be used to determine whether a given policy grants permission to one or more customer-defined AWS resources. This example has been updated to include these new checks. 7 | 8 | In this example, you will automate the validation and analysis of the IAM identity and resources policies that are defined in an AWS CloudFromation template. The workflow will trigger each time a pull request is created against the main branch of an AWS CodeCommit repository called my-iam-policy. The workflow includes 3 checks. The first check will use IAM Access Analyzer's check no new access API to determine if the updated policy is more permissive than a reference IAM policy. The second check will use the check access not granted API to automatically check for critical permissions in an IAM policy. The third check uses the CheckNoPublicAccess API to check whether a resource policy grants public access to supported resource types. In all cases, if the updated policy is more permissive, contains sensitive permissions or grants public access to a sensitive resource type, a comment with the results of the validation will be posted to the pull request. This information can then be used to decide whether or not the pull request is merged in to the mainline for deployment. 9 | 10 | ![Custom IAM Policy Analysis Reference Archiecture](/static/policy_analysis_ref_arch.jpg "Reference Architecture") 11 | 12 | ### Step 1: Deploy the infrastructure and set up the pipeline 13 | 14 | 1. Use the following command to create a local clone of the Cloud Development Kit (CDK) project associated with this example. 15 | 16 | ``` 17 | git clone https://github.com/aws-samples/access-analyzer-automated-policy-analysis-blog.git 18 | cd ./access-analyzer-automated-policy-analysis-blog 19 | ``` 20 | 21 | 2. Create a virtual Python environment to contain the project dependencies by using the following command. 22 | 23 | ``` 24 | python3 -m venv .venv 25 | ``` 26 | 27 | 3. Activate the virtual environment with the following command. 28 | 29 | ``` 30 | source .venv/bin/activate 31 | ``` 32 | 33 | 4. Install the project requirements by using the following command. 34 | 35 | ``` 36 | pip install -r requirements.txt 37 | ``` 38 | 39 | 5. Use the following command to update the CDK CLI to the latest major version. 40 | 41 | ``` 42 | npm install -g aws-cdk@2 --force 43 | ``` 44 | 45 | 6. Before you can deploy the CDK project, use the following command to bootstrap your AWS environment. Bootstrapping is the process of creating resources needed for deploying CDK projects. These resources include an Amazon Simple Storage Service (Amazon S3) bucket for storing files and IAM roles that grant permissions needed to perform deployments. 46 | 47 | ``` 48 | cdk bootstrap 49 | ``` 50 | 51 | 7. Finally, use the following command to deploy the pipeline infrastructure. 52 | 53 | ``` 54 | cdk deploy --require-approval never 55 | ``` 56 | 57 | The deployment will take a few minutes to complete. Feel free to grab a coffee and check back shortly. 58 | 59 | When the deployment completes, there will be two stack outputs listed: one with a name that contains CodeCommitRepo and another with a name that contains ConfigBucket. Make a note of the values of these outputs, because you will need them later. 60 | 61 | 8. Use the following command to create the reference policy. 62 | 63 | ``` 64 | cd ../ 65 | cat << EOF > cnna-reference-policy.json 66 | { 67 | "Version": "2012-10-17", 68 | "Statement": [ 69 | { 70 | "Effect": "Allow", 71 | "Action": "*", 72 | "Resource": "*" 73 | }, 74 | { 75 | "Effect": "Deny", 76 | "Action": "iam:PassRole", 77 | "Resource": "arn:aws:iam::*:role/my-sensitive-roles/*" 78 | } 79 | ] 80 | } 81 | EOF 82 | ``` 83 | 84 | 9. Use the following command to create a list of sensitive actions. This list will be parsed during the build pipeline and passed to the CheckAccessNotGranted API. If the policy grants access to one or more of the sensitive actions in this list, a result of FAIL will be returned. To keep this example simple, add a single API action, as follows. 85 | 86 | ``` 87 | cat << EOF > sensitive-actions.file 88 | dynamodb:DeleteTable 89 | EOF 90 | ``` 91 | 92 | 10. So that the CodeBuild projects can access the dependencies, use the following command to copy the cnna-reference-policy.file and sensitive-actions.file to an S3 bucket. Refer to the stack outputs you noted earlier and replace with the name of the S3 bucket created in your environment. 93 | 94 | ``` 95 | aws s3 cp ./cnna-reference-policy.json s3:///cnna-reference-policy.json 96 | aws s3 cp ./sensitive-actions.file s3:///sensitive-actions.file 97 | ``` 98 | 99 | ### Step 2: Create a new CloudFormation template that defines an IAM policy 100 | 101 | With the pipeline deployed, the next step is to clone the repository that was created and populate it with a CloudFormation template that defines an IAM policy. 102 | 103 | 1. Install git-remote-codecommit by using the following command. 104 | 105 | ``` 106 | pip install git-remote-codecommit 107 | ``` 108 | 109 | For more information on installing and configuring git-remote-codecommit, see the AWS CodeCommit User Guide. 110 | 111 | 2. With git-remote-codecommit installed, use the following command to clone the my-iam-policy repository from AWS CodeCommit. 112 | 113 | ``` 114 | git clone codecommit://my-iam-policy && cd ./my-iam-policy 115 | ``` 116 | 117 | If you’ve configured a named profile for use with the AWS CLI, use the following command, replacing with the name of your named profile. 118 | 119 | git clone codecommit://@my-iam-policy && cd ./my-iam-policy 120 | 121 | > [!CAUTION] 122 | > This example CloudFormation templates in the following sections should not be used in production. They are intended to be used for demonstration purposes only. 123 | 124 | 3. Use the following command to create the CloudFormation template in the local clone of the repository. 125 | 126 | ```yaml 127 | cat << EOF > ec2-instance-role.yaml 128 | --- 129 | AWSTemplateFormatVersion: 2010-09-09 130 | Description: CloudFormation Template to deploy base resources for access_analyzer_blog 131 | Resources: 132 | EC2Role: 133 | Type: AWS::IAM::Role 134 | Properties: 135 | AssumeRolePolicyDocument: 136 | Version: 2012-10-17 137 | Statement: 138 | - Effect: Allow 139 | Principal: 140 | Service: ec2.amazonaws.com 141 | Action: sts:AssumeRole 142 | Path: / 143 | Policies: 144 | - PolicyName: my-application-permissions 145 | PolicyDocument: 146 | Version: 2012-10-17 147 | Statement: 148 | - Effect: Allow 149 | Action: 150 | - 'ec2:RunInstances' 151 | - 'lambda:CreateFunction' 152 | - 'lambda:InvokeFunction' 153 | - 'dynamodb:Scan' 154 | - 'dynamodb:Query' 155 | - 'dynamodb:UpdateItem' 156 | - 'dynamodb:GetItem' 157 | Resource: '*' 158 | - Effect: Allow 159 | Action: 160 | - iam:PassRole 161 | Resource: "arn:aws:iam::*:role/my-custom-role" 162 | 163 | EC2InstanceProfile: 164 | Type: AWS::IAM::InstanceProfile 165 | Properties: 166 | Path: / 167 | Roles: 168 | - !Ref EC2Role 169 | EOF 170 | ``` 171 | The actions in the IAMPolicyValidation stage are run by a CodeBuild project. CodeBuild environments run arbitrary commands that are passed to the project using a buildspec file. Each project has already been configured to use an inline buildspec file. 172 | 173 | ### Step 3: Run analysis on the IAM policy 174 | 175 | The next step involves checking in the first version of the CloudFormation template to the repository and checking two things. First, that the policy does not grant more access than the reference policy. Second, that the policy does not contain any of the sensitive actions defined in the sensitive-actions.file. 176 | 177 | 1. To begin tracking the CloudFormation template created earlier, use the following command. 178 | 179 | ``` 180 | git add ec2-instance-role.yaml 181 | ``` 182 | 183 | 2. Commit the changes you have made to the repository. 184 | 185 | ``` 186 | git commit -m 'committing a new CFN template with IAM policy' 187 | ``` 188 | 189 | 3. Finally, push these changes to the remote repository. 190 | 191 | ``` 192 | git push 193 | ``` 194 | 195 | 4. Pushing these changes will initiate the pipeline. After a few minutes the pipeline should complete successfully. To view the status of the pipeline, do the following: 196 | 197 | - Navigate to https://*{region}*.console.aws.amazon.com/codesuite/codepipeline/pipelines (replacing *{region}* with your AWS Region). 198 | - Choose the pipeline called accessanalyzer-pipeline. 199 | - Scroll down to the IAMPolicyValidation stage of the pipeline. 200 | - For both the check no new access and check access not granted actions, choose View Logs to inspect the log output. 201 | 202 | 5. If you inspect the build logs for both the **check-no-new-access**, **check-access-not-granted** and **check-no-public-access** actions within the pipeline, you should see that there were no blocking or non-blocking findings. This indicates that the policy was checked successfully. In other words, the policy was not more permissive than the reference policy, it did not include any of the critical permissions or resources and there were no resources that were public. 203 | 204 | ### Step 4: Create a pull request to merge a new update to the CloudFormation template 205 | 206 | In this step, you will make a change to the IAM policy in the CloudFormation template. The change deliberately makes the policy grant more access than the reference policy. The change also includes a critical permission and makes makes and adds a secrets manager secret with a resource policy that permits public access. 207 | 208 | 1. Use the following command to create a new branch called add-new-permissions in the local clone of the repository. 209 | 210 | ``` 211 | git checkout -b add-new-permissions 212 | ``` 213 | 214 | 2. Next, edit the IAM policy in ec2-instance-role.yaml to include an additional API action, dynamodb:Delete*, update the resource property of the inline policy to use an IAM role in the /my-sensitive-roles/*” path anf finally add a new Secrets Manager Secret that has resource policy that grants public access to the secret. You can copy the following example, if you’re unsure of how to do this. 215 | 216 | ```yaml 217 | cat << EOF > ec2-instance-role.yaml 218 | --- 219 | AWSTemplateFormatVersion: 2010-09-09 220 | Description: CloudFormation Template to deploy base resources for access_analyzer_blog 221 | Resources: 222 | EC2Role: 223 | Type: AWS::IAM::Role 224 | Properties: 225 | AssumeRolePolicyDocument: 226 | Version: 2012-10-17 227 | Statement: 228 | - Effect: Allow 229 | Principal: 230 | Service: ec2.amazonaws.com 231 | Action: sts:AssumeRole 232 | Path: / 233 | Policies: 234 | - PolicyName: my-application-permissions 235 | PolicyDocument: 236 | Version: 2012-10-17 237 | Statement: 238 | - Effect: Allow 239 | Action: 240 | - 'ec2:RunInstances' 241 | - 'lambda:CreateFunction' 242 | - 'lambda:InvokeFunction' 243 | - 'dynamodb:Scan' 244 | - 'dynamodb:Query' 245 | - 'dynamodb:UpdateItem' 246 | - 'dynamodb:GetItem' 247 | - 'dynamodb:Delete*' 248 | Resource: '*' 249 | - Effect: Allow 250 | Action: 251 | - iam:PassRole 252 | Resource: "arn:aws:iam::*:role/my-sensitive-roles/my-custom-admin-role" 253 | 254 | EC2InstanceProfile: 255 | Type: AWS::IAM::InstanceProfile 256 | Properties: 257 | Path: / 258 | Roles: 259 | - !Ref EC2Role 260 | 261 | MySecret: 262 | Type: AWS::SecretsManager::Secret 263 | Properties: 264 | Description: This is a secret that I want to attach a resource-based policy to 265 | 266 | MySecretResourcePolicy: 267 | Type: AWS::SecretsManager::ResourcePolicy 268 | Properties: 269 | SecretId: 270 | Ref: MySecret 271 | ResourcePolicy: 272 | Version: '2012-10-17' 273 | Statement: 274 | - Sid: "DenyAllAccountDeleteSecret" 275 | Resource: "*" 276 | Action: secretsmanager:DeleteSecret 277 | Effect: Deny 278 | Principal: "*" 279 | - Sid: "AllowAllAccountGetSecretValue" 280 | Resource: "*" 281 | Action: secretsmanager:GetSecretValue 282 | Effect: Allow 283 | Principal: "*" 284 | EOF 285 | ``` 286 | 287 | 3. Commit the policy change and push the updated policy document to the repo by using the following commands. 288 | 289 | ``` 290 | git add ec2-instance-role.yaml 291 | git commit -m "adding new permission and allowing my ec2 instance to assume a pass sensitive IAM role" 292 | ``` 293 | 294 | 4. The add-new-permissions branch is currently a local branch. Use the following command to push the branch to the remote repository. This action will not initiate the pipeline, because the pipeline only runs when changes are made to the repository’s main branch. 295 | 296 | ``` 297 | git push -u origin add-new-permissions 298 | ``` 299 | 300 | 5. With the new branch and changes pushed to the repository, follow these steps to create a pull request: 301 | 302 | - Navigate to https://console.aws.amazon.com/codesuite/codecommit/repositories (don’t forget to the switch to the correct Region). 303 | - Choose the repository called **my-iam-policy**. 304 | - Choose the branch add-new-permissions from the drop-down list at the top of the repository screen. 305 | - Choose **Create pull request**. 306 | - Enter a title and description for the pull request. 307 | - (Optional) Scroll down to see the differences between the current version and new version of the CloudFormation template highlighted. 308 | - Choose **Create pull request**. 309 | 310 | 6. The creation of the pull request will Initiate the pipeline to fetch the CloudFormation template from the repository and run the check no new access and check access not granted analysis actions. 311 | 312 | 7. After a few minutes, choose the Activity tab for the pull request. You should see a comment from the pipeline that contains the results of the failed validation. 313 | 314 | ### Why did the validations fail? 315 | 316 | The updated IAM role and inline policy failed validation for three reasons. First, the reference policy said that no one should have more permissions than the reference policy does. The reference policy in this example included a deny statement for the iam:PassRole permission with a resource of /my-sensitive-role/*. The new created inline policy included an allow statement for the iam:PassRole permission with a resource of arn:aws:iam::*:role/my-sensitive-roles/my-custom-admin-role. In other words, the new policy had more permissions than the reference policy. 317 | 318 | Second, the list of critical permissions included the dynamodb:DeleteTable permission. The inline policy included a statement that would allow the EC2 instance to perform the dynamodb:DeleteTable action. 319 | 320 | Third and finally, the newly create Secrets Manager Secret had resource policy that grants public access to the secret. 321 | 322 | ### Cleanup 323 | 324 | Use the following command to delete the infrastructure that was provisioned as part of the examples in this blog post. 325 | 326 | cdk destroy 327 | --------------------------------------------------------------------------------