├── .eslintignore ├── .eslintrc ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Config ├── LICENSE ├── LicenseHeader.txt ├── README.md ├── bin └── audit-manager-blog.ts ├── cdk.json ├── deploy.sh ├── examples ├── controls │ ├── datasecurity-encryption-at-rest.yaml │ └── datasecurity-encryption-in-transit.yaml └── frameworks │ └── enterprise-framework.yaml ├── images └── solution-overview.png ├── jest.config.js ├── lambda ├── controls-library │ └── .DS_Store ├── package-lock.json ├── package.json ├── src │ ├── controls.ts │ ├── data-file.ts │ ├── frameworks.ts │ ├── index.ts │ ├── tags.ts │ └── types.ts └── webpack.config.js ├── lib ├── audit-manager-actions.ts └── audit-manager-blog-stack.ts ├── package-lock.json ├── package.json ├── test ├── .DS_Store ├── __snapshots__ │ └── audit-manager-blog.test.ts.snap └── audit-manager-blog.test.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | build/ 4 | **/.vscode/* 5 | reports/ 6 | coverage/ 7 | .aws-sam/ 8 | output/ 9 | .DS_Store 10 | cdk.out 11 | 12 | *.d.ts 13 | 14 | # CDK asset staging directory 15 | .cdk.staging 16 | cdk.out 17 | 18 | cdk.context.json 19 | Blog-Code-awsSecurityBlog-948-.zip 20 | **/.scannerwork/* -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Config: -------------------------------------------------------------------------------- 1 | package.Audit-Manager-Blog = { 2 | interfaces = (1.0); 3 | 4 | # Use NoOpBuild. See https://w.amazon.com/index.php/BrazilBuildSystem/NoOpBuild 5 | build-system = no-op; 6 | build-tools = { 7 | 1.0 = { 8 | NoOpBuild = 1.0; 9 | }; 10 | }; 11 | 12 | # Use runtime-dependencies for when you want to bring in additional 13 | # packages when deploying. 14 | # Use dependencies instead if you intend for these dependencies to 15 | # be exported to other packages that build against you. 16 | dependencies = { 17 | 1.0 = { 18 | }; 19 | }; 20 | 21 | runtime-dependencies = { 22 | 1.0 = { 23 | }; 24 | }; 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LicenseHeader.txt: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enterprise Custom Controls using Audit Manager 2 | 3 | ## Prerequisite 4 | 5 | Please make sure the following software packages are installed in your environment before the deployment: 6 | 7 | - Node.js v12 or above (https://nodejs.org) 8 | - AWS CLI version 2 (https://docs.aws.amazon.com/cli/latest/userguide/welcome-versions.html) 9 | - jq (https://stedolan.github.io/jq/) 10 | - git (https://git-scm.com/) 11 | 12 | ## Solution Overview 13 | 14 | Companies go through audit and security assessments regularly by their customers, 3rd party audit firms or internal teams. This is required to identify the cybersecurity concerns and to mitigate the risks to protect your company and customers. Often the process of audit and liaising with the industry standards are mostly manual and time consuming. The key challenge is to keep up to date on the standards with continuous capability to build and review the controls to be audit ready. 15 | 16 | In this solution, we will walk through the steps required to automatically create and maintain a custom enterprise controls catalog and assessment framework using AWS Audit Manager. AWS Audit Manager helps you continuously audit your AWS usage to simplify how you assess risk and compliance with regulations and industry standards. The proposed solution enables the Controls Owner team to help design, build IT controls that can be easily customised and audited. 17 | 18 | The proposed solution utilised the event driven architecture that enables agility and reduce manual administration effort. 19 | • AWS Audit Manager - AWS Audit Manager helps you continuously audit your AWS usage to simplify how you assess risk and compliance with regulations and industry standards. 20 | • AWS Lambda - AWS Lambda is a serverless compute service that lets you run code without provisioning or managing servers in response to events such as changes in data, application state or user actions. 21 | • Amazon S3 - Amazon S3 is object storage built to store and retrieve any amount of data from anywhere that offers industry leading durability, availability, performance, security, and virtually unlimited scalability at very low costs. 22 | • AWS CDK – AWS Cloud Development Kit is a software development framework for provisioning your cloud infrastructure in code through AWS CloudFormation. 23 | 24 | ## Architecture: 25 | 26 | The proposed solution enables automated controls management using event driven architecture with AWS Services like AWS Audit Manager, AWS lambda and Amazon S3 in integration with code management service like GitHub. The Controls owner can design, manage, monitor and rollout the custom controls in GitHub with a simple custom controls configuration file. Once the controls configuration file is checked in, the on-commit event of the file triggers controls pipeline to load controls in audit manager using Lambda function. 27 | 28 | ### Step 1 : Control owner load the controls as code (Controls and Framework) to S3 bucket 29 | 30 | ### Step 2 : Controls yaml file uploaded into S3 triggers lambda function to process the control file 31 | 32 | ### Step 3: Lambda function process the control file and creates or updates the existing control into Audit Manager. 33 | 34 | ### Step 4: Controls Framework yaml uploaded into S3 triggers lambda function to process the controls framework file 35 | 36 | ### Step 5: Lambda function validates the control framework file and updates the controls frameowrk library in Audit Manager 37 | 38 | ![Enterprise Controls using Audit Manager](/images/solution-overview.png?raw=true 'Enterprise Controls using Audit Manager') 39 | 40 | ## Deployment 41 | 42 | Before deployment, please make sure that the correct AWS credentials are configured in your terminal session. It can be either in the environment variables or in `~/.aws`. More details, please refer to [Configuring the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). 43 | 44 | Kindly ensure following pre-requisite are configured before running ./deploy.sh 45 | 46 | - AWS Audit Manager is enabled in the region deployed 47 | - Set your AWS Credentials to deploy the stack 48 | - - export AWS_ACCESS_KEY_ID=<> 49 | - - export AWS_SECRET_ACCESS_KEY=<> 50 | - - export AWS_REGION = <> 51 | 52 | When ready, clone this repo, then use the following command 53 | 54 | ``` 55 | cd enterprise-controls-catalog-via-aws-audit-manager 56 | 57 | ./deploy.sh 58 | 59 | ``` 60 | 61 | ## Contributors 62 | 63 | Deenadayaalan Thirugnanasambandam, Principal SA SDE, AWS 64 | 65 | Hu Jin - SA SDE, AWS 66 | 67 | Vinodh Shankar - Senior SA, AWS 68 | 69 | Jasper Wang - Cloud Architect, AWS Professional Services 70 | 71 | ## Security 72 | 73 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 74 | 75 | ## License 76 | 77 | This library is licensed under the MIT-0 License. See the LICENSE file. 78 | -------------------------------------------------------------------------------- /bin/audit-manager-blog.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | import 'source-map-support/register'; 19 | import { App } from 'aws-cdk-lib'; 20 | 21 | import { AuditManagerBlogStack } from '../lib/audit-manager-blog-stack'; 22 | 23 | const app = new App(); 24 | new AuditManagerBlogStack(app, 'AuditManagerBlogStack'); 25 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/audit-manager-blog.ts", 3 | "context": {} 4 | } 5 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo Preparing 3 | cd lambda && npm ci && cd .. 4 | npm ci 5 | 6 | echo Building 7 | npm run all 8 | 9 | echo Deploying 10 | npx cdk deploy --require-approval=never 11 | 12 | BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name AuditManagerBlogStack --output json | jq -r '.Stacks[].Outputs[] | select ( .OutputKey | contains("bucket") ) | .OutputValue') 13 | echo Upload Example Data Security Controls to S3 bucket $BUCKET_NAME/controls 14 | 15 | # upload the controls 16 | aws s3 cp ./examples/controls/datasecurity-encryption-at-rest.yaml s3://$BUCKET_NAME/controls/ 17 | aws s3 cp ./examples/controls/datasecurity-encryption-in-transit.yaml s3://$BUCKET_NAME/controls/ 18 | 19 | # wait for controls to be created 20 | echo Waiting for Example Data Security Controls to be Created 21 | NUM_CONTROLS=0 22 | while [ $NUM_CONTROLS -ne 2 ]; do 23 | NUM_CONTROLS=$(aws auditmanager list-controls --control-type Custom --output json | jq -r '.controlMetadataList[] | select ( .name | test("^DataSecurity")) | .name' | wc -l) 24 | sleep 2 25 | done 26 | echo Example Data Security Controls are Created 27 | 28 | # upload the frameworks 29 | echo Upload Example Enterprise Framework to S3 bucket $BUCKET_NAME/frameworks 30 | aws s3 cp ./examples/frameworks/enterprise-framework.yaml s3://$BUCKET_NAME/frameworks/ 31 | 32 | # wait for controls to be created 33 | echo Waiting for Example NIST Enterprise Framework to be Created 34 | NUM_FRAMEWORKS=0 35 | while [ $NUM_FRAMEWORKS -ne 1 ]; do 36 | NUM_FRAMEWORKS=$(aws auditmanager list-assessment-frameworks --framework-type Custom --output json | jq -r '.frameworkMetadataList[] | select ( .name | test("^NIST Enterprise")) | .name' | wc -l) 37 | sleep 2 38 | done 39 | echo Example NIST Enterprise Frameworks is Created. 40 | 41 | echo Deployment Completed 42 | -------------------------------------------------------------------------------- /examples/controls/datasecurity-encryption-at-rest.yaml: -------------------------------------------------------------------------------- 1 | name: DataSecurity-DataAtRest 2 | description: Information and records (data) are managed consistent with the organization’s risk strategy to protect the confidentiality, integrity, and availability of information. 3 | actionPlanTitle: All Resources needs to be encrypted in Rest 4 | actionPlanInstructions: Ensure all resources have attestations for Encryption in Rest enabled 5 | testingInformation: Test attestations - preventive and detective controls for encryption in Rest 6 | tags: 7 | ID: PRDS-2 8 | Subcategory: Data-At-Rest 9 | Category: Data Security-PRDS 10 | CIS: CIS17 11 | COBIT: COBIT 5 APO07-03 12 | NIST: NIST SP 800-53 Rev 4 13 | -------------------------------------------------------------------------------- /examples/controls/datasecurity-encryption-in-transit.yaml: -------------------------------------------------------------------------------- 1 | name: DataSecurity-DatainTransit 2 | description: Information and records (data) are managed consistent with the organization’s risk strategy to protect the confidentiality, integrity, and availability of information. 3 | actionPlanTitle: All Resources needs to be encrypted in Transit 4 | actionPlanInstructions: Ensure all resources have attestations for Encryption in Transit enabled 5 | testingInformation: Test attestations - preventive and detective controls for encryption in Transit 6 | tags: 7 | ID: PRDS-1 8 | Subcategory: Data-in-transit 9 | Category: Data Security-PRDS 10 | CIS: CIS17 11 | COBIT: COBIT 5 APO07-03 12 | NIST: NIST SP 800-53 Rev 4 13 | datasources: 14 | - sourceName: Manual Attestation 15 | sourceDescription: Manual attestation 16 | sourceSetUpOption: Procedural_Controls_Mapping 17 | sourceType: MANUAL 18 | - sourceName: Config Attestation 19 | sourceDescription: Config attestation 20 | sourceSetUpOption: System_Controls_Mapping 21 | sourceType: AWS_Config 22 | sourceKeyword: 23 | keywordInputType: SELECT_FROM_LIST 24 | keywordValue: S3_DEFAULT_ENCRYPTION_KMS 25 | -------------------------------------------------------------------------------- /examples/frameworks/enterprise-framework.yaml: -------------------------------------------------------------------------------- 1 | name: NIST Enterprise Framework 2 | description: NIST Enterprise Control Framework customized 3 | complianceType: NIST 4 | controlSets: 5 | - name: DataSecurity 6 | controls: 7 | - DataSecurity-DataAtRest 8 | - DataSecurity-DatainTransit 9 | tags: 10 | Owner: AWSSamples 11 | Company: AWSLabs 12 | -------------------------------------------------------------------------------- /images/solution-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/enterprise-controls-catalog-via-aws-audit-manager/072866b843b9470431edc8324db0b7aaee04b990/images/solution-overview.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | module.exports = { 18 | testEnvironment: 'node', 19 | roots: ['/test'], 20 | testMatch: ['**/*.test.ts'], 21 | transform: { 22 | '^.+\\.tsx?$': 'ts-jest', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /lambda/controls-library/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/enterprise-controls-catalog-via-aws-audit-manager/072866b843b9470431edc8324db0b7aaee04b990/lambda/controls-library/.DS_Store -------------------------------------------------------------------------------- /lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda", 3 | "version": "1.0.0", 4 | "description": "lambda function for audit manager blog", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "webpack" 9 | }, 10 | "author": "Amazon Web Services", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "ansi-regex": "^6.0.1", 14 | "aws-sdk": "^2.1354.0", 15 | "js-yaml": "^4.1.0", 16 | "tmpl": "^1.0.5" 17 | }, 18 | "devDependencies": { 19 | "@types/aws-lambda": "^8.10.78", 20 | "clean-webpack-plugin": "^4.0.0-alpha.0", 21 | "jest": "^27.0.6", 22 | "ts-jest": "^27.0.3", 23 | "ts-loader": "^9.2.3", 24 | "typescript": "^4.3.5", 25 | "webpack": "^5.76.0", 26 | "webpack-cli": "^4.7.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lambda/src/controls.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import * as AWS from 'aws-sdk'; 19 | import { DataFile } from './data-file'; 20 | import { Control } from './types'; 21 | import { updateTags } from './tags'; 22 | 23 | const auditManager = new AWS.AuditManager({ apiVersion: '2017-07-25' }); 24 | 25 | export async function loadCustomControlList(): Promise { 26 | const results: AWS.AuditManager.ControlMetadataList = []; 27 | let nextToken: string | undefined = undefined; 28 | do { 29 | const response: AWS.AuditManager.ListControlsResponse = await auditManager 30 | .listControls({ controlType: 'Custom', nextToken }) 31 | .promise(); 32 | if (response.controlMetadataList) { 33 | results.push(...response.controlMetadataList); 34 | } 35 | nextToken = response.nextToken; 36 | } while (nextToken); 37 | return results; 38 | } 39 | 40 | function buildControlRequest(control: Control): AWS.AuditManager.CreateControlRequest { 41 | return { 42 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 43 | 44 | controlMappingSources: control.datasources!, 45 | 46 | name: control.name, 47 | actionPlanInstructions: control.actionPlanInstructions, 48 | actionPlanTitle: control.actionPlanTitle, 49 | description: control.description, 50 | testingInformation: control.testingInformation, 51 | }; 52 | } 53 | 54 | export async function processControlFile(dataFile: DataFile): Promise { 55 | const controlDefinition = dataFile.data as Control; 56 | console.log(controlDefinition); 57 | 58 | // get the list of all custom controls 59 | const controlList = await loadCustomControlList(); 60 | 61 | // check whether or not the control already exists 62 | const existingControl = controlList.find( 63 | ({ name }) => name === controlDefinition.name 64 | ); 65 | 66 | if (!controlDefinition.datasources) { 67 | controlDefinition.datasources = [ 68 | { 69 | sourceName: 'Manual Attestation', 70 | sourceDescription: 'Manual attestation', 71 | sourceSetUpOption: 'Procedural_Controls_Mapping', 72 | sourceType: 'MANUAL', 73 | }, 74 | ]; 75 | } 76 | // create or update the control 77 | if (!existingControl) { 78 | console.log('create new control'); 79 | const request = { 80 | ...buildControlRequest(controlDefinition), 81 | tags: controlDefinition.tags, 82 | }; 83 | const response = await auditManager.createControl(request).promise(); 84 | console.log(response); 85 | } else { 86 | const controlId = existingControl.id || ''; 87 | const resourceArn = existingControl.arn || ''; 88 | console.log(`update existing control id = ${controlId}`); 89 | const request: AWS.AuditManager.UpdateControlRequest = { 90 | ...buildControlRequest(controlDefinition), 91 | controlId, 92 | }; 93 | const response = await auditManager.updateControl(request).promise(); 94 | console.log(response); 95 | 96 | //update tags 97 | await updateTags(resourceArn, controlDefinition.tags); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lambda/src/data-file.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | import * as AWS from 'aws-sdk'; 18 | import { S3EventRecord } from 'aws-lambda'; 19 | import * as YAML from 'js-yaml'; 20 | 21 | const s3 = new AWS.S3({ apiVersion: '2006-03-01' }); 22 | 23 | function getType(objectKey: string) { 24 | const type = objectKey.split('/').slice(-2, -1)[0]; 25 | return type && ['controls', 'frameworks'].includes(type.toLowerCase()) 26 | ? type.toLowerCase() 27 | : null; 28 | } 29 | 30 | function getExtension(objectKey: string) { 31 | const extension = objectKey.split('.').slice(-1)[0]; 32 | return extension && ['json', 'yaml', 'yml'].includes(extension.toLowerCase()) 33 | ? extension.toLowerCase() 34 | : null; 35 | } 36 | 37 | export interface DataFile { 38 | type: string; 39 | data: unknown; 40 | } 41 | 42 | export async function loadDataFile(record: S3EventRecord): Promise { 43 | const bucketName = record.s3.bucket.name; 44 | const objectKey = record.s3.object.key; 45 | 46 | const type = getType(objectKey); 47 | const extension = getExtension(objectKey); 48 | 49 | const file = await s3 50 | .getObject({ 51 | Bucket: bucketName, 52 | Key: objectKey, 53 | }) 54 | .promise(); 55 | 56 | const content = file.Body?.toString('utf-8'); 57 | if (type && extension && content) { 58 | let data; 59 | if (extension === 'json') { 60 | data = JSON.parse(content); 61 | } else { 62 | data = YAML.load(content); 63 | } 64 | return { 65 | type, 66 | data, 67 | }; 68 | } else { 69 | return null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lambda/src/frameworks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import * as AWS from 'aws-sdk'; 19 | import { DataFile } from './data-file'; 20 | import { Framework } from './types'; 21 | import { loadCustomControlList } from './controls'; 22 | import { updateTags } from './tags'; 23 | 24 | const auditManager = new AWS.AuditManager({ apiVersion: '2017-07-25' }); 25 | 26 | async function loadCustomFrameworkList(): Promise { 27 | const results: AWS.AuditManager.FrameworkMetadataList = []; 28 | let nextToken: string | undefined = undefined; 29 | do { 30 | const response: AWS.AuditManager.ListAssessmentFrameworksResponse = 31 | await auditManager 32 | .listAssessmentFrameworks({ frameworkType: 'Custom', nextToken }) 33 | .promise(); 34 | if (response.frameworkMetadataList) { 35 | results.push(...response.frameworkMetadataList); 36 | } 37 | nextToken = response.nextToken; 38 | } while (nextToken); 39 | return results; 40 | } 41 | 42 | function buildFrameworkRequest( 43 | framework: Framework, 44 | customControlList: AWS.AuditManager.ControlMetadataList 45 | ): AWS.AuditManager.CreateAssessmentFrameworkRequest { 46 | const getControlIdByName = (name: string): string | undefined => 47 | customControlList.find(({ name: controlName }) => controlName === name)?.id; 48 | return { 49 | name: framework.name, 50 | description: framework.description, 51 | complianceType: framework.complianceType, 52 | controlSets: framework.controlSets.map((item) => ({ 53 | name: item.name, 54 | controls: item.controls.map((name) => ({ id: getControlIdByName(name) })), 55 | })), 56 | }; 57 | } 58 | 59 | export async function processFrameworkFile(dataFile: DataFile): Promise { 60 | const frameworkDefinition = dataFile.data as Framework; 61 | console.log(frameworkDefinition); 62 | 63 | // get the list of all custom frameworks 64 | const frameworkList = await loadCustomFrameworkList(); 65 | 66 | // check whether or not the framework already exists 67 | const existingFramework = frameworkList.find( 68 | ({ name }) => name === frameworkDefinition.name 69 | ); 70 | 71 | // load all custom controls 72 | const customControlList = await loadCustomControlList(); 73 | 74 | // create or update the framework 75 | if (!existingFramework) { 76 | console.log('create new framework'); 77 | const request: AWS.AuditManager.CreateAssessmentFrameworkRequest = { 78 | ...buildFrameworkRequest(frameworkDefinition, customControlList), 79 | tags: frameworkDefinition.tags, 80 | }; 81 | const response = await auditManager.createAssessmentFramework(request).promise(); 82 | console.log(response); 83 | } else { 84 | const frameworkId = existingFramework.id || ''; 85 | const resourceArn = existingFramework.arn || ''; 86 | console.log(`update existing framework id = ${frameworkId}`); 87 | const request: AWS.AuditManager.UpdateAssessmentFrameworkRequest = { 88 | ...buildFrameworkRequest(frameworkDefinition, customControlList), 89 | frameworkId, 90 | }; 91 | const response = await auditManager.updateAssessmentFramework(request).promise(); 92 | console.log(response); 93 | 94 | //update tags 95 | await updateTags(resourceArn, frameworkDefinition.tags); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lambda/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | import * as AWS from 'aws-sdk'; 18 | import { S3Event } from 'aws-lambda'; 19 | import { loadDataFile } from './data-file'; 20 | import { processControlFile } from './controls'; 21 | import { processFrameworkFile } from './frameworks'; 22 | 23 | const notificationTopicArn = process.env.SNS_TOPIC_ARN; 24 | const sns = new AWS.SNS({ apiVersion: '2010-03-31' }); 25 | 26 | async function sendNotification(message: string) { 27 | return sns 28 | .publish({ 29 | Message: message, 30 | Subject: 'Audit Manager Blog Notification', 31 | TopicArn: notificationTopicArn, 32 | }) 33 | .promise(); 34 | } 35 | 36 | async function handleError(error: Error): Promise { 37 | console.log(error.message); 38 | await sendNotification(`Error occured during processing. ${error.message}`); 39 | } 40 | 41 | export const handler = async (event: S3Event): Promise => { 42 | const record = event.Records[0]; 43 | if (record) { 44 | try { 45 | console.log( 46 | `Loading data file from s3 bucket. Bucket: ${record.s3.bucket.name}, Key: ${record.s3.object.key}` 47 | ); 48 | const dataFile = await loadDataFile(record); 49 | if (dataFile) { 50 | if (dataFile.type === 'controls') { 51 | console.log('Process control file.'); 52 | await processControlFile(dataFile); 53 | } else if (dataFile.type === 'frameworks') { 54 | console.log('Process framework file.'); 55 | await processFrameworkFile(dataFile); 56 | } 57 | } else { 58 | console.log('Failed to load data file from s3 bucket'); 59 | throw new Error(`Failed to load data file.`); 60 | } 61 | 62 | // send successful notification 63 | await sendNotification( 64 | `Successfully processed ${record.s3.object.key}` 65 | ); 66 | } catch (error) { 67 | console.log( 68 | `Error occurred on processing data file. Key: ${record.s3.object.key}` 69 | ); 70 | await handleError(new Error(`Key: ${record.s3.object.key}; `)); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /lambda/src/tags.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | import * as AWS from 'aws-sdk'; 18 | const auditManager = new AWS.AuditManager({ apiVersion: '2017-07-25' }); 19 | 20 | export async function updateTags( 21 | resourceArn: string, 22 | tags?: Record 23 | ): Promise { 24 | console.log('updating tags'); 25 | console.log(tags); 26 | 27 | // get tags from the resource 28 | const resourceTags = await auditManager 29 | .listTagsForResource({ resourceArn }) 30 | .promise(); 31 | 32 | // remove all tags from resource 33 | if (resourceTags.tags) { 34 | await auditManager 35 | .untagResource({ 36 | resourceArn, 37 | tagKeys: Object.keys(resourceTags.tags), 38 | }) 39 | .promise(); 40 | } 41 | 42 | // add tags to resource 43 | if (tags) { 44 | await auditManager 45 | .tagResource({ 46 | resourceArn, 47 | tags, 48 | }) 49 | .promise(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lambda/src/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | export interface Control { 18 | name: string; 19 | description?: string; 20 | actionPlanInstructions?: string; 21 | actionPlanTitle?: string; 22 | testingInformation?: string; 23 | tags?: Record; 24 | datasources?: dataSource[]; 25 | } 26 | 27 | export interface dataSource { 28 | sourceSetUpOption: string; 29 | sourceType: string; 30 | sourceName: string; 31 | sourceDescription: string; 32 | troubleshootingText?: string; 33 | sourceKeyword?: Record; 34 | sourceFrequency?: string; 35 | } 36 | export interface ControlSet { 37 | name: string; 38 | controls: string[]; 39 | } 40 | 41 | export interface Framework { 42 | name: string; 43 | description?: string; 44 | complianceType?: string; 45 | tags?: Record; 46 | controlSets: ControlSet[]; 47 | } 48 | -------------------------------------------------------------------------------- /lambda/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | index: './src/index.ts', 8 | }, 9 | devtool: 'source-map', 10 | mode: process.env.NODE_ENV || 'production', 11 | target: 'node', 12 | plugins: [new CleanWebpackPlugin()], 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.tsx?$/, 17 | use: 'ts-loader', 18 | exclude: /node_modules/, 19 | }, 20 | ], 21 | }, 22 | externals: [{ 'aws-sdk': 'commonjs aws-sdk' }], 23 | resolve: { 24 | extensions: ['.tsx', '.ts', '.js'], 25 | }, 26 | output: { 27 | filename: '[name].js', 28 | path: path.resolve(__dirname, '../dist/lambda/dist'), 29 | libraryTarget: 'commonjs', 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /lib/audit-manager-actions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | export const auditManagerActions = [ 18 | 'auditmanager:ListAssessmentFrameworks', 19 | 'auditmanager:CreateAssessmentFramework', 20 | 'auditmanager:UpdateAssessmentFramework', 21 | 'auditmanager:GetAssessmentFramework', 22 | 'auditmanager:ListControls', 23 | 'auditmanager:CreateControl', 24 | 'auditmanager:UpdateControl', 25 | 'auditmanager:GetControl', 26 | 'auditmanager:ListTagsForResource', 27 | 'auditmanager:TagResource', 28 | 'auditmanager:UntagResource', 29 | ]; 30 | -------------------------------------------------------------------------------- /lib/audit-manager-blog-stack.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | import * as path from 'path'; 18 | import { auditManagerActions } from './audit-manager-actions'; 19 | import { Construct } from 'constructs'; 20 | import { 21 | CfnOutput, 22 | RemovalPolicy, 23 | Stack, 24 | StackProps, 25 | Duration, 26 | } from 'aws-cdk-lib'; 27 | import { IKey, Key } from 'aws-cdk-lib/aws-kms'; 28 | import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; 29 | import { Function, Code, Runtime } from 'aws-cdk-lib/aws-lambda'; 30 | import { ManagedPolicy, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; 31 | import { Topic } from 'aws-cdk-lib/aws-sns'; 32 | import { Bucket, BucketEncryption, EventType } from 'aws-cdk-lib/aws-s3'; 33 | import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications'; 34 | 35 | export class AuditManagerBlogStack extends Stack { 36 | constructor(scope: Construct, id: string, props?: StackProps) { 37 | super(scope, id, props); 38 | 39 | const auditControlsBucket = new Bucket(this, 'bucket', { 40 | encryption: BucketEncryption.S3_MANAGED, 41 | versioned: true, 42 | removalPolicy: RemovalPolicy.DESTROY, 43 | autoDeleteObjects: true, 44 | }); 45 | const snskey: IKey = new Key(this, 'auditblogkey', { 46 | enableKeyRotation: true, 47 | description: 'auditblogkey', 48 | }); 49 | 50 | const notificationTopic = new Topic(this, 'topic', { 51 | topicName: 'AuditManagerBlogNotification', 52 | masterKey: snskey, 53 | }); 54 | 55 | const auditManagerPolicyStatement = new PolicyStatement({ 56 | actions: auditManagerActions, 57 | effect: Effect.ALLOW, 58 | resources: ['*'], 59 | }); 60 | 61 | const listenerFunction = new Function(this, 'lambda', { 62 | handler: 'index.handler', 63 | code: Code.fromAsset( 64 | path.resolve(__dirname, `../dist/lambda/dist`) 65 | ), 66 | timeout: Duration.seconds(30), 67 | runtime: Runtime.NODEJS_14_X, 68 | reservedConcurrentExecutions: 1, 69 | initialPolicy: [auditManagerPolicyStatement], 70 | environment: { 71 | SNS_TOPIC_ARN: notificationTopic.topicArn, 72 | }, 73 | }); 74 | snskey.grantEncryptDecrypt(listenerFunction); 75 | 76 | auditControlsBucket.grantRead(listenerFunction); 77 | notificationTopic.grantPublish(listenerFunction); 78 | 79 | listenerFunction.role?.addManagedPolicy( 80 | ManagedPolicy.fromAwsManagedPolicyName( 81 | 'service-role/AWSLambdaBasicExecutionRole' 82 | ) 83 | ); 84 | 85 | auditControlsBucket.addEventNotification( 86 | EventType.OBJECT_CREATED_PUT, 87 | new LambdaDestination(listenerFunction) 88 | ); 89 | 90 | new CfnOutput(this, 'bucketOutput', { 91 | description: 92 | 'Bucket name for Audit Manager Custom Controls and Frameworks', 93 | value: auditControlsBucket.bucketName, 94 | }); 95 | 96 | new CfnOutput(this, 'notificationTopicArnOutput', { 97 | description: 'SNS topic ARN for notification', 98 | value: notificationTopic.topicArn, 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audit-manager-blog", 3 | "version": "0.1.0", 4 | "bin": { 5 | "audit-manager-blog": "bin/audit-manager-blog.js" 6 | }, 7 | "scripts": { 8 | "lint": "eslint --fix --ext .ts .", 9 | "build:lambda": "cd lambda && npm run build && cd ..", 10 | "build:infra": "tsc", 11 | "build": "npm run build:lambda && npm run build:infra", 12 | "synth": "cdk synth -q", 13 | "watch": "tsc -w", 14 | "test": "jest", 15 | "cdk": "cdk", 16 | "all": "npm run lint && npm run build && npm run test && npm run synth" 17 | }, 18 | "devDependencies": { 19 | "@types/jest": "27.0.2", 20 | "@types/node": "16.11.9", 21 | "@typescript-eslint/eslint-plugin": "^4.28.5", 22 | "@typescript-eslint/parser": "^4.28.5", 23 | "aws-cdk": "2.2.0", 24 | "aws-cdk-lib": "2.80.0", 25 | "constructs": "^10.0.0", 26 | "eslint": "^7.32.0", 27 | "eslint-config-prettier": "^7.1.0", 28 | "eslint-config-typescript": "^3.0.0", 29 | "eslint-plugin-header": "^3.1.0", 30 | "eslint-plugin-prettier": "^3.3.0", 31 | "prettier": "^2.3.2", 32 | "ts-jest": "27.0.5", 33 | "ts-loader": "^9.2.3", 34 | "ts-node": "10.2.1", 35 | "typescript": "4.4.3", 36 | "webpack": "^5.76.0" 37 | }, 38 | "dependencies": { 39 | "@types/js-yaml": "^4.0.2", 40 | "ansi-regex": "^6.0.1", 41 | "jest": "^27.4.7", 42 | "lodash": "^4.17.21", 43 | "set-value": "^4.1.0", 44 | "source-map-support": "^0.5.16", 45 | "tmpl": "^1.0.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/enterprise-controls-catalog-via-aws-audit-manager/072866b843b9470431edc8324db0b7aaee04b990/test/.DS_Store -------------------------------------------------------------------------------- /test/__snapshots__/audit-manager-blog.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Snapshot Test 1`] = ` 4 | Object { 5 | "Outputs": Object { 6 | "bucketOutput": Object { 7 | "Description": "Bucket name for Audit Manager Custom Controls and Frameworks", 8 | "Value": Object { 9 | "Ref": "bucket43879C71", 10 | }, 11 | }, 12 | "notificationTopicArnOutput": Object { 13 | "Description": "SNS topic ARN for notification", 14 | "Value": Object { 15 | "Ref": "topic69831491", 16 | }, 17 | }, 18 | }, 19 | "Parameters": Object { 20 | "AssetParameters4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392ArtifactHashE56CD69A": Object { 21 | "Description": "Artifact hash for asset \\"4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392\\"", 22 | "Type": "String", 23 | }, 24 | "AssetParameters4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392S3BucketBF7A7F3F": Object { 25 | "Description": "S3 bucket for asset \\"4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392\\"", 26 | "Type": "String", 27 | }, 28 | "AssetParameters4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392S3VersionKeyFAF93626": Object { 29 | "Description": "S3 key for asset version \\"4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392\\"", 30 | "Type": "String", 31 | }, 32 | "AssetParameters64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2dArtifactHashCE1013B5": Object { 33 | "Description": "Artifact hash for asset \\"64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2d\\"", 34 | "Type": "String", 35 | }, 36 | "AssetParameters64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2dS3BucketE414825A": Object { 37 | "Description": "S3 bucket for asset \\"64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2d\\"", 38 | "Type": "String", 39 | }, 40 | "AssetParameters64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2dS3VersionKey224FC20B": Object { 41 | "Description": "S3 key for asset version \\"64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2d\\"", 42 | "Type": "String", 43 | }, 44 | }, 45 | "Resources": Object { 46 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691": Object { 47 | "DependsOn": Array [ 48 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", 49 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", 50 | ], 51 | "Properties": Object { 52 | "Code": Object { 53 | "ZipFile": "exports.handler = (event, context) => { 54 | // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies 55 | const s3 = new (require('aws-sdk').S3)(); 56 | // eslint-disable-next-line @typescript-eslint/no-require-imports 57 | const https = require('https'); 58 | // eslint-disable-next-line @typescript-eslint/no-require-imports 59 | const url = require('url'); 60 | log(JSON.stringify(event, undefined, 2)); 61 | const props = event.ResourceProperties; 62 | if (event.RequestType === 'Delete') { 63 | props.NotificationConfiguration = {}; // this is how you clean out notifications 64 | } 65 | const req = { 66 | Bucket: props.BucketName, 67 | NotificationConfiguration: props.NotificationConfiguration, 68 | }; 69 | return s3.putBucketNotificationConfiguration(req, (err, data) => { 70 | log({ err, data }); 71 | if (err) { 72 | return submitResponse('FAILED', err.message + \`\\\\nMore information in CloudWatch Log Stream: \${context.logStreamName}\`); 73 | } 74 | else { 75 | return submitResponse('SUCCESS'); 76 | } 77 | }); 78 | function log(obj) { 79 | console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj); 80 | } 81 | // eslint-disable-next-line max-len 82 | // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule 83 | // to allow sending an error message as a reason. 84 | function submitResponse(responseStatus, reason) { 85 | const responseBody = JSON.stringify({ 86 | Status: responseStatus, 87 | Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName, 88 | PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId, 89 | StackId: event.StackId, 90 | RequestId: event.RequestId, 91 | LogicalResourceId: event.LogicalResourceId, 92 | NoEcho: false, 93 | }); 94 | log({ responseBody }); 95 | const parsedUrl = url.parse(event.ResponseURL); 96 | const options = { 97 | hostname: parsedUrl.hostname, 98 | port: 443, 99 | path: parsedUrl.path, 100 | method: 'PUT', 101 | headers: { 102 | 'content-type': '', 103 | 'content-length': responseBody.length, 104 | }, 105 | }; 106 | const request = https.request(options, (r) => { 107 | log({ statusCode: r.statusCode, statusMessage: r.statusMessage }); 108 | context.done(); 109 | }); 110 | request.on('error', (error) => { 111 | log({ sendError: error }); 112 | context.done(); 113 | }); 114 | request.write(responseBody); 115 | request.end(); 116 | } 117 | };", 118 | }, 119 | "Description": "AWS CloudFormation handler for \\"Custom::S3BucketNotifications\\" resources (@aws-cdk/aws-s3)", 120 | "Handler": "index.handler", 121 | "Role": Object { 122 | "Fn::GetAtt": Array [ 123 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", 124 | "Arn", 125 | ], 126 | }, 127 | "Runtime": "nodejs12.x", 128 | "Timeout": 300, 129 | }, 130 | "Type": "AWS::Lambda::Function", 131 | }, 132 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": Object { 133 | "Properties": Object { 134 | "AssumeRolePolicyDocument": Object { 135 | "Statement": Array [ 136 | Object { 137 | "Action": "sts:AssumeRole", 138 | "Effect": "Allow", 139 | "Principal": Object { 140 | "Service": "lambda.amazonaws.com", 141 | }, 142 | }, 143 | ], 144 | "Version": "2012-10-17", 145 | }, 146 | "ManagedPolicyArns": Array [ 147 | Object { 148 | "Fn::Join": Array [ 149 | "", 150 | Array [ 151 | "arn:", 152 | Object { 153 | "Ref": "AWS::Partition", 154 | }, 155 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 156 | ], 157 | ], 158 | }, 159 | ], 160 | }, 161 | "Type": "AWS::IAM::Role", 162 | }, 163 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36": Object { 164 | "Properties": Object { 165 | "PolicyDocument": Object { 166 | "Statement": Array [ 167 | Object { 168 | "Action": "s3:PutBucketNotification", 169 | "Effect": "Allow", 170 | "Resource": "*", 171 | }, 172 | ], 173 | "Version": "2012-10-17", 174 | }, 175 | "PolicyName": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", 176 | "Roles": Array [ 177 | Object { 178 | "Ref": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", 179 | }, 180 | ], 181 | }, 182 | "Type": "AWS::IAM::Policy", 183 | }, 184 | "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": Object { 185 | "DependsOn": Array [ 186 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", 187 | ], 188 | "Properties": Object { 189 | "Code": Object { 190 | "S3Bucket": Object { 191 | "Ref": "AssetParameters4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392S3BucketBF7A7F3F", 192 | }, 193 | "S3Key": Object { 194 | "Fn::Join": Array [ 195 | "", 196 | Array [ 197 | Object { 198 | "Fn::Select": Array [ 199 | 0, 200 | Object { 201 | "Fn::Split": Array [ 202 | "||", 203 | Object { 204 | "Ref": "AssetParameters4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392S3VersionKeyFAF93626", 205 | }, 206 | ], 207 | }, 208 | ], 209 | }, 210 | Object { 211 | "Fn::Select": Array [ 212 | 1, 213 | Object { 214 | "Fn::Split": Array [ 215 | "||", 216 | Object { 217 | "Ref": "AssetParameters4cd61014b71160e8c66fe167e43710d5ba068b80b134e9bd84508cf9238b2392S3VersionKeyFAF93626", 218 | }, 219 | ], 220 | }, 221 | ], 222 | }, 223 | ], 224 | ], 225 | }, 226 | }, 227 | "Description": Object { 228 | "Fn::Join": Array [ 229 | "", 230 | Array [ 231 | "Lambda function for auto-deleting objects in ", 232 | Object { 233 | "Ref": "bucket43879C71", 234 | }, 235 | " S3 bucket.", 236 | ], 237 | ], 238 | }, 239 | "Handler": "__entrypoint__.handler", 240 | "MemorySize": 128, 241 | "Role": Object { 242 | "Fn::GetAtt": Array [ 243 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", 244 | "Arn", 245 | ], 246 | }, 247 | "Runtime": "nodejs12.x", 248 | "Timeout": 900, 249 | }, 250 | "Type": "AWS::Lambda::Function", 251 | }, 252 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": Object { 253 | "Properties": Object { 254 | "AssumeRolePolicyDocument": Object { 255 | "Statement": Array [ 256 | Object { 257 | "Action": "sts:AssumeRole", 258 | "Effect": "Allow", 259 | "Principal": Object { 260 | "Service": "lambda.amazonaws.com", 261 | }, 262 | }, 263 | ], 264 | "Version": "2012-10-17", 265 | }, 266 | "ManagedPolicyArns": Array [ 267 | Object { 268 | "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 269 | }, 270 | ], 271 | }, 272 | "Type": "AWS::IAM::Role", 273 | }, 274 | "bucket43879C71": Object { 275 | "DeletionPolicy": "Delete", 276 | "Properties": Object { 277 | "BucketEncryption": Object { 278 | "ServerSideEncryptionConfiguration": Array [ 279 | Object { 280 | "ServerSideEncryptionByDefault": Object { 281 | "SSEAlgorithm": "AES256", 282 | }, 283 | }, 284 | ], 285 | }, 286 | "VersioningConfiguration": Object { 287 | "Status": "Enabled", 288 | }, 289 | }, 290 | "Type": "AWS::S3::Bucket", 291 | "UpdateReplacePolicy": "Delete", 292 | }, 293 | "bucketAllowBucketNotificationsToMyTestStacklambdaA681BEC4C571CDE3": Object { 294 | "Properties": Object { 295 | "Action": "lambda:InvokeFunction", 296 | "FunctionName": Object { 297 | "Fn::GetAtt": Array [ 298 | "lambda8B5974B5", 299 | "Arn", 300 | ], 301 | }, 302 | "Principal": "s3.amazonaws.com", 303 | "SourceAccount": Object { 304 | "Ref": "AWS::AccountId", 305 | }, 306 | "SourceArn": Object { 307 | "Fn::GetAtt": Array [ 308 | "bucket43879C71", 309 | "Arn", 310 | ], 311 | }, 312 | }, 313 | "Type": "AWS::Lambda::Permission", 314 | }, 315 | "bucketAutoDeleteObjectsCustomResource3F4990B2": Object { 316 | "DeletionPolicy": "Delete", 317 | "DependsOn": Array [ 318 | "bucketPolicy638F945D", 319 | ], 320 | "Properties": Object { 321 | "BucketName": Object { 322 | "Ref": "bucket43879C71", 323 | }, 324 | "ServiceToken": Object { 325 | "Fn::GetAtt": Array [ 326 | "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", 327 | "Arn", 328 | ], 329 | }, 330 | }, 331 | "Type": "Custom::S3AutoDeleteObjects", 332 | "UpdateReplacePolicy": "Delete", 333 | }, 334 | "bucketNotifications2CB09E7A": Object { 335 | "DependsOn": Array [ 336 | "bucketAllowBucketNotificationsToMyTestStacklambdaA681BEC4C571CDE3", 337 | ], 338 | "Properties": Object { 339 | "BucketName": Object { 340 | "Ref": "bucket43879C71", 341 | }, 342 | "NotificationConfiguration": Object { 343 | "LambdaFunctionConfigurations": Array [ 344 | Object { 345 | "Events": Array [ 346 | "s3:ObjectCreated:Put", 347 | ], 348 | "LambdaFunctionArn": Object { 349 | "Fn::GetAtt": Array [ 350 | "lambda8B5974B5", 351 | "Arn", 352 | ], 353 | }, 354 | }, 355 | ], 356 | }, 357 | "ServiceToken": Object { 358 | "Fn::GetAtt": Array [ 359 | "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691", 360 | "Arn", 361 | ], 362 | }, 363 | }, 364 | "Type": "Custom::S3BucketNotifications", 365 | }, 366 | "bucketPolicy638F945D": Object { 367 | "Properties": Object { 368 | "Bucket": Object { 369 | "Ref": "bucket43879C71", 370 | }, 371 | "PolicyDocument": Object { 372 | "Statement": Array [ 373 | Object { 374 | "Action": Array [ 375 | "s3:GetBucket*", 376 | "s3:List*", 377 | "s3:DeleteObject*", 378 | ], 379 | "Effect": "Allow", 380 | "Principal": Object { 381 | "AWS": Object { 382 | "Fn::GetAtt": Array [ 383 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", 384 | "Arn", 385 | ], 386 | }, 387 | }, 388 | "Resource": Array [ 389 | Object { 390 | "Fn::GetAtt": Array [ 391 | "bucket43879C71", 392 | "Arn", 393 | ], 394 | }, 395 | Object { 396 | "Fn::Join": Array [ 397 | "", 398 | Array [ 399 | Object { 400 | "Fn::GetAtt": Array [ 401 | "bucket43879C71", 402 | "Arn", 403 | ], 404 | }, 405 | "/*", 406 | ], 407 | ], 408 | }, 409 | ], 410 | }, 411 | ], 412 | "Version": "2012-10-17", 413 | }, 414 | }, 415 | "Type": "AWS::S3::BucketPolicy", 416 | }, 417 | "lambda8B5974B5": Object { 418 | "DependsOn": Array [ 419 | "lambdaServiceRoleDefaultPolicyBF6FA5E7", 420 | "lambdaServiceRole494E4CA6", 421 | ], 422 | "Properties": Object { 423 | "Code": Object { 424 | "S3Bucket": Object { 425 | "Ref": "AssetParameters64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2dS3BucketE414825A", 426 | }, 427 | "S3Key": Object { 428 | "Fn::Join": Array [ 429 | "", 430 | Array [ 431 | Object { 432 | "Fn::Select": Array [ 433 | 0, 434 | Object { 435 | "Fn::Split": Array [ 436 | "||", 437 | Object { 438 | "Ref": "AssetParameters64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2dS3VersionKey224FC20B", 439 | }, 440 | ], 441 | }, 442 | ], 443 | }, 444 | Object { 445 | "Fn::Select": Array [ 446 | 1, 447 | Object { 448 | "Fn::Split": Array [ 449 | "||", 450 | Object { 451 | "Ref": "AssetParameters64b8f0e1dee54523e2d6c777a3f2585dbdc6f296e445da83549e39498ab69c2dS3VersionKey224FC20B", 452 | }, 453 | ], 454 | }, 455 | ], 456 | }, 457 | ], 458 | ], 459 | }, 460 | }, 461 | "Environment": Object { 462 | "Variables": Object { 463 | "SNS_TOPIC_ARN": Object { 464 | "Ref": "topic69831491", 465 | }, 466 | }, 467 | }, 468 | "Handler": "index.handler", 469 | "ReservedConcurrentExecutions": 1, 470 | "Role": Object { 471 | "Fn::GetAtt": Array [ 472 | "lambdaServiceRole494E4CA6", 473 | "Arn", 474 | ], 475 | }, 476 | "Runtime": "nodejs14.x", 477 | "Timeout": 30, 478 | }, 479 | "Type": "AWS::Lambda::Function", 480 | }, 481 | "lambdaServiceRole494E4CA6": Object { 482 | "Properties": Object { 483 | "AssumeRolePolicyDocument": Object { 484 | "Statement": Array [ 485 | Object { 486 | "Action": "sts:AssumeRole", 487 | "Effect": "Allow", 488 | "Principal": Object { 489 | "Service": "lambda.amazonaws.com", 490 | }, 491 | }, 492 | ], 493 | "Version": "2012-10-17", 494 | }, 495 | "ManagedPolicyArns": Array [ 496 | Object { 497 | "Fn::Join": Array [ 498 | "", 499 | Array [ 500 | "arn:", 501 | Object { 502 | "Ref": "AWS::Partition", 503 | }, 504 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 505 | ], 506 | ], 507 | }, 508 | ], 509 | }, 510 | "Type": "AWS::IAM::Role", 511 | }, 512 | "lambdaServiceRoleDefaultPolicyBF6FA5E7": Object { 513 | "Properties": Object { 514 | "PolicyDocument": Object { 515 | "Statement": Array [ 516 | Object { 517 | "Action": Array [ 518 | "auditmanager:ListAssessmentFrameworks", 519 | "auditmanager:CreateAssessmentFramework", 520 | "auditmanager:UpdateAssessmentFramework", 521 | "auditmanager:GetAssessmentFramework", 522 | "auditmanager:ListControls", 523 | "auditmanager:CreateControl", 524 | "auditmanager:UpdateControl", 525 | "auditmanager:GetControl", 526 | "auditmanager:ListTagsForResource", 527 | "auditmanager:TagResource", 528 | "auditmanager:UntagResource", 529 | ], 530 | "Effect": "Allow", 531 | "Resource": "*", 532 | }, 533 | Object { 534 | "Action": Array [ 535 | "s3:GetObject*", 536 | "s3:GetBucket*", 537 | "s3:List*", 538 | ], 539 | "Effect": "Allow", 540 | "Resource": Array [ 541 | Object { 542 | "Fn::GetAtt": Array [ 543 | "bucket43879C71", 544 | "Arn", 545 | ], 546 | }, 547 | Object { 548 | "Fn::Join": Array [ 549 | "", 550 | Array [ 551 | Object { 552 | "Fn::GetAtt": Array [ 553 | "bucket43879C71", 554 | "Arn", 555 | ], 556 | }, 557 | "/*", 558 | ], 559 | ], 560 | }, 561 | ], 562 | }, 563 | Object { 564 | "Action": "sns:Publish", 565 | "Effect": "Allow", 566 | "Resource": Object { 567 | "Ref": "topic69831491", 568 | }, 569 | }, 570 | ], 571 | "Version": "2012-10-17", 572 | }, 573 | "PolicyName": "lambdaServiceRoleDefaultPolicyBF6FA5E7", 574 | "Roles": Array [ 575 | Object { 576 | "Ref": "lambdaServiceRole494E4CA6", 577 | }, 578 | ], 579 | }, 580 | "Type": "AWS::IAM::Policy", 581 | }, 582 | "topic69831491": Object { 583 | "Properties": Object { 584 | "TopicName": "AuditManagerBlogNotification", 585 | }, 586 | "Type": "AWS::SNS::Topic", 587 | }, 588 | }, 589 | } 590 | `; 591 | -------------------------------------------------------------------------------- /test/audit-manager-blog.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 12 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 14 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 15 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | import * as cdk from '@aws-cdk/core'; 18 | import { SynthUtils } from '@aws-cdk/assert'; 19 | import { AuditManagerBlogStack } from '../lib/audit-manager-blog-stack'; 20 | 21 | test('Snapshot Test', () => { 22 | const app = new cdk.App(); 23 | // WHEN 24 | const stack = new AuditManagerBlogStack(app, 'MyTestStack'); 25 | // THEN 26 | expect(SynthUtils.toCloudFormation(stack)).toBeDefined(); 27 | }); 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "target": "ES2018", 5 | "module": "commonjs", 6 | "lib": ["es2018"], 7 | "declaration": false, 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "noImplicitThis": true, 12 | "alwaysStrict": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": false, 17 | "inlineSourceMap": true, 18 | "inlineSources": true, 19 | "experimentalDecorators": true, 20 | "strictPropertyInitialization": false, 21 | "typeRoots": ["./node_modules/@types"] 22 | }, 23 | "exclude": ["cdk.out", "dist", "test"] 24 | } 25 | --------------------------------------------------------------------------------