├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.py ├── backend ├── infrastructure.py └── runtime │ ├── Dockerfile │ ├── app.py │ └── requirements.txt ├── cdk.json ├── constants.py ├── deployment.py ├── package.json ├── pipeline.py ├── requirements-dev.txt ├── requirements.txt ├── scripts ├── install_dependencies.sh ├── run_tests.sh └── run_zap.sh ├── sectools └── infrastructure.py ├── securityhub ├── runtime │ └── invoke_action_function.py └── securityhub_report_step.py ├── shared ├── network │ └── infrastructure.py └── shared_infra.py └── vulnerable_code └── hard_coded_secret.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .venv 6 | .env 7 | *.egg-info 8 | 9 | # CDK asset staging directory 10 | .cdk.staging 11 | cdk.out 12 | node_modules 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS CI/CD Pipeline for DevSecOps 2 | 3 | This project is a reference implementation for a CI/CD pipeline integrated with security vulnerability scanning tools. 4 | 5 | The pipeline is implemented as code using [AWS CDK](https://aws.amazon.com/cdk/) and the [CDK Pipelines construct](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html). The current implementation performs security vulnerability scanning using SonarQube and Dependency-Check. The pipeline sends to [AWS Security Hub](https://aws.amazon.com/security-hub/) the reports for the security scanning executions. It also contains a sample application implementation for testing purposes. 6 | 7 | ## Project structure 8 | 9 | * `backend/infrastructure.py`: definition of the infrastructure components necessary to run the `backend` component of the sample application. 10 | * `backend/runtime/`: the actual code of the sample application 11 | * `shared/`: definition of the core components that the security tools and components of the sample application share. 12 | * `securityhub/`: implementation of the integration with AWS Security Hub 13 | * `sectools/`: definition of all the security tools the pipeline uses for security vulnerability scanning. 14 | * `pipeline.py`: definition of the CI/CD pipeline as code. 15 | * `deployment.py`: definition of the deployment unit of the sample application that the pipeline will deploy. 16 | 17 | ## Create development environment 18 | See [Getting Started With the AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) 19 | for additional details and prerequisites 20 | 21 | ### Clone the code 22 | ```bash 23 | git clone https://github.com/aws-samples/cdk-devsecops-cicd-pipeline 24 | cd cdk-devsecops-cicd-pipeline 25 | ``` 26 | 27 | ### Create Python virtual environment and install the dependencies 28 | ```bash 29 | python3.7 -m venv .venv 30 | source .venv/bin/activate 31 | pip install -r requirements.txt 32 | ``` 33 | 34 | ### Deploy to a sandbox environment 35 | 36 | To deploy the stacks, use CDK commands. If you are new to CDK, see [Getting started with the AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html). 37 | 38 | Before proceeding, update the `DEV_ACCOUNT_ID` and `REGION` values on `constants.py` to the AWS account ID of your sandbox environment and your region of choice. After that, run a `cdk ls` command to test if everything is correct with your CDK app. This will list the 4 stacks existing on the CDK app. Note that a file called `cdk.context.json` will also be generated in the root of the project. This is the runtime context file, and it must be commited to your source control. See [Runtime context](https://docs.aws.amazon.com/cdk/v2/guide/context.html) to learn more about that. 39 | 40 | #### Push the code to AWS CodeCommit 41 | For this sample CI/CD pipeline, we are using [AWS CodeCommit](https://aws.amazon.com/codecommit/) as a Git repository. For your own projects, you can update `pipeline.py` to use your Git repository of choice. 42 | 43 | From the terminal, create a new Git repository on your sandbox environment: 44 | ```bash 45 | aws codecommit create-repository --repository-name cdk-devsecops-cicd-pipeline 46 | ``` 47 | 48 | After you created the repository, push the code of this sample pipeline to CodeCommit. If you are new to CodeCommit, see [Getting started with Git and AWS CodeCommit](https://docs.aws.amazon.com/codecommit/latest/userguide/getting-started.html). 49 | 50 | #### Bootstrap CDK 51 | ```bash 52 | cdk bootstrap aws:// 53 | ``` 54 | 55 | #### Deploy shared components 56 | ```bash 57 | cdk deploy SharedInfraStack 58 | ``` 59 | 60 | #### Deploy security tools 61 | ```bash 62 | cdk deploy SecToolsStack 63 | ``` 64 | 65 | After deploying the security tools, you'll see output values from CDK on your terminal. Find an output named `SecToolsStack.SonarquebEcsTaskServiceURLABCXYZ`, which is the URL from where your [SonarQube](https://www.sonarqube.org/) instance is responding. 66 | 67 | Example output: 68 | ```text 69 | ✅ SecToolsStack 70 | 71 | ✨ Deployment time: 1018.67s 72 | 73 | Outputs: 74 | SecToolsStack.SonarQubeSecretArnOutput = arn:aws:secretsmanager:us-east-1:.elb.amazonaws.com 76 | SecToolsStack.SonarquebEcsTaskServiceURLE4434029 = http://SecTo-Sonar-ABCXYZ..elb.amazonaws.com 77 | ``` 78 | 79 | To interact with SonarQube's APIs, you need to generate a user token. See [Generating and Using Tokens](https://docs.sonarqube.org/latest/user-guide/user-token/) on SonarQube's documentation to learn how to create yours. You'll also have to create a project on SonarQube to represent the sample application we are using in this reference implementation. The deployment of DB instance associated with SonarQube service can take between 15 to 20 minutes. 80 | 81 | As part of the `SecToolsStack`, a secret is created on [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). Find on the CDK output on your terminal the ARN of this secret, which must be similar to `SecToolsStack.SonarQubeSecretArnOutput`. Update this secret with the SonarQube access data: 82 | ```bash 83 | aws secretsmanager put-secret-value \ 84 | --secret-id \ 85 | --secret-string "{\"access_token\":\"\",\"host\":\" None: 20 | super().__init__(scope, id_) 21 | 22 | runtime_asset = str(pathlib.Path(__file__).parent.joinpath("runtime").resolve()) 23 | docker_image_asset = DockerImageAsset(self, "SampleApp-API", 24 | directory=runtime_asset 25 | ) 26 | 27 | vpc = ec2.Vpc.from_lookup(self, "VPC", 28 | vpc_id=ssm.StringParameter.value_from_lookup(self, constants.CORE_VPC_PARAMETER_NAME) 29 | ) 30 | 31 | backend_ecs = ecsp.ApplicationLoadBalancedFargateService(self, "BackendAPI", 32 | task_image_options=ecsp.ApplicationLoadBalancedTaskImageOptions( 33 | image=ecs.ContainerImage.from_ecr_repository( 34 | repository=docker_image_asset.repository, 35 | tag=docker_image_asset.image_tag), 36 | container_port=5000 37 | ), 38 | public_load_balancer=True, 39 | vpc=vpc 40 | ) 41 | 42 | self.application_url_output_key = f'{constants.CDK_APP_NAME}-{stage_name}' 43 | 44 | cdk.CfnOutput(self, 'SonarQubeSecretArnOutput', 45 | value=f"http://{backend_ecs.load_balancer.load_balancer_dns_name}", 46 | export_name=self.application_url_output_key 47 | ) 48 | 49 | -------------------------------------------------------------------------------- /backend/runtime/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | FROM public.ecr.aws/docker/library/python:3.9.15-slim 4 | 5 | WORKDIR /api 6 | 7 | COPY requirements.txt requirements.txt 8 | RUN pip3 install -r requirements.txt 9 | 10 | COPY . . 11 | 12 | ENV FLASK_APP app/app.py 13 | 14 | CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"] -------------------------------------------------------------------------------- /backend/runtime/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | from flask import Flask 6 | from flask import request 7 | 8 | app = Flask(__name__) 9 | 10 | @app.route('/') 11 | def index(): 12 | return '

Hello from Flask & Docker

' 13 | 14 | if __name__ == "__main__": 15 | app.run() -------------------------------------------------------------------------------- /backend/runtime/requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.1.3 2 | Flask==2.3.2 3 | importlib-metadata==5.0.0 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.2 6 | MarkupSafe==2.1.1 7 | typing_extensions==4.4.0 8 | Werkzeug==2.2.3 9 | zipp==3.10.0 10 | # cryptography==2.9.1 11 | -------------------------------------------------------------------------------- /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:enableDiffNoFail": true, 36 | "@aws-cdk/core:target-partitions": [ 37 | "aws", 38 | "aws-cn" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | import aws_cdk as cdk 6 | 7 | CDK_APP_NAME = "SampleDevSecOps-CICD-Pipeline" 8 | 9 | CDK_APP_PYTHON_VERSION = "3.7" 10 | 11 | DEV_ACCOUNT_ID = "" 12 | REGION = "" 13 | 14 | DEV_ENV = cdk.Environment(account=DEV_ACCOUNT_ID, region=REGION) 15 | 16 | CODECOMMIT_REPOSITORY_NAME = "cdk-devsecops-cicd-pipeline" 17 | 18 | CORE_VPC_PARAMETER_NAME = "/SampleDevSecOpsCICDPipeline/CoreVPCID" 19 | 20 | SONARQUBE_SECRET_ARN_EXPORT_NAME = "SonarQubeSecretArn" 21 | 22 | SONARQUBE_RESULT_REPORT_OUTPUT_NAME = "sonar_result" 23 | SECURITY_SCANNING_RESULT_DIR = "security_scanning_output" 24 | SONARQUBE_SCAN_RESULT_OUTPUT_FILE = f"{SECURITY_SCANNING_RESULT_DIR}/sonarscan_result.txt" 25 | SONARQUBE_QUALITY_STATUS_OUTPUT_FILE = f"{SECURITY_SCANNING_RESULT_DIR}/sonar_quality_status.json" 26 | SONARQUBE_ISSUES_OUTPUT_FILE = f"{SECURITY_SCANNING_RESULT_DIR}/sonar_issues.json" 27 | OWASP_DEPENDENCY_CHECK_OUTPUT_FILE = f"{SECURITY_SCANNING_RESULT_DIR}/owasp_dependency_check_result.json" 28 | OWASP_ZAP_OUTPUT_FILE = f"{SECURITY_SCANNING_RESULT_DIR}/owasp_zap_result.json" 29 | 30 | SECURITY_HUB_PRODUCT_ARN = "arn:aws:securityhub:{0}:{1}:product/{1}/default".format(REGION, DEV_ACCOUNT_ID) 31 | -------------------------------------------------------------------------------- /deployment.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | import aws_cdk as cdk 6 | 7 | from backend.infrastructure import Backend 8 | 9 | class SampleApplicationBackend(cdk.Stage): 10 | def __init__(self, scope, id_, stage_name: str, **kwargs): 11 | super().__init__(scope, id_, **kwargs) 12 | 13 | stateless = cdk.Stack(self, "Stateless") 14 | 15 | self.backend = Backend(stateless, 'Backend', stage_name) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "aws-cdk": "2.50.0" 4 | }, 5 | "scripts": { 6 | "cdk": "cdk" 7 | }, 8 | "dependencies": { 9 | "requirements": "^1.4.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pipeline.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_codecommit as codecommit 7 | from aws_cdk import aws_codebuild as codebuild 8 | from aws_cdk import pipelines 9 | from aws_cdk import aws_secretsmanager as secretsmanager 10 | from aws_cdk import aws_iam as iam 11 | from constructs import Construct 12 | 13 | from deployment import SampleApplicationBackend 14 | from securityhub.securityhub_report_step import SecurityHubReportStep 15 | 16 | import constants 17 | 18 | class DevSecOpsPipelineStack(cdk.Stack): 19 | 20 | def __init__(self, scope: Construct, id_: str, **kwargs) -> None: 21 | super().__init__(scope, id_, **kwargs) 22 | 23 | repository = codecommit.Repository.from_repository_name(self, 24 | 'CodeCommitRepo', 25 | constants.CODECOMMIT_REPOSITORY_NAME 26 | ) 27 | source = pipelines.CodePipelineSource.code_commit(repository, branch='main') 28 | 29 | sonarqube_secret_arn = cdk.Fn.import_value(constants.SONARQUBE_SECRET_ARN_EXPORT_NAME) 30 | sonar_secret = secretsmanager.Secret.from_secret_complete_arn(self, 'SonarQubeSecret', 31 | sonarqube_secret_arn 32 | ) 33 | 34 | build_spec = codebuild.BuildSpec.from_object( 35 | { 36 | "env": { 37 | "secrets-manager": { 38 | "LOGIN": f'{sonar_secret.secret_full_arn}:access_token', 39 | "HOST": f'{sonar_secret.secret_full_arn}:host', 40 | "PROJECT": f'{sonar_secret.secret_full_arn}:project' 41 | }, 42 | "variables": { 43 | "SECURITY_SCANNING_OUTPUT_DIR": constants.SECURITY_SCANNING_RESULT_DIR, 44 | "SONAR_SCAN_OUTPUT_FILE": constants.SONARQUBE_SCAN_RESULT_OUTPUT_FILE, 45 | "SONAR_QUALITY_STATUS_OUTPUT_FILE": constants.SONARQUBE_QUALITY_STATUS_OUTPUT_FILE, 46 | "SONAR_ISSUES_OUTPUT_FILE": constants.SONARQUBE_ISSUES_OUTPUT_FILE, 47 | "OWASP_DEPENDENCY_CHECK_OUTPUT_FILE": constants.OWASP_DEPENDENCY_CHECK_OUTPUT_FILE, 48 | "FAIL_BUILD_FOR_SONAR_QUALITY_STATUS": False 49 | } 50 | }, 51 | "phases": { 52 | "install": { 53 | "runtime-versions": { 54 | "python": constants.CDK_APP_PYTHON_VERSION 55 | }, 56 | "commands": [ 57 | "./scripts/install_dependencies.sh", 58 | "npm install", 59 | "pip3 install -r requirements.txt" 60 | ], 61 | }, 62 | "build": { 63 | "commands": [ 64 | "./scripts/run_tests.sh", 65 | "npx cdk synth" 66 | ] 67 | } 68 | }, 69 | "version": "0.2", 70 | } 71 | ) 72 | 73 | # Synth CDK application and build artifacts 74 | synth_action = pipelines.CodeBuildStep( 75 | 'Build', 76 | input=source, 77 | partial_build_spec=build_spec, 78 | commands=[], 79 | role_policy_statements=[ 80 | iam.PolicyStatement( 81 | actions=[ 82 | 'secretsmanager:DescribeSecret', 83 | 'secretsmanager:GetSecretValue' 84 | ], 85 | resources=[sonarqube_secret_arn], 86 | ), 87 | iam.PolicyStatement( 88 | actions=[ 89 | 'ssm:GetParameter', 90 | ], 91 | resources=[f'arn:aws:ssm:{constants.REGION}:{constants.DEV_ACCOUNT_ID}:parameter{constants.CORE_VPC_PARAMETER_NAME}'], 92 | ), 93 | ], 94 | build_environment=codebuild.BuildEnvironment( 95 | privileged=True 96 | ), 97 | cache=codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER), 98 | ) 99 | scan_report_output = synth_action.add_output_directory( 100 | constants.SECURITY_SCANNING_RESULT_DIR 101 | ) 102 | 103 | pipeline = pipelines.CodePipeline(self, 104 | 'Pipeline', 105 | docker_enabled_for_synth=True, 106 | synth=synth_action, 107 | ) 108 | 109 | # Deploy application in dev 110 | sample_application_backend = SampleApplicationBackend( 111 | self, 112 | f'{constants.CDK_APP_NAME}-Dev', 113 | 'dev', 114 | env=constants.DEV_ENV 115 | ) 116 | pipeline.add_stage( 117 | sample_application_backend, 118 | pre=[ 119 | pipelines.ManualApprovalStep("PromoteToDev") 120 | ] 121 | ) 122 | 123 | # Scan with OWASP ZAP 124 | owasp_zap_build_spec = codebuild.BuildSpec.from_object( 125 | { 126 | "env": { 127 | "variables": { 128 | "SECURITY_SCANNING_OUTPUT_DIR": constants.SECURITY_SCANNING_RESULT_DIR, 129 | "OWASP_ZAP_OUTPUT_FILE": constants.OWASP_ZAP_OUTPUT_FILE, 130 | "APPLICATION_URL_OUTPUT_KEY": sample_application_backend.backend.application_url_output_key 131 | } 132 | }, 133 | "phases": { 134 | "build": { 135 | "commands": [ 136 | "./scripts/run_zap.sh" 137 | ] 138 | } 139 | }, 140 | "version": "0.2", 141 | } 142 | ) 143 | owasp_zap_build_step = pipelines.CodeBuildStep( 144 | 'OwaspZap', 145 | input=source, 146 | partial_build_spec=owasp_zap_build_spec, 147 | build_environment=codebuild.BuildEnvironment( 148 | privileged=True 149 | ), 150 | commands=[], 151 | role_policy_statements=[ 152 | iam.PolicyStatement( 153 | actions=[ 154 | 'cloudformation:ListExports', 155 | ], 156 | resources=['*'], 157 | ) 158 | ], 159 | cache=codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER), 160 | ) 161 | zap_scan_report_output = owasp_zap_build_step.add_output_directory( 162 | constants.SECURITY_SCANNING_RESULT_DIR 163 | ) 164 | pipeline.add_wave("OWASP-ZAP").add_pre(owasp_zap_build_step) 165 | 166 | # Send report to Security Hub 167 | pipeline.add_wave("SecurityHub-Report").add_pre( 168 | SecurityHubReportStep('SecurityHubReportStep', inputs=[scan_report_output, zap_scan_report_output]) 169 | ) 170 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==6.2.5 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib==2.50.0 2 | constructs>=10.0.0,<11.0.0 3 | 4 | -------------------------------------------------------------------------------- /scripts/install_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ##Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | ##SPDX-License-Identifier: MIT-0 5 | 6 | set -o errexit 7 | set -o verbose 8 | 9 | wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747-linux.zip 10 | unzip sonar-scanner-cli-4.7.0.2747-linux.zip 11 | 12 | wget https://github.com/jeremylong/DependencyCheck/releases/download/v7.3.0/dependency-check-7.3.0-release.zip 13 | unzip dependency-check-7.3.0-release.zip -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ##Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | ##SPDX-License-Identifier: MIT-0 5 | 6 | set -o errexit 7 | set -o verbose 8 | 9 | mkdir $SECURITY_SCANNING_OUTPUT_DIR 10 | 11 | ################ 12 | # SonarQube 13 | ################ 14 | 15 | ./sonar-scanner-4.7.0.2747-linux/bin/sonar-scanner -Dsonar.host.url=$HOST -Dsonar.login=$LOGIN -Dsonar.projectKey=$PROJECT -Dsonar.sources=backend/runtime/ > $SONAR_SCAN_OUTPUT_FILE 16 | sonar_task_id=$(cat $SONAR_SCAN_OUTPUT_FILE | egrep -o "task\?id=[^ ]+" | cut -d'=' -f2) 17 | stat="PENDING"; 18 | while [ "$stat" != "SUCCESS" ]; do 19 | if [ $stat = "FAILED" ] || [ $stat = "CANCELLED" ]; then 20 | echo "SonarQube task $sonar_task_id failed"; 21 | exit 1; 22 | fi 23 | stat=$(curl -u $LOGIN: $HOST/api/ce/task\?id=$sonar_task_id | jq -r '.task.status'); 24 | sleep 5; 25 | done 26 | 27 | sonar_analysis_id=$(curl -u $LOGIN: $HOST/api/ce/task\?id=$sonar_task_id | jq -r '.task.analysisId') 28 | curl -o $SONAR_QUALITY_STATUS_OUTPUT_FILE -u $LOGIN: $HOST/api/qualitygates/project_status\?analysisId=$sonar_analysis_id 29 | quality_status=$(cat $SONAR_QUALITY_STATUS_OUTPUT_FILE | jq -r '.projectStatus.status') 30 | curl -o $SONAR_ISSUES_OUTPUT_FILE -u $LOGIN: $HOST/api/issues/search?createdAfter=2022-11-10&componentKeys=$PROJECT&severities=CRITICAL,BLOCKER&types=VULNERABILITY&onComponentOnly=true 31 | 32 | if [ $FAIL_BUILD_FOR_SONAR_QUALITY_STATUS = true ] ; then 33 | if [ $quality_status = "ERROR" ] || [ $quality_status = "WARN" ]; then 34 | echo "in quality_status ERROR or WARN condition" 35 | exit 1; 36 | elif [ $quality_status = "OK" ]; then 37 | echo "in quality_status OK condition" 38 | else 39 | echo "in quality_status unexpected condition" 40 | exit 1; 41 | fi 42 | fi 43 | 44 | ######################## 45 | # OWASP Dependency-Check 46 | ######################## 47 | ./dependency-check/bin/dependency-check.sh --project $PROJECT --format JSON --prettyPrint --enableExperimental --scan backend/runtime --out $OWASP_DEPENDENCY_CHECK_OUTPUT_FILE 48 | echo "OWASP dependency check analysis status is completed..."; 49 | -------------------------------------------------------------------------------- /scripts/run_zap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ##Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | ##SPDX-License-Identifier: MIT-0 5 | 6 | set -o errexit 7 | set -o verbose 8 | 9 | mkdir $SECURITY_SCANNING_OUTPUT_DIR 10 | 11 | ################ 12 | # OWASP ZAP 13 | ################ 14 | 15 | echo $APPLICATION_URL_OUTPUT_KEY 16 | application_url=$(aws cloudformation list-exports --query "Exports[?Name=='$APPLICATION_URL_OUTPUT_KEY'].Value" --output text) 17 | docker run -v $(pwd):/zap/wrk/:rw --user root public.ecr.aws/deepfactor/zap2docker-stable:2.10.0-df zap-baseline.py -t $application_url -J $OWASP_ZAP_OUTPUT_FILE || true 18 | echo "OWASP ZAP analysis status is completed..."; -------------------------------------------------------------------------------- /sectools/infrastructure.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | import os 6 | from constructs import Construct 7 | import aws_cdk as cdk 8 | 9 | from aws_cdk import aws_ec2 as ec2 10 | from aws_cdk import aws_iam as iam 11 | from aws_cdk import aws_ecs as ecs 12 | from aws_cdk import aws_rds as rds 13 | from aws_cdk import aws_secretsmanager as secretsmanager 14 | from aws_cdk import aws_ssm as ssm 15 | 16 | import aws_cdk.aws_ecs_patterns as ecsp 17 | 18 | import constants 19 | 20 | ######################################### 21 | # In this section we need to create new # 22 | # infra resrouces used to deploy tools # 23 | ######################################### 24 | 25 | # Class defined to deploy new ECS infra and Security Tools 26 | class SecTools(cdk.Stack): 27 | 28 | def __init__(self, scope: Construct, id: str, **kwargs) -> None: 29 | super().__init__(scope, id, **kwargs) 30 | 31 | ######################################### 32 | # Shared components # 33 | ######################################### 34 | 35 | vpc = ec2.Vpc.from_lookup(self, "VPC", 36 | vpc_id=ssm.StringParameter.value_from_lookup( 37 | self, constants.CORE_VPC_PARAMETER_NAME) 38 | ) 39 | 40 | # Create IAM Role using AWS managed policies with permissions to deploy ECS Tasks 41 | ecs_task_role = iam.Role( 42 | self, 43 | id="ECSTaskRole", 44 | role_name="ECSTaskRole", 45 | assumed_by=iam.ServicePrincipal(service="ecs-tasks.amazonaws.com"), 46 | managed_policies=[ 47 | iam.ManagedPolicy.from_aws_managed_policy_name( 48 | "service-role/AmazonECSTaskExecutionRolePolicy") 49 | ] 50 | ) 51 | 52 | cluster = ecs.Cluster(self, "SecurityToolsECSCluster", 53 | capacity=ecs.AddCapacityOptions( 54 | instance_type=ec2.InstanceType('m5.large')), 55 | vpc=vpc 56 | ) 57 | 58 | asg = cluster.autoscaling_group 59 | asg.add_user_data( 60 | 'sudo sysctl -w vm.max_map_count=524288', 61 | 'sudo sysctl -w fs.file-max=131072', 62 | 'sudo ulimit -n 131072', 63 | 'sudo ulimit -u 8192' 64 | 'sudo echo "vm.max_map_count=524288" >> /etc/sysctl.conf', 65 | 'sudo sysctl -p' 66 | ) 67 | 68 | ######################################### 69 | # SonarQube # 70 | ######################################### 71 | 72 | # Create SG for RDS PostGres to be used by Sonarqube allowing all outbound traffic 73 | self.sg = ec2.SecurityGroup( 74 | self, 'RDSSecurityGroup', 75 | vpc=vpc, 76 | allow_all_outbound=True, 77 | description="RDS Instance Security Group" 78 | ) 79 | 80 | # Create Sonarqube Postgres DB for Sonarqube service connect 81 | self.database = rds.DatabaseInstance(self, 'SonarqubeDB', 82 | engine=rds.DatabaseInstanceEngine.postgres( 83 | version=rds.PostgresEngineVersion.VER_14_2), 84 | database_name="sonarqube", 85 | credentials=rds.Credentials.from_generated_secret( 86 | "sonar_creds"), 87 | instance_type=ec2.InstanceType.of( 88 | ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), 89 | vpc=vpc, 90 | multi_az=True, 91 | vpc_subnets=ec2.SubnetSelection( 92 | subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS, 93 | ), 94 | publicly_accessible=False, 95 | security_groups=[self.sg] 96 | ) 97 | 98 | rds_url = 'jdbc:postgresql://{}/sonarqube'.format( 99 | self.database.db_instance_endpoint_address) 100 | 101 | # Addins SG rule allowing access to DB from VPC 102 | self.sg.add_ingress_rule( 103 | ec2.Peer.ipv4('10.0.0.0/16'), ec2.Port.tcp(5432), "AccessToDB" 104 | ) 105 | 106 | sonarqube_ecs_service = ecsp.ApplicationLoadBalancedEc2Service(self, "SonarquebEcsTask", 107 | task_image_options=ecsp.ApplicationLoadBalancedTaskImageOptions( 108 | environment={ 109 | 'sonar.jdbc.url': rds_url, 110 | }, 111 | image=ecs.ContainerImage.from_registry( 112 | "public.ecr.aws/docker/library/sonarqube:latest"), 113 | container_port=9000, 114 | secrets={ 115 | "sonar.jdbc.username": ecs.Secret.from_secrets_manager(self.database.secret, field="username"), 116 | "sonar.jdbc.password": ecs.Secret.from_secrets_manager(self.database.secret, field="password") 117 | }, 118 | task_role=ecs_task_role, 119 | ), 120 | public_load_balancer=True, 121 | cluster=cluster, 122 | cpu=512, 123 | memory_limit_mib=2048, 124 | ) 125 | 126 | sonarqube_secret = secretsmanager.Secret(self, "SonarQubeSecret", 127 | secret_object_value={ 128 | "host": cdk.SecretValue.unsafe_plain_text(f'http://{sonarqube_ecs_service.load_balancer.load_balancer_dns_name}') 129 | } 130 | ) 131 | 132 | ######################################### 133 | # Outputs # 134 | ######################################### 135 | 136 | cdk.CfnOutput(self, 'SonarQubeSecretArnOutput', 137 | value=sonarqube_secret.secret_full_arn, 138 | export_name=constants.SONARQUBE_SECRET_ARN_EXPORT_NAME 139 | ) 140 | -------------------------------------------------------------------------------- /securityhub/runtime/invoke_action_function.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | 6 | import os 7 | import boto3 8 | import botocore 9 | import zipfile 10 | import tempfile 11 | import traceback 12 | import json 13 | 14 | from boto3.session import Session 15 | from datetime import datetime, timezone 16 | 17 | code_pipeline = boto3.client('codepipeline') 18 | securityhub = boto3.client('securityhub') 19 | 20 | securityhub_product_arn = os.environ['SECURITY_HUB_PRODUCT_ARN'] 21 | account_id = boto3.client('sts').get_caller_identity().get('Account') 22 | region = os.environ['AWS_REGION'] 23 | 24 | def setup_s3_client(job_data): 25 | key_id = job_data['artifactCredentials']['accessKeyId'] 26 | key_secret = job_data['artifactCredentials']['secretAccessKey'] 27 | session_token = job_data['artifactCredentials']['sessionToken'] 28 | 29 | session = Session(aws_access_key_id=key_id, 30 | aws_secret_access_key=key_secret, 31 | aws_session_token=session_token) 32 | return session.client('s3', config=botocore.client.Config(signature_version='s3v4')) 33 | 34 | def get_scan_results(s3, scan_report_s3_location): 35 | tmp_file = tempfile.NamedTemporaryFile() 36 | bucket = scan_report_s3_location['bucketName'] 37 | key = scan_report_s3_location['objectKey'] 38 | 39 | scan_results = {} 40 | 41 | with tempfile.NamedTemporaryFile() as tmp_file: 42 | s3.download_file(bucket, key, tmp_file.name) 43 | with zipfile.ZipFile(tmp_file.name, 'r') as zip: 44 | for filename in zip.namelist(): 45 | scan_results[filename] = zip.read(filename).decode('utf-8') 46 | 47 | return scan_results 48 | 49 | def process_zap_message(zap_message, job_id): 50 | finding_type = "Dynamic Code Analysis/Zed Attack Proxy" 51 | created_at = datetime.now(timezone.utc).isoformat() 52 | 53 | findings = [] 54 | 55 | for site in zap_message['site']: 56 | source_url = site['@name'] 57 | 58 | for alert in site['alerts']: 59 | report_severity = alert['riskcode'] 60 | if report_severity == '2': 61 | normalized_severity = 50 62 | elif report_severity == '3': 63 | normalized_severity = 90 64 | else: 65 | normalized_severity= 20 66 | 67 | findings.append(build_security_hub_finding( 68 | account_id=account_id, 69 | region=region, 70 | generator_id=f"codepipeline-{job_id}-zap-alert-{alert['alertRef']}", 71 | finding_id=alert['alertRef'], 72 | finding_type=finding_type, 73 | created_at=created_at, 74 | finding_title=f"CodePipeline/ZapAnalysis/Alert/{alert['alert']}", 75 | finding_description=alert['desc'], 76 | normalized_severity=normalized_severity, 77 | source_url=source_url, 78 | job_id=job_id 79 | )) 80 | 81 | return findings 82 | 83 | def process_dependency_check_message(dependency_check_message, job_id): 84 | finding_type = "Software Composition Analysis/DependencyCheck" 85 | created_at = datetime.now(timezone.utc).isoformat() 86 | 87 | findings = [] 88 | 89 | for dependency in dependency_check_message["dependencies"]: 90 | if "vulnerabilities" in dependency: 91 | vulnerability = dependency["vulnerabilities"][0] 92 | if vulnerability['severity'] == "CRITICAL": 93 | normalized_severity = 90 94 | elif vulnerability['severity'] == "HIGH": 95 | normalized_severity = 70 96 | else: 97 | normalized_severity = 50 98 | finding_description = f"{vulnerability['name']}: Package {dependency['packages'][0]['id']}. Vulnerability ID: {dependency['vulnerabilityIds'][0]['id']}" 99 | findings.append(build_security_hub_finding( 100 | account_id=account_id, 101 | region=region, 102 | generator_id=f"codepipeline-{job_id}-dependency-check-vulnerability-{dependency['packages'][0]['id']}", 103 | finding_id=dependency['packages'][0]['id'], 104 | finding_type=finding_type, 105 | created_at=created_at, 106 | finding_title=f"CodePipeline/DependencyCheckAnalysis/Vulnerability/{vulnerability['name']}", 107 | finding_description=finding_description, 108 | normalized_severity=normalized_severity, 109 | source_url=dependency['packages'][0]['url'], 110 | job_id=job_id 111 | )) 112 | 113 | return findings 114 | 115 | def process_sonar_message(sonar_message, job_id): 116 | finding_type = "Static Code Analysis/SonarQube" 117 | created_at = datetime.now(timezone.utc).isoformat() 118 | 119 | sonar_findings = [] 120 | 121 | for issue in filter(lambda issue: issue['type'] == 'VULNERABILITY', sonar_message['issues']): 122 | finding_id = f"{issue['hash']}-sonarqube-codepipeline-{job_id}" 123 | finding_description = f"{issue['type']}: {issue['message']}. Component: {issue['component']}. Issue ID: {issue['hash']}" 124 | report_severity = issue['severity'] 125 | if report_severity == 'MAJOR': 126 | normalized_severity = 70 127 | elif report_severity == 'BLOCKER': 128 | normalized_severity = 90 129 | elif report_severity == 'CRITICAL': 130 | normalized_severity = 90 131 | else: 132 | normalized_severity= 20 133 | 134 | sonar_findings.append(build_security_hub_finding( 135 | account_id=account_id, 136 | region=region, 137 | generator_id=f"codepipeline-{job_id}-sonarqube-issue-{issue['hash']}", 138 | finding_id=finding_id, 139 | finding_type=finding_type, 140 | created_at=created_at, 141 | finding_title=f"CodePipeline/SonarQubeCodeAnalysis/Issue/{issue['hash']}", 142 | finding_description=finding_description, 143 | normalized_severity=normalized_severity, 144 | source_url='', 145 | job_id=job_id 146 | )) 147 | 148 | return sonar_findings 149 | 150 | def build_security_hub_finding( 151 | account_id, 152 | region, 153 | generator_id, 154 | finding_id, 155 | finding_type, 156 | created_at, 157 | finding_title, 158 | finding_description, 159 | normalized_severity, 160 | source_url, 161 | job_id 162 | ): 163 | payload = { 164 | "SchemaVersion": "2018-10-08", 165 | "Id": finding_id, 166 | "ProductArn": securityhub_product_arn, 167 | "GeneratorId": generator_id, 168 | "AwsAccountId": account_id, 169 | "Types": [ 170 | finding_type 171 | ], 172 | "CreatedAt": created_at, 173 | "UpdatedAt": created_at, 174 | "Severity": { 175 | "Normalized": normalized_severity, 176 | }, 177 | "Title": finding_title, 178 | "Description": finding_description, 179 | 'Resources': [ 180 | { 181 | 'Id': job_id, 182 | 'Type': "CodePipeline", 183 | 'Partition': "aws", 184 | 'Region': region 185 | } 186 | ], 187 | } 188 | 189 | if source_url: 190 | payload['SourceUrl'] = source_url 191 | 192 | return payload 193 | 194 | def handler(event, context): 195 | 196 | try: 197 | job_id = event['CodePipeline.job']['id'] 198 | job_data = event['CodePipeline.job']['data'] 199 | artifacts = job_data['inputArtifacts'] 200 | 201 | s3 = setup_s3_client(job_data) 202 | 203 | securityhub_findings = [] 204 | 205 | for artifact in artifacts: 206 | 207 | scan_report_s3_location = artifact['location']['s3Location'] 208 | 209 | scan_results = get_scan_results(s3, scan_report_s3_location) 210 | 211 | if "sonar_issues.json" in scan_results: 212 | securityhub_findings.extend( 213 | process_sonar_message( 214 | json.loads(scan_results['sonar_issues.json']), 215 | job_id 216 | ) 217 | ) 218 | 219 | if "owasp_dependency_check_result.json" in scan_results: 220 | securityhub_findings.extend( 221 | process_dependency_check_message( 222 | json.loads(scan_results['owasp_dependency_check_result.json']), 223 | job_id 224 | ) 225 | ) 226 | 227 | if "owasp_zap_result.json" in scan_results: 228 | securityhub_findings.extend( 229 | process_zap_message( 230 | json.loads(scan_results['owasp_zap_result.json']), 231 | job_id 232 | ) 233 | ) 234 | 235 | response = securityhub.batch_import_findings(Findings=securityhub_findings) 236 | if response['FailedCount'] > 0: 237 | raise Exception("Failed to import finding: {}".format(response['FailedCount'])) 238 | 239 | code_pipeline.put_job_success_result(jobId=job_id) 240 | 241 | except Exception as e: 242 | traceback.print_exc() 243 | code_pipeline.put_job_failure_result( 244 | jobId=job_id, 245 | failureDetails={ 246 | 'message': 'Function exception: ' + str(e), 247 | 'type': 'JobFailed' 248 | } 249 | ) 250 | 251 | return "Complete." 252 | -------------------------------------------------------------------------------- /securityhub/securityhub_report_step.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | 6 | import jsii 7 | import pathlib 8 | 9 | from aws_cdk import aws_codepipeline_actions as cpa 10 | from aws_cdk import aws_codepipeline as codepipeline 11 | from aws_cdk import pipelines 12 | from aws_cdk import aws_lambda as lambda_ 13 | from aws_cdk import aws_iam as iam 14 | 15 | from constructs import Construct 16 | 17 | import constants 18 | 19 | @jsii.implements(pipelines.ICodePipelineActionFactory) 20 | class SecurityHubReportStep(pipelines.Step): 21 | def __init__(self, id_: str, inputs: pipelines.FileSet, **kwargs) -> None: 22 | super().__init__(id_) 23 | 24 | self.inputs = inputs 25 | 26 | @jsii.member(jsii_name="produceAction") 27 | def produce_action( 28 | self, 29 | stage: codepipeline.IStage, 30 | options: pipelines.ProduceActionOptions, 31 | ) -> pipelines.CodePipelineActionFactoryResult: 32 | 33 | runtime_asset = str(pathlib.Path(__file__).parent.joinpath("runtime").resolve()) 34 | function = lambda_.Function(options.scope, "InvokeActionFunction", 35 | runtime=lambda_.Runtime.PYTHON_3_9, 36 | handler="invoke_action_function.handler", 37 | code=lambda_.Code.from_asset(runtime_asset), 38 | environment={ 39 | "SECURITY_HUB_PRODUCT_ARN": constants.SECURITY_HUB_PRODUCT_ARN 40 | } 41 | ) 42 | function.add_to_role_policy( 43 | statement=iam.PolicyStatement( 44 | actions=[ 45 | 'securityhub:BatchImportFindings' 46 | ], 47 | resources=[constants.SECURITY_HUB_PRODUCT_ARN], 48 | ) 49 | ) 50 | 51 | stage.add_action( 52 | cpa.LambdaInvokeAction( 53 | action_name="SecurityHubReport", 54 | inputs= [options.artifacts.to_code_pipeline(input) for input in self.inputs], 55 | lambda_=function, 56 | ) 57 | ) 58 | 59 | return pipelines.CodePipelineActionFactoryResult(run_orders_consumed=1) -------------------------------------------------------------------------------- /shared/network/infrastructure.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | import aws_cdk.aws_ec2 as ec2 6 | import aws_cdk.aws_ssm as ssm 7 | from constructs import Construct 8 | 9 | import constants 10 | 11 | class Network(Construct): 12 | def __init__(self, scope: Construct, id_: str) -> None: 13 | super().__init__(scope, id_) 14 | 15 | self.vpc = ec2.Vpc(self, 'VPC', 16 | ip_addresses=ec2.IpAddresses.cidr("10.0.0.0/16") 17 | ) 18 | 19 | ssm.StringParameter(self, 'VPCID', 20 | parameter_name=constants.CORE_VPC_PARAMETER_NAME, 21 | string_value=self.vpc.vpc_id 22 | ) -------------------------------------------------------------------------------- /shared/shared_infra.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | import aws_cdk as cdk 6 | from constructs import Construct 7 | 8 | from shared.network.infrastructure import Network 9 | 10 | class SharedInfraStack(cdk.Stack): 11 | def __init__(self, scope: Construct, id_: str, **kwargs) -> None: 12 | super().__init__(scope, id_, **kwargs) 13 | 14 | self.network = Network(self, "Network") 15 | 16 | cdk.CfnOutput(self, 'CoreVPC', value=self.network.vpc.vpc_id, export_name='CoreVPCId') 17 | -------------------------------------------------------------------------------- /vulnerable_code/hard_coded_secret.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: MIT-0 4 | """ 5 | from cryptography.fernet import Fernet 6 | 7 | def encrypt(): 8 | key = Fernet.generate_key() 9 | f = Fernet(key) 10 | f.encrypt(b"a secret message") 11 | --------------------------------------------------------------------------------