├── CODE_OF_CONDUCT.md ├── dockerfile-wp.dockerfile ├── LICENSE ├── lambda-functions ├── securityhub.py └── import_findings_security_hub.py ├── CONTRIBUTING.md ├── README.md └── devsecops-codepipeline-template.yaml /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 | -------------------------------------------------------------------------------- /dockerfile-wp.dockerfile: -------------------------------------------------------------------------------- 1 | FROM wordpress:5.5-apache 2 | 3 | # APT Update/Upgrade, then install packages we need 4 | RUN apt update && \ 5 | apt upgrade -y && \ 6 | apt autoremove && \ 7 | apt install -y \ 8 | wget 9 | 10 | #ADD wp-config.php /var/www/html/wp-config.php 11 | 12 | # Install WP-CLI 13 | RUN wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ 14 | php wp-cli.phar --info&& \ 15 | chmod +x wp-cli.phar && \ 16 | mv wp-cli.phar /usr/local/bin/wp 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lambda-functions/securityhub.py: -------------------------------------------------------------------------------- 1 | """ 2 | AWS Security Hub Integration 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | SPDX-License-Identifier: MIT-0 5 | """ 6 | import sys 7 | import logging 8 | sys.path.insert(0, "external") 9 | import boto3 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | securityhub = boto3.client('securityhub') 14 | 15 | # This function import agregated report findings to securityhub 16 | def import_finding_to_sh(count: int, account_id: str, region: str, created_at: str, source_repository: str, 17 | source_branch: str, source_commitid: str, build_id: str, report_url: str, finding_id: str, generator_id: str, 18 | normalized_severity: str, severity: str, finding_type: str, finding_title: str, finding_description: str, best_practices_cfn: str): 19 | print("called securityhub.py..................") 20 | new_findings = [] 21 | 22 | new_findings.append({ 23 | "SchemaVersion": "2018-10-08", 24 | "Id": finding_id, 25 | "ProductArn": "arn:aws-us-gov:securityhub:{0}:{1}:product/{1}/default".format(region, account_id), 26 | "GeneratorId": generator_id, 27 | "AwsAccountId": account_id, 28 | "Types": [ 29 | "Software and Configuration Checks/AWS Security Best Practices/{0}".format( 30 | finding_type) 31 | ], 32 | "CreatedAt": created_at, 33 | "UpdatedAt": created_at, 34 | "Severity": { 35 | "Normalized": normalized_severity, 36 | }, 37 | "Title": f"{count}-{finding_title}", 38 | "Description": f"{finding_description}", 39 | 'Remediation': { 40 | 'Recommendation': { 41 | 'Text': 'For directions on how to fix this issue, see the AWS Cloud Formation documentation and AWS Best practices', 42 | 'Url': best_practices_cfn 43 | } 44 | }, 45 | 'SourceUrl': report_url, 46 | 'Resources': [ 47 | { 48 | 'Id': build_id, 49 | 'Type': "CodeBuild", 50 | 'Partition': "aws", 51 | 'Region': region 52 | } 53 | ], 54 | }) 55 | response = securityhub.batch_import_findings(Findings=new_findings) 56 | if response['FailedCount'] > 0: 57 | logger.error("Error importing finding: " + response) 58 | raise Exception("Failed to import finding: {}".format(response['FailedCount'])) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS DevSecOps Pipeline 2 | 3 | Kubernetes DevSecOps pipeline using AWS cloudnative services and open source security vulnerability scanning tools. 4 | 5 | ![CodeBuild badge](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoieDJkVmY0VXl2bVRjaFdBYkRzZExTNS9ZTUZVQXE4Sy9GMkh1dk1sOE54VkJKcEowOGdXcnJiZDlGL1RGeXJGUmR5UHlWT1psaks2N1dKbk5qUSt6L1BnPSIsIml2UGFyYW1ldGVyU3BlYyI6InhST3ZVeEZ6bkxLWC9IZG4iLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master) 6 | 7 | This DevSecOps pipeline uses AWS DevOps tools AWS CodeCommit, AWS CodeBuild, and AWS CodePipeline along with other AWS services. It is highly recommended to fully test the pipeline in lower environments and adjust as needed before deploying to production. 8 | 9 | ### Build and Test: 10 | 11 | The buildspecs and property files for vulnerability scanning using AWS CodeBuild: 12 | * buildspec-gitsecrets.yml: buildspec file to perform secret analysis using open source git-secrets. 13 | * buildspec-anchore.yml: buildspec file to perform SCA/SAST analysis using open source Anchore. 14 | * buildspec-snyk.yml: buildspec file to perform SCA/SAST analysis using open source Snyk. 15 | * buildspec-ecr-yml: buildspec file to retrive ECR SCA/SAST analysis results and deploy to staging EKS cluster. 16 | * buildspec-owasp-zap.yml: buildspec file to perform DAST analysis using open source OWASP ZAP. 17 | * buildspec-prod.yml: buildspec file to deploy to prod EKS cluster. 18 | * buildspec-owasp-zap.yml: buildspec file to perform DAST analysis using OWASP Zap. 19 | * dockerfile-wp.dockerfile: sample docker file. Please replace with your Dockerfile. 20 | 21 | ### Lambda files: 22 | 23 | AWS lambda is used to parse the scanning analysis results and post it to AWS Security Hub 24 | * import_findings_security_hub.py: to parse the scanning results, extract the vulnerability details. 25 | * securityhub.py: to post the vulnerability details to AWS Security Hub in ASFF format (AWS Security Finding Format). 26 | 27 | ### CloudFormation for Pipeline: 28 | 29 | * devsecops-codepipeline-template.yaml: CloudFormation template to deploy the Kubernetes DevSecOps Pipeline 30 | 31 | ## Prerequisites 32 | 33 | 1. An EKS cluster environment with your application deployed. In this post, we use PHP WordPress as a sample application, but you can use any other application. 34 | 2. Sysdig Falco installed on an EKS cluster. Sysdig Falco captures events on the EKS cluster and sends those events to CloudWatch using AWS FireLens. For implementation instructions, see Implementing Runtime security in Amazon EKS using CNCF Falco. This step is required only if you need to implement RASP in the software factory. 35 | 3. A CodeCommit repo with your application code and a Dockerfile. For more information, see Create an AWS CodeCommit repository. 36 | 4. An Amazon ECR repo to store container images and scan for vulnerabilities. Enable vulnerability scanning on image push in Amazon ECR. You can enable or disable the automatic scanning on image push via the Amazon ECR 37 | 5. The provided buildspec-*.yml files for git-secrets, Anchore, Snyk, Amazon ECR, OWASP ZAP, and your Kubernetes deployment .yml files uploaded to the root of the application code repository. Please update the Kubernetes (kubectl) commands in the buildspec files as needed. 38 | 6. A Snyk API key if you use Snyk as a SAST tool. 39 | 7. The Lambda function uploaded to an S3 bucket. We use this function to parse the scan reports and post the results to Security Hub. 40 | 8. An OWASP ZAP URL and generated API key for dynamic web scanning. 41 | 9. An application web URL to run the DAST testing. 42 | 10. An email address to receive approval notifications for deployment, pipeline change notifications, and CloudTrail events. 43 | 11. AWS Config and Security Hub services enabled. For instructions, see Managing the Configuration Recorder and Enabling Security Hub manually, respectively. 44 | 45 | ## Deploying pipeline: 46 | 47 | 1. Download the CloudFormation template and pipeline code from the GitHub repo. 48 | 2. Sign in to your AWS account if you have not done so already. 49 | 3. On the CloudFormation console, choose Create Stack. 50 | 4. Choose the CloudFormation pipeline template. 51 | 5. Choose Next. 52 | 6. Under Code, provide the following information: 53 | i. Code details, such as repository name and the branch to trigger the pipeline. 54 | ii.The Amazon ECR container image repository name. 55 | 7. Under SAST, provide the following information: 56 | i. Choose the SAST tool (Anchore or Snyk) for code analysis. 57 | ii.If you select Snyk, provide an API key for Snyk. 58 | 8. Under DAST, choose the DAST tool (OWASP ZAP) for dynamic testing and enter the API token, DAST tool URL, and the application URL to run the scan. 59 | 9. Under Lambda functions, enter the Lambda function S3 bucket name, filename, and the handler name. 60 | 10. For STG EKS cluster, enter the staging EKS cluster name. 61 | 11. For PRD EKS cluster, enter the production EKS cluster name to which this pipeline deploys the container image. 62 | 12. Under General, enter the email addresses to receive notifications for approvals and pipeline status changes. 63 | 13. Choose Next. 64 | 14. Complete the stack. 65 | 15. After the pipeline is deployed, confirm the subscription by choosing the provided link in the email to receive notifications. 66 | 67 | The provided CloudFormation template in this post is formatted for AWS GovCloud. If you’re setting this up in a standard Region, you have to adjust the partition name in the CloudFormation template. For example, change ARN values from arn:aws-us-gov to arn:aws. 68 | 69 | ## Cleanup 70 | 71 | 1. Delete the EKS cluster. 72 | 2. Delete the S3 bucket. 73 | 3. Delete the CodeCommit repo. 74 | 4. Delete the Amazon ECR repo. 75 | 5. Disable Security Hub. 76 | 6. Disable AWS Config. 77 | 7. Delete the pipeline CloudFormation stack. 78 | 79 | ## License 80 | 81 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 82 | SPDX-License-Identifier: MIT-0 -------------------------------------------------------------------------------- /lambda-functions/import_findings_security_hub.py: -------------------------------------------------------------------------------- 1 | """ 2 | Imports finding in Security Hub and upload the reports to S3 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | SPDX-License-Identifier: MIT-0 5 | """ 6 | 7 | import os 8 | import json 9 | import logging 10 | import boto3 11 | import securityhub 12 | from datetime import datetime, timezone 13 | 14 | logger = logging.getLogger() 15 | logger.setLevel(logging.DEBUG) 16 | 17 | FINDING_TITLE = "CodeAnalysis" 18 | FINDING_DESCRIPTION_TEMPLATE = "Summarized report of code scan with {0}" 19 | FINDING_TYPE_TEMPLATE = "{0} code scan" 20 | BEST_PRACTICES_CFN = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/best-practices.html" 21 | BEST_PRACTICES_OWASP = "https://owasp.org/www-project-top-ten/" 22 | report_url = "https://aws.amazon.com" 23 | vul_level = "LOW" 24 | 25 | def process_message(event): 26 | """ Process Lambda Event """ 27 | print('#### complete event details') 28 | print(event) 29 | if event['messageType'] == 'CodeScanReport': 30 | account_id = boto3.client('sts').get_caller_identity().get('Account') 31 | region = os.environ['AWS_REGION'] 32 | created_at = event['createdAt'] 33 | source_repository = event['source_repository'] 34 | source_branch = event['source_branch'] 35 | source_commitid = event['source_commitid'] 36 | build_id = event['build_id'] 37 | report_type = event['reportType'] 38 | finding_type = FINDING_TYPE_TEMPLATE.format(report_type) 39 | generator_id = f"{report_type.lower()}-{source_repository}-{source_branch}" 40 | vul_level = "LOW" 41 | ##upload to S3 bucket. 42 | s3 = boto3.client('s3') 43 | s3bucket = "pipeline-artifact-bucket-" + account_id 44 | key = f"reports/{event['reportType']}/{build_id}-{created_at}.json" 45 | s3.put_object(Bucket=s3bucket, Body=json.dumps(event), Key=key, ServerSideEncryption='aws:kms') 46 | report_url = f"https://s3.console.aws.amazon.com/s3/object/{s3bucket}/{key}?region={region}" 47 | 48 | if ( event['reportType'] == 'ECR' ): 49 | FINDING_TITLE = "AWS ECR StaticCode Analysis" 50 | severity = 50 51 | vuln_ct = event['report']['imageScanFindings']['findings'] 52 | vuln_count = len(vuln_ct) 53 | count = 1 54 | title_list = [] 55 | for i in range(vuln_count): 56 | severity = event['report']['imageScanFindings']['findings'][i]['severity'] 57 | name = event['report']['imageScanFindings']['findings'][i]['name'] 58 | url = event['report']['imageScanFindings']['findings'][i]['uri'] 59 | if severity not in ['Negligible', 'Unknown', 'INFORMATIONAL']: 60 | normalized_severity = assign_normalized_severity(severity) 61 | finding_description = f"{count}---Name:{name}---Sevierity:{severity}---URL:{url}" 62 | finding_id = f"{count}-{report_type.lower()}-{build_id}" 63 | created_at = datetime.now(timezone.utc).isoformat() 64 | count += 1 65 | securityhub.import_finding_to_sh(count, account_id, region, created_at, source_repository, source_branch, source_commitid, build_id, report_url, finding_id, generator_id, normalized_severity, severity, finding_type, FINDING_TITLE, finding_description, BEST_PRACTICES_CFN) 66 | 67 | elif ( event['reportType'] == 'SNYK' ): 68 | FINDING_TITLE = "Snyk StaticCode Analysis" 69 | severity = 50 70 | vuln_ct = event['report']['vulnerabilities'] 71 | vuln_count = len(vuln_ct) 72 | print(f"alert count is {vuln_count}") 73 | count = 1 74 | title_list = [] 75 | for i in range(vuln_count): 76 | title = event['report']['vulnerabilities'][i]['title'] 77 | if title not in title_list: 78 | title_list.append(title) 79 | severity = event['report']['vulnerabilities'][i]['severity'] 80 | packageName = event['report']['vulnerabilities'][i]['packageName'] 81 | cvssScore = event['report']['vulnerabilities'][i]['cvssScore'] 82 | nvdSeverity = event['report']['vulnerabilities'][i]['nvdSeverity'] 83 | if severity not in ['Negligible', 'Unknown']: 84 | normalized_severity = assign_normalized_severity(severity) 85 | finding_description = f"{count}---Title:{title}---Package:{packageName}---Sevierity:{severity}---CVSSv3_Score:{cvssScore}" 86 | finding_id = f"{count}-{report_type.lower()}-{build_id}" 87 | created_at = datetime.now(timezone.utc).isoformat() 88 | count += 1 89 | securityhub.import_finding_to_sh(count, account_id, region, created_at, source_repository, source_branch, source_commitid, build_id, report_url, finding_id, generator_id, normalized_severity, severity, finding_type, FINDING_TITLE, finding_description, BEST_PRACTICES_CFN) 90 | 91 | elif ( event['reportType'] == 'ANCHORE' ): 92 | FINDING_TITLE = "Anchore StaticCode Analysis" 93 | severity = 50 94 | vuln_ct = event['report']['vulnerabilities'] 95 | vuln_count = len(vuln_ct) 96 | print(f"alert count is {vuln_count}") 97 | count = 1 98 | for i in range(vuln_count): 99 | severity = event['report']['vulnerabilities'][i]['severity'] 100 | vuln = event['report']['vulnerabilities'][i]['vuln'] 101 | url = event['report']['vulnerabilities'][i]['url'] 102 | feed_group = event['report']['vulnerabilities'][i]['feed_group'] 103 | package = event['report']['vulnerabilities'][i]['package'] 104 | if severity not in ['Negligible', 'Unknown']: 105 | normalized_severity = assign_normalized_severity(severity) 106 | finding_description = f"{count}---Package:{package}--- Vulnerability:{vuln}---Details:{url}" 107 | print(f"finding description is: {finding_description}") 108 | finding_id = f"{count}-{report_type.lower()}-{build_id}" 109 | created_at = datetime.now(timezone.utc).isoformat() 110 | count += 1 111 | securityhub.import_finding_to_sh(count, account_id, region, created_at, source_repository, source_branch, source_commitid, build_id, report_url, finding_id, generator_id, normalized_severity, severity, finding_type, FINDING_TITLE, finding_description, BEST_PRACTICES_CFN) 112 | 113 | elif event['reportType'] == 'OWASP-Zap': 114 | severity = 50 115 | vul_level = "LOW" 116 | FINDING_TITLE = "OWASP ZAP DynamicCode Analysis" 117 | alert_ct = event['report']['site'][0]['alerts'] 118 | alert_count = len(alert_ct) 119 | for alertno in range(alert_count): 120 | risk_desc = event['report']['site'][0]['alerts'][alertno]['riskdesc'] 121 | severity = risk_desc[0:3] 122 | normalized_severity = assign_normalized_severity(severity) 123 | instances = len(event['report']['site'][0]['alerts'][alertno]['instances']) 124 | finding_description = f"{alertno}-Vulerability:{event['report']['site'][0]['alerts'][alertno]['alert']}-Total occurances of this issue:{instances}" 125 | finding_id = f"{alertno}-{report_type.lower()}-{build_id}" 126 | created_at = datetime.now(timezone.utc).isoformat() 127 | securityhub.import_finding_to_sh(alertno, account_id, region, created_at, source_repository, source_branch, source_commitid, build_id, report_url, finding_id, generator_id, normalized_severity, severity, finding_type, FINDING_TITLE, finding_description, BEST_PRACTICES_OWASP) 128 | else: 129 | print("Invalid report type was provided") 130 | return vul_level 131 | else: 132 | logger.error("Report type not supported:") 133 | 134 | def assign_normalized_severity(severity): 135 | if severity in ['MAJOR', 'MEDIUM', 'Med', 'Medium', 'medium']: 136 | normalized_severity = 70 137 | vul_level = "NOTLOW" 138 | elif severity in ['CRITICAL', 'BLOCKER', 'MAJOR', 'HIGH', 'Hig', 'High', 'high']: 139 | normalized_severity = 90 140 | vul_level = "NOTLOW" 141 | elif severity in ['LOW', 'Low', 'Inf', 'low', 'INFORMATIONAL']: 142 | normalized_severity = 20 143 | vul_level = "LOW" 144 | else: 145 | normalized_severity= 20 146 | return normalized_severity 147 | 148 | def lambda_handler(event, context): 149 | """ Lambda entrypoint """ 150 | try: 151 | logger.info("Starting function") 152 | return process_message(event) 153 | except Exception as error: 154 | logger.error("Error {}".format(error)) 155 | raise 156 | 157 | -------------------------------------------------------------------------------- /devsecops-codepipeline-template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Parameters: 3 | BranchName: 4 | Description: CodeCommit branch name 5 | Type: String 6 | Default: master 7 | RepositoryName: 8 | Description: CodeComit repository name 9 | Type: String 10 | Default: eksrepo 11 | DASTTool: 12 | Description: Select the DAST tool from the list 13 | Type: String 14 | AllowedValues: [OWASP-Zap] 15 | OwaspZapURLName: 16 | Description: OWASP Zap DAST Tool URL 17 | Type: String 18 | Default: http://18.221.16.46:81/ 19 | ApplicationURLForDASTScan: 20 | Description: Application URL to run the DAST/Pen testing 21 | Type: String 22 | Default: https://eksstg.smanepalli.com 23 | OwaspZapApiKey: 24 | Description: OWASP Zap ApiKey 25 | Type: String 26 | NoEcho: true 27 | Default: zapapikey 28 | SnykApiKey: 29 | Description: Snyk ApiKey 30 | Type: String 31 | NoEcho: true 32 | LambdaPackageLoc: 33 | Description: S3 loc of lambda package 34 | Type: String 35 | Default: manepals-artifacts-eks 36 | LambdaPackageS3Key: 37 | Description: S3 Key for Lambda package object 38 | Type: String 39 | Default: import_findings_security_hub.zip 40 | LambdaHandlerName: 41 | Description: Name of the lambda handler 42 | Type: String 43 | Default: import_findings_security_hub.lambda_handler 44 | SASTTool: 45 | Description: Select the SAST tool from the list 46 | Type: String 47 | AllowedValues: [Anchore, Snyk] 48 | PipelineNotificationsEmail: 49 | Description: Email address to receive SNS notifications for pipelineChanges 50 | Type: String 51 | Default: manepals@amazon.com 52 | PipelineApproverEmail: 53 | Description: Email address to send approval notifications 54 | Type: String 55 | Default: manepals@amazon.com 56 | EksClusterName: 57 | Type: String 58 | Description: The name of the EKS cluster created 59 | Default: prod-eks 60 | MinLength: 1 61 | MaxLength: 100 62 | ConstraintDescription: You must enter the EKS cluster name 63 | EcrRepositoryName: 64 | Description: CodeComit repository name 65 | Type: String 66 | Default: eks-container-repo 67 | EksProdClusterName: 68 | Type: String 69 | Description: The name of the EKS cluster created 70 | Default: eks-prod-1 71 | MinLength: 1 72 | MaxLength: 100 73 | ConstraintDescription: You must enter the EKS cluster name 74 | 75 | #### Parameter Groups, Labels 76 | Metadata: 77 | 'AWS::CloudFormation::Interface': 78 | ParameterGroups: 79 | - Label: 80 | default: Code 81 | Parameters: 82 | - BranchName 83 | - RepositoryName 84 | - EcrRepositoryName 85 | - EksClusterName 86 | - EksProdClusterName 87 | - Label: 88 | default: SAST 89 | Parameters: 90 | - SASTTool 91 | - SnykApiKey 92 | - Label: 93 | default: DAST 94 | Parameters: 95 | - DASTTool 96 | - OwaspZapURLName 97 | - OwaspZapApiKey 98 | - ApplicationURLForDASTScan 99 | - Label: 100 | default: Lambda function 101 | Parameters: 102 | - LambdaPackageLoc 103 | - LambdaPackageS3Key 104 | - LambdaHandlerName 105 | - Label: 106 | default: General 107 | Parameters: 108 | - PipelineNotificationsEmail 109 | - PipelineApproverEmail 110 | 111 | ParameterLabels: 112 | BranchName: 113 | default: CodeCommit branch 114 | RepositoryName: 115 | default: CodeCommit repository 116 | SASTTool: 117 | default: Select SAST tool 118 | SnykApiKey: 119 | default: If Snyk is selected as SAST tool, provide Snyk API token 120 | DASTTool: 121 | default: Select DAST tool 122 | OwaspZapURLName: 123 | default: OWASP Zap URL name 124 | OwaspZapApiKey: 125 | default: OWASP ZAP API authentication token 126 | ApplicationURLForDASTScan: 127 | default: Application web URL 128 | LambdaPackageLoc: 129 | default: S3 bucket name of lambda code 130 | LambdaPackageS3Key: 131 | default: S3 bucket folder of lambda code 132 | LambdaHandlerName: 133 | default: Lambda function handler name 134 | PipelineNotificationsEmail: 135 | default: Email for pipeline notifications 136 | PipelineApproverEmai: 137 | default: Email for approval notifications 138 | EksClusterName: 139 | default: EKS cluster name 140 | EcrRepositoryName: 141 | default: ECR repository name 142 | EksProdClusterName: 143 | default: PROD EKS cluster name 144 | 145 | ###################### 146 | Conditions: 147 | ScanWith_Anchore: !Equals [ !Ref SASTTool, "Anchore" ] 148 | ScanWith_Snyk: !Equals [ !Ref SASTTool, "Snyk" ] 149 | 150 | Resources: 151 | SSMParameterForSnykApiKey: 152 | Type: 'AWS::SSM::Parameter' 153 | Condition: ScanWith_Snyk 154 | Properties: 155 | Name: !Sub ${AWS::StackName}-Snyk-ApiKey 156 | Type: StringList 157 | Value: !Ref SnykApiKey 158 | 159 | SSMParameterForZapApiKey: 160 | Type: 'AWS::SSM::Parameter' 161 | Properties: 162 | Name: !Sub ${AWS::StackName}-Zap-ApiKey 163 | Type: StringList 164 | Value: !Ref OwaspZapApiKey 165 | 166 | SSMParameterOwaspZapURL: 167 | Type: 'AWS::SSM::Parameter' 168 | Properties: 169 | Name: !Sub ${AWS::StackName}-Owasp-Zap-URL 170 | Type: StringList 171 | Value: !Ref OwaspZapURLName 172 | 173 | SSMParameterAppURL: 174 | Type: 'AWS::SSM::Parameter' 175 | Properties: 176 | Name: !Sub ${AWS::StackName}-Application-URL 177 | Type: StringList 178 | Value: !Ref ApplicationURLForDASTScan 179 | 180 | CloudWatchLogGroup: 181 | Type: AWS::Logs::LogGroup 182 | Properties: 183 | LogGroupName: !Sub ${AWS::StackName}-pipeline-logs 184 | RetentionInDays: 7 185 | #KmsKeyId: !Ref PipelineKMSKey 186 | 187 | PipelineKMSKey: 188 | Type: AWS::KMS::Key 189 | Properties: 190 | Description: KMS key for pipeline 191 | Enabled: true 192 | EnableKeyRotation: true 193 | KeyPolicy: 194 | Version: '2012-10-17' 195 | Statement: 196 | - Effect: Allow 197 | Principal: 198 | AWS: !Sub 'arn:aws-us-gov:iam::${AWS::AccountId}:root' 199 | Action: 200 | - 'kms:Create*' 201 | - 'kms:Describe*' 202 | - 'kms:Enable*' 203 | - 'kms:List*' 204 | - 'kms:Put*' 205 | - 'kms:Update*' 206 | - 'kms:Revoke*' 207 | - 'kms:Disable*' 208 | - 'kms:Get*' 209 | - 'kms:Delete*' 210 | - 'kms:ScheduleKeyDeletion' 211 | - 'kms:CancelKeyDeletion' 212 | Resource: '*' 213 | - Effect: Allow 214 | Principal: 215 | Service: 216 | - codepipeline.amazonaws.com 217 | - !Sub logs.${AWS::Region}.amazonaws.com 218 | - codebuild.amazonaws.com 219 | Action: 220 | - 'kms:Encrypt' 221 | - 'kms:Decrypt' 222 | - 'kms:ReEncrypt*' 223 | - 'kms:GenerateDataKey*' 224 | - 'kms:CreateGrant' 225 | - 'kms:ListGrants' 226 | - 'kms:DescribeKey' 227 | Resource: '*' 228 | - Effect: Allow 229 | Principal: 230 | AWS: 231 | - !GetAtt PipelineServiceRole.Arn 232 | - !GetAtt StaticCodeAnalysisServiceRole.Arn 233 | Action: 234 | - 'kms:Encrypt' 235 | - 'kms:Decrypt' 236 | - 'kms:ReEncrypt*' 237 | - 'kms:GenerateDataKey*' 238 | - 'kms:CreateGrant' 239 | - 'kms:ListGrants' 240 | - 'kms:DescribeKey' 241 | Resource: '*' 242 | Tags: 243 | - Key: pipeline-name 244 | Value: !Sub ${AWS::StackName}-pipeline 245 | 246 | ApprovalTopic: 247 | Type: AWS::SNS::Topic 248 | Properties: 249 | DisplayName: PipelineApproval 250 | Subscription: 251 | - Endpoint: !Ref PipelineApproverEmail 252 | Protocol: "email" 253 | TopicName: !Sub codestar-notifications-approval-${AWS::StackName} 254 | Tags: 255 | - Key: pipeline-name 256 | Value: !Sub ${AWS::StackName}-pipeline 257 | 258 | PipelineTopic: 259 | Type: AWS::SNS::Topic 260 | Properties: 261 | DisplayName: PipelineStageChangeNotification 262 | Subscription: 263 | - Endpoint: !Ref PipelineNotificationsEmail 264 | Protocol: "email" 265 | TopicName: !Sub codestar-notifications-pipelinechange-${AWS::StackName} 266 | Tags: 267 | - Key: pipeline-name 268 | Value: !Sub ${AWS::StackName}-pipeline 269 | 270 | CloudTrailTopic: 271 | Type: AWS::SNS::Topic 272 | Properties: 273 | DisplayName: CloudTrailNotification 274 | Subscription: 275 | - Endpoint: !Ref PipelineNotificationsEmail 276 | Protocol: "email" 277 | TopicName: !Sub ${AWS::StackName}-cloudtrail-notifications-topic 278 | Tags: 279 | - Key: pipeline-name 280 | Value: !Sub ${AWS::StackName}-pipeline 281 | 282 | LambdaFunSecurityHubImport: 283 | Type: 'AWS::Lambda::Function' 284 | Properties: 285 | FunctionName: ImpToSecurityHubEKS 286 | Handler: !Ref LambdaHandlerName 287 | Role: !GetAtt LambdaExecutionRole.Arn 288 | Runtime: python3.8 289 | Code: 290 | S3Bucket: !Ref LambdaPackageLoc 291 | S3Key: !Ref LambdaPackageS3Key 292 | Timeout: 10 293 | Tags: 294 | - Key: pipeline-name 295 | Value: !Sub ${AWS::StackName}-pipeline 296 | 297 | 298 | # creating a bucket for storing artifacts, with server side encryption enabled. 299 | CodePipelineArtifactStoreBucket: 300 | Type: 'AWS::S3::Bucket' 301 | Properties: 302 | BucketName: !Sub ${AWS::StackName}-artifact-bucket 303 | Tags: 304 | - Key: pipeline-name 305 | Value: !Sub ${AWS::StackName}-pipeline 306 | 307 | # S3bucket poilicy is attached to resource(S3 bucket) "CodePipelineArtifactStoreBucket" 308 | # To deny if object server side encryption is not enabled with header 309 | # To deny all actions if transport security (SSL/TLS) is not enabled (i.e, aws:SecureTransport: false) 310 | CodePipelineArtifactStoreBucketPolicy: 311 | Type: 'AWS::S3::BucketPolicy' 312 | Properties: 313 | Bucket: !Ref CodePipelineArtifactStoreBucket 314 | PolicyDocument: 315 | Version: 2012-10-17 316 | Statement: 317 | - Sid: DenyUnEncryptedObjectUploads 318 | Effect: Deny 319 | Principal: '*' 320 | Action: 's3:PutObject' 321 | Resource: 322 | - !Sub arn:aws:s3:::${CodePipelineArtifactStoreBucket}/* 323 | - !Sub arn:aws:s3:::${CodePipelineArtifactStoreBucket} 324 | Condition: 325 | StringNotEquals: 326 | 's3:x-amz-server-side-encryption': 'aws:kms' 327 | - Sid: DenyInsecureConnections 328 | Effect: Deny 329 | Principal: '*' 330 | Action: 's3:*' 331 | Resource: 332 | - !Sub arn:aws:s3:::${CodePipelineArtifactStoreBucket}/* 333 | - !Sub arn:aws:s3:::${CodePipelineArtifactStoreBucket} 334 | Condition: 335 | Bool: 336 | 'aws:SecureTransport': false 337 | 338 | ###Cloud watch event role 339 | AmazonCloudWatchEventRole: 340 | Type: 'AWS::IAM::Role' 341 | Properties: 342 | AssumeRolePolicyDocument: 343 | Version: 2012-10-17 344 | Statement: 345 | - Effect: Allow 346 | Principal: 347 | Service: 348 | - events.amazonaws.com 349 | Action: 'sts:AssumeRole' 350 | Path: / 351 | Policies: 352 | - PolicyName: cwe-pipeline-execution 353 | PolicyDocument: 354 | Version: 2012-10-17 355 | Statement: 356 | - Effect: Allow 357 | Action: 'codepipeline:StartPipelineExecution' 358 | Resource: !Join 359 | - '' 360 | - - 'arn:aws-us-gov:codepipeline:' 361 | - !Ref 'AWS::Region' 362 | - ':' 363 | - !Ref 'AWS::AccountId' 364 | - ':' 365 | - !Ref AppPipeline 366 | 367 | ### Cloudwatch event to trigger the pipeline on commit 368 | AmazonCloudWatchEventRule: 369 | Type: 'AWS::Events::Rule' 370 | Properties: 371 | EventPattern: 372 | source: 373 | - aws.codecommit 374 | detail-type: 375 | - CodeCommit Repository State Change 376 | resources: 377 | - !Join 378 | - '' 379 | - - 'arn:aws-us-gov:codecommit:' 380 | - !Ref 'AWS::Region' 381 | - ':' 382 | - !Ref 'AWS::AccountId' 383 | - ':' 384 | - !Ref RepositoryName 385 | detail: 386 | event: 387 | - referenceCreated 388 | - referenceUpdated 389 | referenceType: 390 | - branch 391 | referenceName: 392 | - master 393 | Targets: 394 | - Arn: !Join 395 | - '' 396 | - - 'arn:aws-us-gov:codepipeline:' 397 | - !Ref 'AWS::Region' 398 | - ':' 399 | - !Ref 'AWS::AccountId' 400 | - ':' 401 | - !Ref AppPipeline 402 | RoleArn: !GetAtt 403 | - AmazonCloudWatchEventRole 404 | - Arn 405 | Id: codepipeline-AppPipeline 406 | 407 | ###Cloudwatch event rule for SNS notifications for pipeline state change 408 | CloudWatchPipelineEventRule: 409 | Type: 'AWS::Events::Rule' 410 | Properties: 411 | EventPattern: 412 | source: 413 | - aws.codepipeline 414 | detail-type: 415 | - CodePipeline Stage Execution State Change 416 | Targets: 417 | - Arn: !Ref PipelineTopic 418 | Id: "PipelineNotifications" 419 | 420 | ###Cloudtrail event notifications for pipeline updates, deletes, and codebuild project creation, deletion, etc. 421 | TrailBucket: 422 | Type: AWS::S3::Bucket 423 | Properties: 424 | Tags: 425 | - Key: pipeline-name 426 | Value: !Sub ${AWS::StackName}-pipeline 427 | 428 | TrailBucketPolicy: 429 | Type: 'AWS::S3::BucketPolicy' 430 | Properties: 431 | Bucket: !Ref TrailBucket 432 | PolicyDocument: 433 | Version: '2012-10-17' 434 | Statement: 435 | - Sid: AWSCloudTrailAclCheck 436 | Effect: Allow 437 | Principal: 438 | Service: 'cloudtrail.amazonaws.com' 439 | Action: 's3:GetBucketAcl' 440 | Resource: !Sub 'arn:aws-us-gov:s3:::${TrailBucket}' 441 | - Sid: AWSCloudTrailWrite 442 | Effect: Allow 443 | Principal: 444 | Service: 'cloudtrail.amazonaws.com' 445 | Action: 's3:PutObject' 446 | Resource: !Sub 'arn:aws-us-gov:s3:::${TrailBucket}/AWSLogs/${AWS::AccountId}/*' 447 | Condition: 448 | StringEquals: 449 | 's3:x-amz-acl': 'bucket-owner-full-control' 450 | - Sid: AllowSSLRequestsOnly # AWS Foundational Security Best Practices v1.0.0 S3.5 451 | Effect: Deny 452 | Principal: '*' 453 | Action: 's3:*' 454 | Resource: !Join 455 | - '' 456 | - - !GetAtt 457 | - TrailBucket 458 | - Arn 459 | - /* 460 | Condition: 461 | Bool: 462 | 'aws:SecureTransport': false 463 | 464 | Trail: 465 | DependsOn: 466 | - TrailBucketPolicy 467 | Type: AWS::CloudTrail::Trail 468 | Properties: 469 | S3BucketName: 470 | Ref: TrailBucket 471 | IncludeGlobalServiceEvents: true 472 | IsLogging: true 473 | IsMultiRegionTrail: true 474 | EnableLogFileValidation: true 475 | CloudWatchLogsLogGroupArn: !GetAtt 'TrailLogGroup.Arn' 476 | CloudWatchLogsRoleArn: !GetAtt 'TrailLogGroupRole.Arn' 477 | 478 | TrailLogGroup: 479 | Type: 'AWS::Logs::LogGroup' 480 | Properties: 481 | RetentionInDays: 90 482 | 483 | TrailLogGroupRole: 484 | Type: 'AWS::IAM::Role' 485 | Properties: 486 | AssumeRolePolicyDocument: 487 | Version: '2012-10-17' 488 | Statement: 489 | - Sid: AssumeRole1 490 | Effect: Allow 491 | Principal: 492 | Service: 'cloudtrail.amazonaws.com' 493 | Action: 'sts:AssumeRole' 494 | Policies: 495 | - PolicyName: 'cloudtrail-policy' 496 | PolicyDocument: 497 | Version: '2012-10-17' 498 | Statement: 499 | - Effect: Allow 500 | Action: 501 | - 'logs:CreateLogStream' 502 | - 'logs:PutLogEvents' 503 | Resource: !GetAtt 'TrailLogGroup.Arn' 504 | 505 | PipelineStateChangeMetricFiletr: 506 | Type: 'AWS::Logs::MetricFilter' 507 | Properties: 508 | FilterPattern: '{ ($.eventName = "StartPipelineExecution") || ($.eventName = "StopPipelineExecution") || ($.eventName = "UpdatePipeline") || ($.eventName = "DeletePipeline") }' 509 | LogGroupName: !Ref TrailLogGroup 510 | MetricTransformations: 511 | - MetricName: "pipelineEvent" 512 | MetricNamespace: "CloudTrailMetrics" 513 | MetricValue: "1" 514 | 515 | PipelineStateChangeAlarm: 516 | Type: AWS::CloudWatch::Alarm 517 | Properties: 518 | AlarmName: !Sub ${AWS::StackName}-CloudTrailPipelineEventChange 519 | AlarmDescription: "Alarm when cloudtrail receives an state change event from codepipeline" 520 | MetricName: "pipelineEvent" 521 | AlarmActions: 522 | - !Ref CloudTrailTopic 523 | ComparisonOperator: "GreaterThanThreshold" 524 | EvaluationPeriods: 1 525 | Threshold: 0 526 | Namespace: "CloudTrailMetrics" 527 | Statistic: "Sum" 528 | Period: 1800 529 | 530 | CodeBuildChangeMetricFilter: 531 | Type: 'AWS::Logs::MetricFilter' 532 | Properties: 533 | FilterPattern: '{ (($.eventSource = "codebuild.amazonaws.com") && (($.eventName = "CreateProject") || ($.eventName = "DeleteProject"))) }' 534 | LogGroupName: !Ref TrailLogGroup 535 | MetricTransformations: 536 | - MetricName: "codebuildEvent" 537 | MetricNamespace: "CloudTrailMetrics" 538 | MetricValue: "1" 539 | 540 | CodeBuildStateChangeAlarm: 541 | Type: AWS::CloudWatch::Alarm 542 | Properties: 543 | AlarmName: !Sub ${AWS::StackName}-CloudTrailCodebuildEventChange 544 | AlarmDescription: "Alarm when cloudtrail receives an state change event from codebuild" 545 | MetricName: "codebuildEvent" 546 | AlarmActions: 547 | - !Ref CloudTrailTopic 548 | ComparisonOperator: "GreaterThanThreshold" 549 | EvaluationPeriods: 1 550 | Threshold: 0 551 | Namespace: "CloudTrailMetrics" 552 | Statistic: "Sum" 553 | Period: 1800 554 | 555 | ### AWS Config rules 556 | AWSConfigRule1: 557 | Type: 'AWS::Config::ConfigRule' 558 | Properties: 559 | ConfigRuleName: !Sub ${AWS::StackName}-codebuild-project-envvar-awscred-check 560 | Description: >- 561 | Checks whether the project contains environment variables 562 | AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. The rule is NON_COMPLIANT 563 | when the project environment variables contains plaintext credentials. 564 | InputParameters: {} 565 | Scope: 566 | ComplianceResourceTypes: 567 | - 'AWS::CodeBuild::Project' 568 | Source: 569 | Owner: AWS 570 | SourceIdentifier: CODEBUILD_PROJECT_ENVVAR_AWSCRED_CHECK 571 | 572 | AWSConfigRule2: 573 | Type: 'AWS::Config::ConfigRule' 574 | Properties: 575 | ConfigRuleName: !Sub ${AWS::StackName}-codebuild-project-source-repo-url-check 576 | Description: >- 577 | Checks whether the GitHub or Bitbucket source repository URL contains 578 | either personal access tokens or user name and password. The rule is 579 | complaint with the usage of OAuth to grant authorization for accessing 580 | GitHub or Bitbucket repositories. 581 | InputParameters: {} 582 | Scope: 583 | ComplianceResourceTypes: 584 | - 'AWS::CodeBuild::Project' 585 | Source: 586 | Owner: AWS 587 | SourceIdentifier: CODEBUILD_PROJECT_SOURCE_REPO_URL_CHECK 588 | 589 | AWSConfigRule3: 590 | Type: 'AWS::Config::ConfigRule' 591 | Properties: 592 | ConfigRuleName: !Sub ${AWS::StackName}-cloud-trail-log-file-validation-enabled 593 | Description: >- 594 | Checks whether AWS CloudTrail creates a signed digest file with logs. 595 | AWS recommends that the file validation must be enabled on all trails. 596 | The rule is noncompliant if the validation is not enabled. 597 | InputParameters: {} 598 | Scope: {} 599 | Source: 600 | Owner: AWS 601 | SourceIdentifier: CLOUD_TRAIL_LOG_FILE_VALIDATION_ENABLED 602 | 603 | #### Codepipeline creation 604 | AppPipeline: 605 | Type: 'AWS::CodePipeline::Pipeline' 606 | Properties: 607 | Name: !Sub ${AWS::StackName}-pipeline 608 | RoleArn: !GetAtt 609 | - PipelineServiceRole 610 | - Arn 611 | Stages: 612 | - Name: Source 613 | Actions: 614 | - Name: SourceAction 615 | ActionTypeId: 616 | Category: Source 617 | Owner: AWS 618 | Version: '1' 619 | Provider: CodeCommit 620 | OutputArtifacts: 621 | - Name: SourceOutput 622 | Configuration: 623 | BranchName: !Ref BranchName 624 | RepositoryName: !Ref RepositoryName 625 | PollForSourceChanges: false 626 | RunOrder: 1 627 | ### Actual blog Pipeline stages 628 | - Name: Build-Secrets 629 | Actions: 630 | - Name: Secret-Analysis 631 | InputArtifacts: 632 | - Name: SourceOutput 633 | ActionTypeId: 634 | Category: Build 635 | Owner: AWS 636 | Version: '1' 637 | Provider: CodeBuild 638 | OutputArtifacts: 639 | - Name: SecArtifacts 640 | Configuration: 641 | ProjectName: !Ref SecBuildProject 642 | RunOrder: 2 643 | 644 | - Name: Build-SAST 645 | Actions: 646 | - Name: SAST-Analysis 647 | InputArtifacts: 648 | - Name: SourceOutput 649 | ActionTypeId: 650 | Category: Build 651 | Owner: AWS 652 | Version: '1' 653 | Provider: CodeBuild 654 | OutputArtifacts: 655 | - Name: SASTArtifacts 656 | Configuration: 657 | ProjectName: !Ref SASTBuildProject 658 | RunOrder: 3 659 | - Name: ECR-SAST-and-STG-Deploy 660 | InputArtifacts: 661 | - Name: SourceOutput 662 | ActionTypeId: 663 | Category: Build 664 | Owner: AWS 665 | Version: '1' 666 | Provider: CodeBuild 667 | OutputArtifacts: 668 | - Name: ECRSASTArtifacts 669 | Configuration: 670 | ProjectName: !Ref ECRSASTBuildProject 671 | RunOrder: 4 672 | 673 | # ###### demo stages #### 674 | # - Name: Build-SecretsScanning-and-SAST 675 | # Actions: 676 | # - Name: SecretAnalysis 677 | # InputArtifacts: 678 | # - Name: SourceOutput 679 | # ActionTypeId: 680 | # Category: Build 681 | # Owner: AWS 682 | # Version: '1' 683 | # Provider: CodeBuild 684 | # OutputArtifacts: 685 | # - Name: SecArtifacts 686 | # Configuration: 687 | # ProjectName: !Ref SecBuildProject 688 | # RunOrder: 2 689 | # - Name: SASTAnalysis 690 | # InputArtifacts: 691 | # - Name: SourceOutput 692 | # ActionTypeId: 693 | # Category: Build 694 | # Owner: AWS 695 | # Version: '1' 696 | # Provider: CodeBuild 697 | # OutputArtifacts: 698 | # - Name: SASTArtifacts 699 | # Configuration: 700 | # ProjectName: !Ref SASTBuildProject 701 | # RunOrder: 2 702 | 703 | # - Name: Build-SAST-and-Deploy-STG 704 | # Actions: 705 | # - Name: ECRSASTAnalysis 706 | # InputArtifacts: 707 | # - Name: SourceOutput 708 | # ActionTypeId: 709 | # Category: Build 710 | # Owner: AWS 711 | # Version: '1' 712 | # Provider: CodeBuild 713 | # OutputArtifacts: 714 | # - Name: ECRSASTArtifacts 715 | # Configuration: 716 | # ProjectName: !Ref ECRSASTBuildProject 717 | # RunOrder: 4 718 | 719 | ### Build stage for DAST analysis with OWASP Zap 720 | - Name: Build-DAST 721 | Actions: 722 | - Name: DASTAnalysis 723 | InputArtifacts: 724 | - Name: SourceOutput 725 | ActionTypeId: 726 | Category: Test 727 | Owner: AWS 728 | Version: '1' 729 | Provider: CodeBuild 730 | Configuration: 731 | ProjectName: !Ref DASTBuildProject 732 | RunOrder: 5 733 | 734 | ### Manual approval change 735 | - Name: Manual-Approval 736 | Actions: 737 | - Name: ApprovalRequired2 738 | ActionTypeId: 739 | Category: Approval 740 | Owner: AWS 741 | Version: '1' 742 | Provider: Manual 743 | Configuration: 744 | CustomData: There are no critical security vulnerabilities. Your approval is needed to deploy. 745 | ExternalEntityLink: !Sub https://console.amazonaws.com/codesuite/codepipeline/pipelines/${AWS::StackName}/general?region=${AWS::Region} 746 | NotificationArn: !Ref ApprovalTopic 747 | RunOrder: 6 748 | 749 | ### Deploy to prod EKS 750 | - Name: Deploy-PRD 751 | Actions: 752 | - Name: EKSDeploy 753 | InputArtifacts: 754 | - Name: SourceOutput 755 | ActionTypeId: 756 | Category: Build 757 | Owner: AWS 758 | Version: '1' 759 | Provider: CodeBuild 760 | Configuration: 761 | ProjectName: !Ref DeployBuildProject 762 | RunOrder: 7 763 | 764 | ArtifactStore: 765 | Type: S3 766 | Location: !Ref CodePipelineArtifactStoreBucket 767 | EncryptionKey: 768 | Id: !GetAtt PipelineKMSKey.Arn 769 | Type: KMS 770 | Tags: 771 | - Key: pipeline-name 772 | Value: !Sub ${AWS::StackName}-pipeline 773 | 774 | #### SAST amalysis codebuild project 775 | SASTBuildProject: 776 | Type: AWS::CodeBuild::Project 777 | Properties: 778 | Description: Static Code Analysis Build Project 779 | Artifacts: 780 | Type: CODEPIPELINE 781 | EncryptionKey: !GetAtt PipelineKMSKey.Arn 782 | Environment: 783 | ComputeType: BUILD_GENERAL1_SMALL 784 | Image: aws/codebuild/standard:4.0 785 | Type: LINUX_CONTAINER 786 | EnvironmentVariables: 787 | - Name: IMAGE_REPO_NAME 788 | Value: !Ref EcrRepositoryName 789 | - Name: REPOSITORY_URI 790 | Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryName} 791 | - Name: EKS_CLUSTER_NAME 792 | Value: !Ref EksClusterName 793 | - Name: EKS_KUBECTL_ROLE_ARN 794 | Value: !GetAtt StaticCodeAnalysisServiceRole.Arn 795 | - !If 796 | - ScanWith_Snyk 797 | - Name: SnykApiKey 798 | Type: PARAMETER_STORE 799 | Value: !Ref SSMParameterForSnykApiKey 800 | - !Ref "AWS::NoValue" 801 | PrivilegedMode: true 802 | ServiceRole: !Ref 'StaticCodeAnalysisServiceRole' 803 | Source: 804 | Type: CODEPIPELINE 805 | BuildSpec: 806 | Fn::If: 807 | - ScanWith_Anchore 808 | - buildspec-anchore.yml 809 | - buildspec-snyk.yml 810 | LogsConfig: 811 | CloudWatchLogs: 812 | GroupName: !Ref CloudWatchLogGroup 813 | Status: ENABLED 814 | StreamName: SASTAnalysis 815 | QueuedTimeoutInMinutes: 10 816 | Tags: 817 | - Key: pipeline-name 818 | Value: !Sub ${AWS::StackName}-pipeline 819 | 820 | #### ECR SAST amalysis codebuild project 821 | ECRSASTBuildProject: 822 | Type: AWS::CodeBuild::Project 823 | Properties: 824 | Description: ECR Static Code Analysis Build Project 825 | Artifacts: 826 | Type: CODEPIPELINE 827 | EncryptionKey: !GetAtt PipelineKMSKey.Arn 828 | Environment: 829 | ComputeType: BUILD_GENERAL1_SMALL 830 | Image: aws/codebuild/standard:4.0 831 | Type: LINUX_CONTAINER 832 | EnvironmentVariables: 833 | - Name: IMAGE_REPO_NAME 834 | Value: !Ref EcrRepositoryName 835 | - Name: REPOSITORY_URI 836 | Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryName} 837 | - Name: EKS_CLUSTER_NAME 838 | Value: !Ref EksClusterName 839 | - Name: EKS_KUBECTL_ROLE_ARN 840 | Value: !GetAtt StaticCodeAnalysisServiceRole.Arn 841 | PrivilegedMode: true 842 | ServiceRole: !Ref 'StaticCodeAnalysisServiceRole' 843 | Source: 844 | Type: CODEPIPELINE 845 | BuildSpec: buildspec-ecr.yml 846 | LogsConfig: 847 | CloudWatchLogs: 848 | GroupName: !Ref CloudWatchLogGroup 849 | Status: ENABLED 850 | StreamName: ECRSASTAnalysis 851 | QueuedTimeoutInMinutes: 10 852 | Tags: 853 | - Key: pipeline-name 854 | Value: !Sub ${AWS::StackName}-pipeline 855 | 856 | 857 | ####Secrets Analysis BuildProject 858 | SecBuildProject: 859 | Type: AWS::CodeBuild::Project 860 | Properties: 861 | Description: Secrets Analysis Build Project 862 | Artifacts: 863 | Type: CODEPIPELINE 864 | EncryptionKey: !GetAtt PipelineKMSKey.Arn 865 | Environment: 866 | ComputeType: BUILD_GENERAL1_SMALL 867 | Image: aws/codebuild/standard:4.0 868 | Type: LINUX_CONTAINER 869 | EnvironmentVariables: 870 | - Name: CODECOMMIT_REPO_NAME 871 | Value: !Ref RepositoryName 872 | - Name: REPOSITORY_URI 873 | Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryName} 874 | PrivilegedMode: true 875 | ServiceRole: !Ref 'StaticCodeAnalysisServiceRole' 876 | Source: 877 | Type: CODEPIPELINE 878 | BuildSpec: buildspec-gitsecrets.yml 879 | LogsConfig: 880 | CloudWatchLogs: 881 | GroupName: !Ref CloudWatchLogGroup 882 | Status: ENABLED 883 | StreamName: SecretAnalysis 884 | QueuedTimeoutInMinutes: 10 885 | Tags: 886 | - Key: pipeline-name 887 | Value: !Sub ${AWS::StackName}-pipeline 888 | 889 | #### DAST analysis codebuild project 890 | DASTBuildProject: 891 | Type: AWS::CodeBuild::Project 892 | Properties: 893 | Description: Dynamic Code Analysis Build Project 894 | Artifacts: 895 | Type: CODEPIPELINE 896 | EncryptionKey: !GetAtt PipelineKMSKey.Arn 897 | Environment: 898 | ComputeType: BUILD_GENERAL1_SMALL 899 | Image: aws/codebuild/standard:4.0 900 | Type: LINUX_CONTAINER 901 | PrivilegedMode: true 902 | EnvironmentVariables: ## adding environment variable from SSM parameter 903 | - Name: OwaspZapApiKey 904 | Type: PARAMETER_STORE 905 | Value: !Ref SSMParameterForZapApiKey 906 | - Name: OwaspZapURL 907 | Type: PARAMETER_STORE 908 | Value: !Ref SSMParameterOwaspZapURL 909 | - Name: ApplicationURL 910 | Type: PARAMETER_STORE 911 | Value: !Ref SSMParameterAppURL 912 | ServiceRole: !Ref 'StaticCodeAnalysisServiceRole' 913 | Source: 914 | Type: CODEPIPELINE 915 | BuildSpec: buildspec-owasp-zap.yml 916 | LogsConfig: 917 | CloudWatchLogs: 918 | GroupName: !Ref CloudWatchLogGroup 919 | Status: ENABLED 920 | StreamName: DASTAnalysis 921 | QueuedTimeoutInMinutes: 10 922 | Tags: 923 | - Key: pipeline-name 924 | Value: !Sub ${AWS::StackName}-pipeline 925 | 926 | ### EKS Deploy BuildProject 927 | DeployBuildProject: 928 | Type: AWS::CodeBuild::Project 929 | Properties: 930 | Description: EKS Prod Deploy Build Project 931 | Artifacts: 932 | Type: CODEPIPELINE 933 | EncryptionKey: !GetAtt PipelineKMSKey.Arn 934 | Environment: 935 | ComputeType: BUILD_GENERAL1_SMALL 936 | Image: aws/codebuild/standard:4.0 937 | Type: LINUX_CONTAINER 938 | PrivilegedMode: true 939 | EnvironmentVariables: 940 | - Name: IMAGE_REPO_NAME 941 | Value: !Ref EcrRepositoryName 942 | - Name: REPOSITORY_URI 943 | Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryName} 944 | - Name: EKS_PROD_CLUSTER_NAME 945 | Value: !Ref EksProdClusterName 946 | - Name: EKS_KUBECTL_ROLE_ARN 947 | Value: !GetAtt StaticCodeAnalysisServiceRole.Arn 948 | ServiceRole: !Ref 'StaticCodeAnalysisServiceRole' 949 | Source: 950 | Type: CODEPIPELINE 951 | BuildSpec: buildspec-prod.yml 952 | LogsConfig: 953 | CloudWatchLogs: 954 | GroupName: !Ref CloudWatchLogGroup 955 | Status: ENABLED 956 | StreamName: ProdDeploy 957 | QueuedTimeoutInMinutes: 10 958 | Tags: 959 | - Key: pipeline-name 960 | Value: !Sub ${AWS::StackName}-pipeline 961 | 962 | ###StaticCode Analysis ServiceRole 963 | StaticCodeAnalysisServiceRole: 964 | Type: AWS::IAM::Role 965 | Properties: 966 | AssumeRolePolicyDocument: 967 | Version: '2012-10-17' 968 | Statement: 969 | - Effect: Allow 970 | Action: sts:AssumeRole 971 | Principal: 972 | Service: 973 | - codebuild.amazonaws.com 974 | Policies: 975 | - PolicyName: SecurityCodeAnalysisPolicy 976 | PolicyDocument: 977 | Version: '2012-10-17' 978 | Statement: 979 | - Effect: Allow 980 | Action: iam:PassRole 981 | Resource: '*' 982 | - Effect: Allow 983 | Action: 984 | - iam:PassRole 985 | - logs:* 986 | - s3:* 987 | - cloudformation:* 988 | - cloudwatch:* 989 | - cloudtrail:* 990 | - codebuild:* 991 | - codecommit:* 992 | - codepipeline:* 993 | - ssm:* 994 | - lambda:* 995 | - kms:* 996 | - ecr:* 997 | - eks:DescribeCluster 998 | Resource: '*' 999 | Path: / 1000 | RoleName: !Join 1001 | - '-' 1002 | - - !Ref 'AWS::StackName' 1003 | - SecurityCodeAnalysisRole 1004 | 1005 | #### Lambda Function Execution Role 1006 | LambdaExecutionRole: 1007 | Type: AWS::IAM::Role 1008 | Properties: 1009 | RoleName: !Sub ${AWS::StackName}-LambdaExecutionRole 1010 | AssumeRolePolicyDocument: 1011 | Version: '2012-10-17' 1012 | Statement: 1013 | - Effect: Allow 1014 | Principal: 1015 | Service: 1016 | - lambda.amazonaws.com 1017 | Action: 1018 | - sts:AssumeRole 1019 | Path: "/" 1020 | Policies: 1021 | - PolicyName: lambda-execution-policy 1022 | PolicyDocument: 1023 | Version: '2012-10-17' 1024 | Statement: 1025 | - Effect: Allow 1026 | Action: 1027 | - logs:* 1028 | - S3:* 1029 | - securityhub:* 1030 | Resource: '*' 1031 | 1032 | ###Pipeline Service Role 1033 | PipelineServiceRole: 1034 | Type: 'AWS::IAM::Role' 1035 | Properties: 1036 | AssumeRolePolicyDocument: 1037 | Version: 2012-10-17 1038 | Statement: 1039 | - Effect: Allow 1040 | Principal: 1041 | Service: 1042 | - codepipeline.amazonaws.com 1043 | Action: 'sts:AssumeRole' 1044 | Path: / 1045 | Policies: 1046 | - PolicyName: !Sub ${AWS::StackName}-CodePipeline-Servicepolicy 1047 | PolicyDocument: 1048 | Version: 2012-10-17 1049 | Statement: 1050 | - Effect: Allow 1051 | Action: 1052 | - 'codecommit:CancelUploadArchive' 1053 | - 'codecommit:GetBranch' 1054 | - 'codecommit:GetCommit' 1055 | - 'codecommit:GetUploadArchiveStatus' 1056 | - 'codecommit:UploadArchive' 1057 | Resource: '*' 1058 | - Effect: Allow 1059 | Action: 1060 | - 'codedeploy:CreateDeployment' 1061 | - 'codedeploy:GetApplicationRevision' 1062 | - 'codedeploy:GetDeployment' 1063 | - 'codedeploy:GetDeploymentConfig' 1064 | - 'codedeploy:RegisterApplicationRevision' 1065 | Resource: '*' 1066 | - Effect: Allow 1067 | Action: 1068 | - 'codebuild:BatchGetBuilds' 1069 | - 'codebuild:StartBuild' 1070 | Resource: '*' 1071 | - Effect: Allow 1072 | Action: 1073 | - 'devicefarm:ListProjects' 1074 | - 'devicefarm:ListDevicePools' 1075 | - 'devicefarm:GetRun' 1076 | - 'devicefarm:GetUpload' 1077 | - 'devicefarm:CreateUpload' 1078 | - 'devicefarm:ScheduleRun' 1079 | Resource: '*' 1080 | - Effect: Allow 1081 | Action: 1082 | - 'lambda:InvokeFunction' 1083 | - 'lambda:ListFunctions' 1084 | - 'lambda:CreateFunction' 1085 | - 'lambda:UpdateFunctionConfiguration' 1086 | - 'lambda:UpdateFunctionCode' 1087 | - 'lambda:TagResource' 1088 | - 'lambda:PublishVersion' 1089 | - 'lambda:GetFunctionConfiguration' 1090 | - 'lambda:GetFunction' 1091 | Resource: '*' 1092 | - Effect: Allow 1093 | Action: 1094 | - 'iam:PassRole' 1095 | Resource: '*' 1096 | - Effect: Allow 1097 | Action: 1098 | - 'elasticbeanstalk:*' 1099 | - 'ec2:*' 1100 | - 'elasticloadbalancing:*' 1101 | - 'autoscaling:*' 1102 | - 'cloudwatch:*' 1103 | - 's3:*' 1104 | - 'sns:*' 1105 | - 'cloudformation:*' 1106 | - 'rds:*' 1107 | - 'sqs:*' 1108 | - 'ecs:*' 1109 | - 'logs:*' 1110 | - 'kms:*' 1111 | - 'ecr:*' 1112 | Resource: '*' 1113 | 1114 | ###Outputs 1115 | Outputs: 1116 | ArtifactBucketName: 1117 | Description: The s3 bucket name of the artifact repository with GetAtt function 1118 | Value: !GetAtt CodePipelineArtifactStoreBucket.Arn 1119 | 1120 | ArtifactBucketNameRef: 1121 | Description: S3 bucketname with Ref function 1122 | Value: !Ref CodePipelineArtifactStoreBucket 1123 | 1124 | LambdaFunctionArn: 1125 | Description: LambdaFunction Arn value 1126 | Value: !GetAtt LambdaFunSecurityHubImport.Arn 1127 | 1128 | CloudWatchLogGroupName: 1129 | Description: Cloudwatch Log group name 1130 | Value: !Ref CloudWatchLogGroup 1131 | 1132 | PipelineKeyArn: 1133 | Description: KMS Key ARN for the pipeline 1134 | Value: !GetAtt PipelineKMSKey.Arn 1135 | 1136 | SASTBuildProjectRoleArn: 1137 | Description: servicerole for SAST build project 1138 | Value: !GetAtt StaticCodeAnalysisServiceRole.Arn 1139 | 1140 | 1141 | --------------------------------------------------------------------------------