├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-SAMPLECODE ├── LICENSE-SUMMARY ├── README.md ├── bootstrap.sh ├── docs ├── 00-initial-setup │ ├── README.md │ └── images │ │ ├── 00-base-architecture.png │ │ ├── 00-ee-cloudformation.png │ │ ├── 00-event-engine-console-login-2.png │ │ ├── 00-event-engine-console-login.png │ │ ├── 00-event-engine-login.png │ │ ├── 03-cloudformation-output.png │ │ ├── 0A-ee-cloudformation-output.png │ │ ├── 0A-initial-stack-diagram.png │ │ ├── 0B-aurora-1.png │ │ ├── 0B-aurora-2.png │ │ ├── 0B-aurora-endpoint.png │ │ ├── 0B-aurora-network-setting.png │ │ ├── 0B-clone-repo.png │ │ ├── 0B-cloud9-environments.png │ │ ├── 0B-cloud9-start.png │ │ ├── 0B-copy-past-scratch.png │ │ ├── 0B-create-scratch.png │ │ ├── 0B-serverless-aurora.png │ │ ├── 0C-cloud9-cd.png │ │ ├── 0C-diagram-with-aurora.png │ │ ├── 0C-open-ide.png │ │ ├── 0D-aws-resource-bar.png │ │ ├── 0D-cloudformation-output-w-bucket-highlight.png │ │ ├── 0D-db-endpoint-in-code.png │ │ ├── 0D-review-code.png │ │ ├── 0D-run-apigateway-local.png │ │ ├── 0D-vulnerability.png │ │ ├── 0E-get-endpoint-output.png │ │ ├── 0E-sam-local-result.png │ │ ├── 0E-test-browser.png │ │ ├── 0E-unsaved.png │ │ ├── 0F-copy-bucket.png │ │ ├── 0F-import-postman.png │ │ ├── 0F-postman-after-import.png │ │ ├── 0F-postman-environment.png │ │ ├── 0F-postman-manage-env.png │ │ ├── 0F-postman-test-get.png │ │ ├── 0F-select-dev-env.png │ │ ├── 0a-cloudformation-output-with-aurora-endpoint.png │ │ └── cfn-launch-stack.png ├── 01-add-authentication │ ├── README.md │ └── images │ │ ├── 10-high-level-architecture-w-auth.png │ │ ├── 10-oauth-flow.png │ │ ├── 1A-cognito-domain.png │ │ ├── 1C-custom-auth-workflow.png │ │ ├── 1C-id-mapping-diagram.png │ │ ├── 1C-verify-API-authorizer.png │ │ ├── 1D-admin-clientID.png │ │ ├── 1D-cognito-admin-oauth-scope.png │ │ ├── 1E-create-customization.png │ │ ├── 1E-postman-add-auth.png │ │ ├── 1E-postman-gettoken.png │ │ ├── 1E-postman-header.png │ │ ├── 1E-register-partner-success.png │ │ ├── 1F-get-token-for-company.png │ │ ├── cognito-add-admin.png │ │ ├── cognito-add-custom-scope.png │ │ ├── cognito-admin-oauth-scope.png │ │ ├── postman-auth-get-token.png │ │ ├── postman-no-auth-rejected.png │ │ └── postman-use-token.png ├── 02-add-secrets-manager │ ├── README.md │ └── images │ │ ├── 00-secrets-manager.png │ │ ├── 01-store-new-secret.png │ │ ├── 02-secret-select-db.png │ │ ├── 03-secret-name.png │ │ ├── 04-rotation.png │ │ ├── 05-example-code.png │ │ ├── 06-dbConfig-changed.png │ │ └── 2A-verify-secret.png ├── 03-input-validation │ ├── README.md │ └── images │ │ ├── 06_api_model.png │ │ ├── 06_customizations.png │ │ ├── 06_method_execution.png │ │ ├── 3A-after-injection.png │ │ ├── 3A-injection.png │ │ └── 3B_invalid_request_postman.png ├── 04-ssl-in-transit │ ├── README.md │ └── images │ │ └── check-engine-version.png ├── 05-usage-plan │ ├── README.md │ └── images │ │ ├── 5A-API-key-created.png │ │ ├── 5A-add-stage-to-plan.png │ │ ├── 5A-auto-generate-API-key.png │ │ ├── 5A-create-API-key.png │ │ ├── 5B-add-API-key-to-swagger.png │ │ ├── 5B-add-security-def-no-module-1.png │ │ ├── 5B-add-security-def.png │ │ ├── 5B-confirm-usage-plan-requirement.png │ │ ├── 5C-explicit-API-key.png │ │ ├── 5C-find-api-key.png │ │ ├── 5D-api-source-authorizer-swagger-no-module-1.png │ │ ├── 5D-api-source-authorizer-swagger.png │ │ ├── 5E-postman-throttle.png │ │ ├── 5E-runner-config.png │ │ ├── add-apig-stage.png │ │ └── create-usage-plan.png ├── 06-waf │ ├── README.md │ └── images │ │ ├── SQLi-attack-success.png │ │ ├── classifc-waf-opening.png │ │ ├── large-body-condition.png │ │ ├── large-body-rule.png │ │ ├── list-rules.png │ │ ├── recreate-table.png │ │ ├── request-flood-rule.png │ │ ├── request-sample.png │ │ ├── review-acl.png │ │ ├── sql-condition.png │ │ ├── sql-rule.png │ │ ├── switch-waf-classic.png │ │ └── web-acl-name.png ├── 07-dependency-vulnerability │ ├── README.md │ └── images │ │ ├── audit-result.png │ │ ├── dependency-check.png │ │ ├── functionshield.png │ │ └── vulnerable-dependency.png ├── 08-xray │ ├── README.md │ └── images │ │ ├── 8E-enable-apig.png │ │ ├── 8E-service-map.png │ │ ├── 8E-single-traces.png │ │ └── 8E-traces.png ├── 10-resource-cleanup │ ├── README.md │ └── images │ │ └── empty-s3-bucket.png └── images │ ├── cleanup.png │ ├── moduel1.png │ ├── module0.png │ ├── module2.png │ ├── module3.png │ ├── module4.png │ ├── module5.png │ ├── module6.png │ ├── module7.png │ └── module8.png └── src ├── apiclient ├── css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── main.css │ ├── site.css │ └── vendor │ │ └── bootstrap.css ├── img │ └── request.png ├── index.html └── js │ ├── bootstrap.min.js │ ├── jquery-3.7.1.min.js │ └── main.js ├── app ├── assets │ └── rds-ca-2019-root.pem ├── customUnicornAnalytics.js ├── customizeUnicorn.js ├── dbUtils.js ├── httpUtil.js ├── managePartners.js ├── package.json ├── permissions.js └── unicornParts.js ├── authorizer ├── index.js ├── package-lock.json └── package.json ├── init ├── db │ └── queries.sql └── init-template.yml ├── retired └── bootstrap.sh ├── template.yaml └── test-events └── Customize_Unicorns.postman_collection.json /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # mac .DS_Store 107 | .DS_Store -------------------------------------------------------------------------------- /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 | # Guidelines for contributing 2 | 3 | Thank you for your interest in contributing to AWS documentation! We greatly value feedback and contributions from our community. 4 | 5 | Please read through this document before you submit any pull requests or issues. It will help us work together more effectively. 6 | 7 | ## What to expect when you contribute 8 | 9 | When you submit a pull request, our team is notified and will respond as quickly as we can. We'll do our best to work with you to ensure that your pull request adheres to our style and standards. If we merge your pull request, we might make additional edits later for style or clarity. 10 | 11 | The AWS documentation source files on GitHub aren't published directly to the official documentation website. If we merge your pull request, we'll publish your changes to the documentation website as soon as we can, but they won't appear immediately or automatically. 12 | 13 | We look forward to receiving your pull requests for: 14 | 15 | * New content you'd like to contribute (such as new code samples or tutorials) 16 | * Inaccuracies in the content 17 | * Information gaps in the content that need more detail to be complete 18 | * Typos or grammatical errors 19 | * Suggested rewrites that improve clarity and reduce confusion 20 | 21 | **Note:** We all write differently, and you might not like how we've written or organized something currently. We want that feedback. But please be sure that your request for a rewrite is supported by the previous criteria. If it isn't, we might decline to merge it. 22 | 23 | ## How to contribute 24 | 25 | To contribute, send us a pull request. For small changes, such as fixing a typo or adding a link, you can use the [GitHub Edit Button](https://blog.github.com/2011-04-26-forking-with-the-edit-button/). For larger changes: 26 | 27 | 1. [Fork the repository](https://help.github.com/articles/fork-a-repo/). 28 | 2. In your fork, make your change in a branch that's based on this repo's **master** branch. 29 | 3. Commit the change to your fork, using a clear and descriptive commit message. 30 | 4. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/), answering any questions in the pull request form. 31 | 32 | Before you send us a pull request, please be sure that: 33 | 34 | 1. You're working from the latest source on the **master** branch. 35 | 2. You check [existing open](https://github.com/awsdocs/aws-serverless-security-workshop/pulls), and [recently closed](https://github.com/awsdocs/aws-serverless-security-workshop/pulls?q=is%3Apr+is%3Aclosed), pull requests to be sure that someone else hasn't already addressed the problem. 36 | 3. You [create an issue](https://github.com/awsdocs/aws-serverless-security-workshop/issues/new) before working on a contribution that will take a significant amount of your time. 37 | 38 | For contributions that will take a significant amount of time, [open a new issue](https://github.com/awsdocs/aws-serverless-security-workshop/issues/new) to pitch your idea before you get started. Explain the problem and describe the content you want to see added to the documentation. Let us know if you'll write it yourself or if you'd like us to help. We'll discuss your proposal with you and let you know whether we're likely to accept it. We don't want you to spend a lot of time on a contribution that might be outside the scope of the documentation or that's already in the works. 39 | 40 | ## Finding contributions to work on 41 | 42 | If you'd like to contribute, but don't have a project in mind, look at the [open issues](https://github.com/awsdocs/aws-serverless-security-workshop/issues) in this repository for some ideas. Any issues with the [help wanted](https://github.com/awsdocs/aws-serverless-security-workshop/labels/help%20wanted) or [enhancement](https://github.com/awsdocs/aws-serverless-security-workshop/labels/enhancement) labels are a great place to start. 43 | 44 | In addition to written content, we really appreciate new examples and code samples for our documentation, such as examples for different platforms or environments, and code samples in additional languages. 45 | 46 | ## Code of conduct 47 | 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information, see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments. 49 | 50 | ## Security issue notifications 51 | 52 | If you discover a potential security issue, please notify AWS Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public issue on GitHub. 53 | 54 | ## Licensing 55 | 56 | See the [LICENSE](https://github.com/awsdocs/aws-serverless-security-workshop/blob/master/LICENSE) file for this project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-ShareAlike 4.0 International Public License 2 | 3 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 4 | 5 | Section 1 – Definitions. 6 | 7 | a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 8 | 9 | b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 10 | 11 | c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. 12 | 13 | d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 14 | 15 | e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 16 | 17 | f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 18 | 19 | g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. 20 | 21 | h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 22 | 23 | i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 24 | 25 | j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. 26 | 27 | k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 28 | 29 | l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 30 | 31 | m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 32 | 33 | Section 2 – Scope. 34 | 35 | a. License grant. 36 | 37 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 38 | 39 | A. reproduce and Share the Licensed Material, in whole or in part; and 40 | 41 | B. produce, reproduce, and Share Adapted Material. 42 | 43 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 44 | 45 | 3. Term. The term of this Public License is specified in Section 6(a). 46 | 47 | 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 48 | 49 | 5. Downstream recipients. 50 | 51 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 52 | 53 | B. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. 54 | 55 | C. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 56 | 57 | 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 58 | 59 | b. Other rights. 60 | 61 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 62 | 63 | 2. Patent and trademark rights are not licensed under this Public License. 64 | 65 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 66 | 67 | Section 3 – License Conditions. 68 | 69 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 70 | 71 | a. Attribution. 72 | 73 | 1. If You Share the Licensed Material (including in modified form), You must: 74 | 75 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 76 | 77 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 78 | 79 | ii. a copyright notice; 80 | 81 | iii. a notice that refers to this Public License; 82 | 83 | iv. a notice that refers to the disclaimer of warranties; 84 | 85 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 86 | 87 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 88 | 89 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 90 | 91 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 92 | 93 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 94 | 95 | b. ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 96 | 97 | 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 98 | 99 | 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 100 | 101 | 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. 102 | 103 | Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 108 | 109 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and 110 | 111 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 112 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 113 | 114 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 115 | 116 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 117 | 118 | b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 119 | 120 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 121 | 122 | Section 6 – Term and Termination. 123 | 124 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 125 | 126 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 127 | 128 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 129 | 130 | 2. upon express reinstatement by the Licensor. 131 | 132 | c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 133 | 134 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 135 | 136 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 137 | 138 | Section 7 – Other Terms and Conditions. 139 | 140 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 141 | 142 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 143 | 144 | Section 8 – Interpretation. 145 | 146 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 147 | 148 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 149 | 150 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 151 | 152 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 153 | -------------------------------------------------------------------------------- /LICENSE-SAMPLECODE: -------------------------------------------------------------------------------- 1 | Copyright 2018 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 this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | 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 IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /LICENSE-SUMMARY: -------------------------------------------------------------------------------- 1 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | The documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International License. See the LICENSE file. 4 | 5 | The sample code within this documentation is made available under a modified MIT license. See the LICENSE-SAMPLECODE file. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serverless Security Workshop 2 | 3 | 4 | > **WARNING**: The purpose of the workshop is to provide a starter API which **does NOT follow many security best practices** on purpose. The tutorial modules guide you to identify security gaps in the starter app, and implement protection measures for them. 5 | > 6 | > Furthermore, the modules **do not cover ALL** the security measures that should be applied. After completing all modules, we recommend you to explore additional protections, such as ensuring the principle of least privilege. See the **Extra Credit** section for more details. 7 | 8 | 9 | In this workshop, you will learn techniques to secure a serverless application built with AWS Lambda, Amazon API Gateway and RDS Aurora. We will cover AWS services and features you can leverage to improve the security of a serverless applications in 5 domains: 10 | 11 | 1. identity & access management 12 | 1. infrastructure 13 | 1. data 14 | 1. code 15 | 1. logging & monitoring 16 | 17 | ## Getting Started 18 | 19 | Workshop URL: [serverless-security-workshop](https://catalog.us-east-1.prod.workshops.aws/workshops/026f84fd-f589-4a59-a4d1-81dc543fcd30) 20 | 21 | ## License Summary 22 | 23 | The documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International License. See the LICENSE file. 24 | 25 | The sample 26 | within this documentation is made available under a modified MIT license. See the LICENSE-SAMPLECODE file. 27 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Cloud9 Bootstrap Script 4 | # updated 12/6/2022 5 | # Tested on Amazon Linux 2 6 | # Checks for AWS Event or Cloudformation setup 7 | # 1. Installs JQ 8 | # 2. Creates Environment Variables 9 | # 3. NPM Installs and Deploys Application 10 | # 11 | # Usually takes less than one minute to complete 12 | 13 | set -euxo pipefail 14 | 15 | RED='\033[0;31m' 16 | YELLOW='\033[1;33m' 17 | NC='\033[0m' 18 | 19 | function _logger() { 20 | echo -e "$(date) ${YELLOW}[*] $@ ${NC}" 21 | } 22 | 23 | 24 | function install_utility_tools() { 25 | _logger "[+] Installing jq" 26 | sudo yum install -y jq 27 | } 28 | 29 | function setstackname() { 30 | _logger "[+] Setting StackName" 31 | export stack_name=$(aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE --query 'StackSummaries[].StackName'| grep 'mod\|"Secure-Serverless"' | sed 's/[",\,]//g') 32 | 33 | if [ "$stack_name" = "" ]; 34 | then 35 | echo "Stack Set missing. Check out running the stack set in the instructions." 36 | exit 0 37 | else 38 | echo $stack_name 39 | fi 40 | } 41 | 42 | 43 | function setclustername() { 44 | _logger "[+] Setting Auora Cluster name" 45 | sed -i "s/secure-aurora-cluster.cluster-xxxxxxx.xxxxxxx.rds.amazonaws.com/$AuroraEndpoint/g" /Workshop/src/app/dbUtils.js 46 | } 47 | 48 | function setregion() { 49 | _logger "[+] Setting region" 50 | #echo export REGION=$(aws ec2 describe-availability-zones --output text --query 'AvailabilityZones[0].[RegionName]') >> ~/.bashrc 51 | ##echo export "REGION=$(curl --silent http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)" >> ~/.bashrc 52 | export "REGION=$(aws ec2 describe-availability-zones --output text --query 'AvailabilityZones[0].[RegionName]')" >> ~/.bashrc 53 | echo "REGION=$(aws ec2 describe-availability-zones --output text --query 'AvailabilityZones[0].[RegionName]')" >>/Workshop/scratch.txt 54 | } 55 | 56 | function checkfile(){ 57 | #check for file 58 | export FILE=/Workshop/src/app/dbUtils.js 59 | if [ -f $FILE ]; 60 | then 61 | echo "Files cloned from Git!" 62 | else 63 | echo "Missing files. Please be sure to clone the file from Git: git clone https://github.com/aws-samples/aws-serverless-security-workshop.git" 64 | exit 0 65 | fi 66 | } 67 | 68 | function setcfoutput() { 69 | 70 | # load outputs to env vars 71 | _logger "[+] get Cloudformation outputs and set variables" 72 | for output in $(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[].OutputKey' --output text) 73 | do 74 | export $output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text) 75 | echo "$output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text)" >> ~/.bashrc 76 | echo "$output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text)" >> /Workshop/scratch.txt 77 | #eval "echo $output : \"\$$output\"" 78 | done 79 | 80 | } 81 | 82 | function deployapp() { 83 | _logger "[+] Deploying app" 84 | cd /Workshop/src/app 85 | export UV_USE_IO_URING=0 86 | npm install 87 | cd /Workshop/src 88 | sam deploy --stack-name CustomizeUnicorns --s3-bucket $DeploymentS3Bucket --region $REGION --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND || true 89 | cd /Workshop/ 90 | 91 | } 92 | 93 | function getapiurl(){ 94 | sam_stack_name="CustomizeUnicorns" 95 | echo " " >> /Workshop/scratch.txt 96 | echo "-------------------------------------------" >> /Workshop/scratch.txt 97 | echo "API Gateway URL:" >> /Workshop/scratch.txt 98 | echo "$(aws cloudformation describe-stacks --stack-name $sam_stack_name --query 'Stacks[].Outputs[].OutputValue' --output text)" >> /Workshop/scratch.txt 99 | 100 | } 101 | 102 | function main() { 103 | install_utility_tools 104 | checkfile 105 | setstackname 106 | setcfoutput 107 | setclustername 108 | setregion 109 | deployapp 110 | getapiurl 111 | 112 | exec ${SHELL} 113 | } 114 | 115 | main -------------------------------------------------------------------------------- /docs/00-initial-setup/images/00-base-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/00-base-architecture.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/00-ee-cloudformation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/00-ee-cloudformation.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/00-event-engine-console-login-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/00-event-engine-console-login-2.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/00-event-engine-console-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/00-event-engine-console-login.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/00-event-engine-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/00-event-engine-login.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/03-cloudformation-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/03-cloudformation-output.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0A-ee-cloudformation-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0A-ee-cloudformation-output.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0A-initial-stack-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0A-initial-stack-diagram.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-aurora-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-aurora-1.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-aurora-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-aurora-2.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-aurora-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-aurora-endpoint.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-aurora-network-setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-aurora-network-setting.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-clone-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-clone-repo.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-cloud9-environments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-cloud9-environments.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-cloud9-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-cloud9-start.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-copy-past-scratch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-copy-past-scratch.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-create-scratch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-create-scratch.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0B-serverless-aurora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0B-serverless-aurora.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0C-cloud9-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0C-cloud9-cd.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0C-diagram-with-aurora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0C-diagram-with-aurora.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0C-open-ide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0C-open-ide.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0D-aws-resource-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0D-aws-resource-bar.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0D-cloudformation-output-w-bucket-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0D-cloudformation-output-w-bucket-highlight.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0D-db-endpoint-in-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0D-db-endpoint-in-code.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0D-review-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0D-review-code.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0D-run-apigateway-local.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0D-run-apigateway-local.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0D-vulnerability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0D-vulnerability.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0E-get-endpoint-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0E-get-endpoint-output.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0E-sam-local-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0E-sam-local-result.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0E-test-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0E-test-browser.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0E-unsaved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0E-unsaved.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-copy-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-copy-bucket.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-import-postman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-import-postman.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-postman-after-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-postman-after-import.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-postman-environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-postman-environment.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-postman-manage-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-postman-manage-env.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-postman-test-get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-postman-test-get.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0F-select-dev-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0F-select-dev-env.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/0a-cloudformation-output-with-aurora-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/0a-cloudformation-output-with-aurora-endpoint.png -------------------------------------------------------------------------------- /docs/00-initial-setup/images/cfn-launch-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/00-initial-setup/images/cfn-launch-stack.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/10-high-level-architecture-w-auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/10-high-level-architecture-w-auth.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/10-oauth-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/10-oauth-flow.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1A-cognito-domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1A-cognito-domain.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1C-custom-auth-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1C-custom-auth-workflow.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1C-id-mapping-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1C-id-mapping-diagram.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1C-verify-API-authorizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1C-verify-API-authorizer.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1D-admin-clientID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1D-admin-clientID.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1D-cognito-admin-oauth-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1D-cognito-admin-oauth-scope.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1E-create-customization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1E-create-customization.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1E-postman-add-auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1E-postman-add-auth.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1E-postman-gettoken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1E-postman-gettoken.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1E-postman-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1E-postman-header.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1E-register-partner-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1E-register-partner-success.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/1F-get-token-for-company.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/1F-get-token-for-company.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/cognito-add-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/cognito-add-admin.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/cognito-add-custom-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/cognito-add-custom-scope.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/cognito-admin-oauth-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/cognito-admin-oauth-scope.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/postman-auth-get-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/postman-auth-get-token.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/postman-no-auth-rejected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/postman-no-auth-rejected.png -------------------------------------------------------------------------------- /docs/01-add-authentication/images/postman-use-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/01-add-authentication/images/postman-use-token.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/README.md: -------------------------------------------------------------------------------- 1 | # Module 2: Securely storing our database credentials with AWS Secrets Manager 2 | 3 | Hardcoding database's credentials and connection information is not a best practice. Not only from a security point of view but also operational. Any data breach within your code could expose these secrets and expose your business critical data. You also risk accidentally making your credentials public if you check in your code into a public repo or a private repo that can be accessed by a wide range of people. 4 | 5 | From an operational perspective, when deploying your code between different stages in your CI/CD pipelines, hardcoded values make it difficult to automate and might require manual intervention slowing down your development process. 6 | 7 | During this section we will use AWS Secrets Manager to handle our database credentials for us. Besides keeping your database safe even if people get access to read your code, AWS Secrets Manager also integrates with RDS and will automatically handle password rotations. 8 | 9 | > Once you decide to do this module, you won't be able to use Cloud9 Local testing as we haven't configured the correct permissions to test locally this functionality. 10 | 11 | ## Module 2A: Create a secret in AWS Secrets Manager 12 | 13 | First thing we need to do is create a secret in Secrets Manager. 14 | 15 | 1. Go to your AWS Console to AWS Secrets Manager. 16 | ![AWS Secrets Manager Console](images/00-secrets-manager.png) 17 | 1. Click on *Store a new secret*. 18 | 2. Select ***Credentials for RDS database*** type of secret. Fill it with these values: 19 | - Username: `admin` 20 | - Password: `Corp123!` 21 | - Select the encryption key: `DefaultEncryptionKey`. 22 | ![AWS Secrets Manager - Secret](images/01-store-new-secret.png) 23 | - Select your Aurora cluster (starts with `secure-serverless-aurora`) 24 | 25 | 26 | 27 | 1. Click on *Next* and continue fill the wizard with the following values. 28 | - Secret name: `secure-serverless-db-secret` 29 | - Description: Use an optional description here. 30 | ![Secret name](images/03-secret-name.png) 31 | 1. Again, click on *Next* and configure your rotation. 32 | - Click on `Enable Rotation` 33 | - Select `30 Days` as the rotation interval. 34 | - Choose **Create a new Lambda Function to perform rotation** 35 | - Give the lambda function a name, e.g. `aurora-rotation` 36 | - Select **Use this secret** 37 | ![Rotation](images/04-rotation.png) 38 | 1. Then, click *Next* and, if you want, review the example code. During the next sections we will modify our code to use Secrets Manager and this code will be used as an example. 39 | 1. Finally, click *Store*. 40 | 41 | > Be careful if you are using Firefox or Chrome extensions when performing these steps! Some extensions like *LastPass* might change the values entered before. 42 | > 43 | > You can review and verify the values after you create the secret by clicking **Retrieve secret value** 44 | > 45 | > ![](images/2A-verify-secret.png) 46 | 47 | ## Module 2B: Add permission to Lambda function to read from secrets manager 48 | 49 | We need to modify the execution policy on the lambda functions, so they areallowed to make API calls to Secrets Manager. 50 | 51 | In `src/template.yaml`, look for the block below that defines policies for Secrets Manager (**You should find a total 3 occurrences**) and uncomment them. 52 | 53 | ```yaml 54 | # - Version: '2012-10-17' 55 | # Statement: 56 | # - Effect: Allow 57 | # Action: 58 | # - "secretsmanager:GetSecretValue" 59 | # Resource: "*" 60 | 61 | ``` 62 | 63 | 64 | ⚠ **Note: ENSURE YOU REPLACE ALL 3 OCCURRENCES**! ⚠ 65 | 66 | Also, note that in the **Globals** section we are referencing the name of the secret 67 | 68 | ``` 69 | Globals: 70 | Function: 71 | Timeout: 30 72 | Environment: 73 | Variables: 74 | SECRET_NAME: secure-serverless-db-secret # name of the RDS credentials in secrets manager 75 | 76 | ``` 77 | 78 | ## Module 2C: Modify your code to use the secret 79 | 80 | Once you have created the secret, you will have to modify the application code to use Secrets Manager. Go to the file `src/app/dbUtils.js`. Here is where the connection information is stored. 81 | 82 | At the beginning of the file, add the following lines to create the required variables to use AWS Secrets Manager. You can add them just after the line `const PARTNER_COMPANY_TABLE = "Companies";`. 83 | 84 | ```javascript 85 | // Load the AWS SDK 86 | const AWS = require('aws-sdk'); 87 | const secretName = process.env.SECRET_NAME; 88 | var secret; 89 | 90 | // Create a Secrets Manager client 91 | const client = new AWS.SecretsManager(); 92 | ``` 93 | 94 | Taking a look at this code you might notice that we are using Environment Variables. To see where these variables are defined, go to `template.yaml` and check on *Global*. These have been defined from the beginning. Verify that they follow the previous step work. 95 | 96 | Now, it's time to modify how we set the configuration of our connection to the database. The method that does this is called **getDbConfig** within *dbUtils.js*. 97 | This method returns a [promise](http://google.com) resolving with the JSON parameters. 98 | 99 | ```javascript 100 | resolve({ 101 | host: host, 102 | user: "admin", 103 | password: "Corp123!", 104 | database: "unicorn_customization", 105 | multipleStatements: true 106 | }); 107 | 108 | ``` 109 | 110 | Replace the lines above with the following code: 111 | 112 | ```javascript 113 | client.getSecretValue({SecretId: secretName}, function (err, data) { 114 | if (err) { 115 | console.error(err); 116 | if (err.code === 'ResourceNotFoundException') 117 | reject("The requested secret " + secretName + " was not found"); 118 | else if (err.code === 'InvalidRequestException') 119 | reject("The request was invalid due to: " + err.message); 120 | else if (err.code === 'InvalidParameterException') 121 | reject("The request had invalid params: " + err.message); 122 | else 123 | reject(err.message); 124 | } 125 | else { 126 | if (data.SecretString !== "") { 127 | secret = data.SecretString; 128 | resolve({ 129 | host: JSON.parse(secret).host, 130 | user: JSON.parse(secret).username, 131 | password: JSON.parse(secret).password, 132 | database: "unicorn_customization", 133 | multipleStatements: true 134 | }); 135 | } else { 136 | reject("Cannot parse DB credentials from secrets manager."); 137 | } 138 | } 139 | }); 140 | 141 | ``` 142 | Here is an example of how the method should look after the changes. 143 | 144 | ![dbConfig.jsChanged](images/06-dbConfig-changed.png) 145 | 146 | If you read the code closely, you will see that is gathering the secrets from AWS Secrets Manager service and use them to resolve the promise with the values returned by the service. 147 | 148 | ## Module 2D: Deploy and test 149 | 150 | Now it's time to deploy again and test the application: 151 | 152 | 1. Validate the template: 153 | 154 | ``` 155 | sam validate -t template.yaml 156 | ``` 157 | 158 | 1. Deploy the updates by running: 159 | 160 | ``` 161 | aws cloudformation package --output-template-file packaged.yaml --template-file template.yaml --s3-bucket $BUCKET --s3-prefix securityworkshop --region $REGION && aws cloudformation deploy --template-file packaged.yaml --stack-name CustomizeUnicorns --region $REGION --capabilities CAPABILITY_IAM --parameter-overrides InitResourceStack=Secure-Serverless 162 | ``` 163 | 164 | 165 | 1. Test the API using postman. You can test it with whatever method in the API such as `/socks`, `/horns`... 166 | 167 | ## Extra credit 168 | 169 | **Cache the secrets:** 170 | The code currently makes a call to Secrets Manager to retrieve the secret every time the Lambda function gets invoked. This may generate lots of API calls to Secrets Manager when your API is being invoked frequently. 171 | To reduce network traffic to Secrets Manager, you can take advantage of [Execution Context reuse](https://aws.amazon.com/blogs/compute/container-reuse-in-lambda/) and cache the secret value. 172 | 173 | ## Next Step 174 | 175 | Return to the workshop [landing page](../../README.md) to pick another module. 176 | -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/00-secrets-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/00-secrets-manager.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/01-store-new-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/01-store-new-secret.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/02-secret-select-db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/02-secret-select-db.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/03-secret-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/03-secret-name.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/04-rotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/04-rotation.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/05-example-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/05-example-code.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/06-dbConfig-changed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/06-dbConfig-changed.png -------------------------------------------------------------------------------- /docs/02-add-secrets-manager/images/2A-verify-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/02-add-secrets-manager/images/2A-verify-secret.png -------------------------------------------------------------------------------- /docs/03-input-validation/README.md: -------------------------------------------------------------------------------- 1 | # Module 3: Input validation on API Gateway 2 | 3 | A quote from the OWASP website: 4 | 5 | > "*The most common web application security weakness is the failure to properly validate input from the client or environment*." 6 | > 7 | > --- [**OWASP** (The Open Web Application Security Project)](https://www.owasp.org/index.php/Data_Validation) 8 | 9 | A great [XKCD comic](https://xkcd.com/327/) demonstrate this point well: 10 | 11 | ![xkcd exploits_of_a_mom ](https://imgs.xkcd.com/comics/exploits_of_a_mom.png) 12 | 13 | You can configure API Gateway to perform basic validation of an API request before proceeding with the integration request. When the validation fails, API Gateway immediately fails the request, returns a 400 error response to the caller, and publishes the validation results in CloudWatch Logs. This reduces unnecessary calls to the backend. More importantly, it lets you focus on the validation efforts specific to your application. 14 | 15 | For example, in our application, when defining an customization, we have to be sure that our new customization should have: 16 | 17 | - A **name** for the customization object 18 | - An url for the cape's **image** . 19 | - A type of **socks** for our unicorn specified by an id. 20 | - A specific id for the **horn** to use. 21 | - An id for the pair of **glasses**. 22 | - A type of **cape** by id. 23 | 24 | This information should be in our request body to create a new customization that follows specific patterns. E.g. the imageUrl should be a valid URL, the IDs for socks and horns are numeric values. 25 | 26 | By leveraging input validation on API Gateway, you can enforce required parameters and regex patterns each parameter must adhere to. This allows you to remove boilerplate validation logic from backend implementations and focus on actual business logic and deep validation. 27 | 28 | ## Module 3 - Optional: attack your API with SQL injection! 29 | 30 | If you haven't completed **Module 6: WAF**, your serverless API is currently vulnerable to SQL injection attacks. This optional module shows how you can perform the attack. 31 | 32 |
33 | Click to expand for optional step instructions 34 | 35 | 36 | If you look at our lambda function code right now, no input validation is being performed, and with the below line specified as part of our mysql client setting (under `/src/app/dbUtils.js`): 37 | 38 | ``` 39 | multipleStatements: true 40 | 41 | ``` 42 | 43 | > **Note**: As a best practice you should set the `multipleStatements` option to false in your code (the nodejs mysql client actually defaults it false). However, this is not disabled by default in all programming languages/libraries, so we enabled it in our starter code for you to see the easiness of this attack. 44 | 45 | we can easily embed SQL statements in the body of the request to get executed. For example, in the body of the POST customizations/ API, try using the below: 46 | 47 |
48 | If you have done module 1, use sample input here 49 | 50 | ``` 51 | { 52 | "name":"Orange-themed unicorn", 53 | "imageUrl":"https://en.wikipedia.org/wiki/Orange_(fruit)#/media/File:Orange-Whole-%26-Split.jpg", 54 | "sock":"1", 55 | "horn":"2", 56 | "glasses":"3", 57 | "cape":"2); INSERT INTO Socks (NAME,PRICE) VALUES ('Bad color', 10000.00" 58 | } 59 | ``` 60 | 61 |
62 | 63 |
64 | If you have not done module 1, use sample input here 65 | 66 | ``` 67 | { 68 | "name":"Orange-themed unicorn", 69 | "imageUrl":"https://en.wikipedia.org/wiki/Orange_(fruit)#/media/File:Orange-Whole-%26-Split.jpg", 70 | "sock":"1", 71 | "horn":"2", 72 | "glasses":"3", 73 | "cape":"2); INSERT INTO Socks (NAME,PRICE) VALUES ('Bad color', 10000.00", 74 | "company":"1" 75 | } 76 | ``` 77 | 78 |
79 | 80 | 81 | ![](images/3A-injection.png) 82 | 83 | Send the request using Postman. If the request succeeds, you have now just performed a SQL injection attack! 84 | 85 | If you look at the SQL injection statement we just performed, it's adding a bad value into the `Socks` table. We can verify that took effect by running the **GET /socks** API: 86 | 87 | ![](images/3A-after-injection.png) 88 | 89 |
90 | 91 | ## Module 3A: Create a model for your Customizations 92 | 93 | In API Gateway, a [**model**](https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html#models-mappings-models) defines the data structure of a payload, using the [JSON schema draft 4](https://tools.ietf.org/html/draft-zyp-json-schema-04). 94 | 95 | When we define our model, we can ensure that the parameters we are receiving are in the format we are expecting. Furthermore, you can check them against regex expressions. A good tool to test if your regex is correct is [regexr.com](https://regexr.com/). 96 | 97 | For our **POST /customizations** API, we are going to use the following model: 98 | 99 | ```json 100 | { 101 | "title": "Customizations", 102 | "$schema": "http://json-schema.org/draft-04/schema#", 103 | "type": "object", 104 | "required": [ 105 | "imageUrl", 106 | "sock", 107 | "horn", 108 | "glasses", 109 | "cape", 110 | "name" 111 | ], 112 | "properties": { 113 | "imageUrl": { 114 | "type": "string", 115 | "title": "The Imageurl Schema", 116 | "pattern": "^https?:\/\/[-a-zA-Z0-9@:%_+.~#?&//=]+$" 117 | }, 118 | "name": { 119 | "type": "string", 120 | "title": "The name Schema", 121 | "pattern": "^[a-zA-Z0-9- ]+$" 122 | }, 123 | "sock": { 124 | "type": "string", 125 | "title": "The Sock Schema", 126 | "pattern": "^[0-9]*$" 127 | }, 128 | "horn": { 129 | "type": "string", 130 | "title": "The Horn Schema", 131 | "pattern": "^[0-9]*$" 132 | }, 133 | "glasses": { 134 | "type": "string", 135 | "title": "The Glasses Schema", 136 | "pattern": "^[0-9]*$" 137 | }, 138 | "cape": { 139 | "type": "string", 140 | "title": "The Cape Schema", 141 | "pattern": "^[0-9]*$" 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | Now, follow these steps: 148 | 149 | 1. Go to API Gateway console. 150 | 2. Click on the API **CustomizeUnicorns** 151 | 3. Click on **Models** 152 | 4. Click on **Create** and create a model with the following values: 153 | - Model name: `CustomizationPost` 154 | - Content type: `application/json` 155 | 1. In the model schema, use the one provided before (the *json* before this section). 156 | 1. Once everything is filled, click on **Create model**. 157 | 158 | ![Create model](images/06_api_model.png) 159 | 160 | Once we have created our model, we need to apply it to our customizations/post method. 161 | 162 | 1. Within the API Gateway Console, click on CustomizeUnicorns, **Resources** 163 | 1. Click under /customizations --> **POST** method 164 | 165 | ![Customizations ](images/06_customizations.png) 166 | 1. Click on **Method Request** 167 | 1. Under **Request Validator**, click on the pencil to edit it. Select **Validate Body**. Then, click on the tick to confirm the change. 168 | 1. Under **Request Body**, click on **Add model** with the following values: 169 | - Content type: `application/json` 170 | - Model name: `CustomizationPost` 171 | 1. Click to the tick to confirm. 172 | 173 | ![Method Execution](images/06_method_execution.png) 174 | 175 | > On step number 2 you might have noticed that we can also validate query parameters and request headers in addition to request body. This is really useful when our application uses both at the same time and we want to have complex validations. If you want to find more information, [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-request-validation.html) is our documentation about this. 176 | 177 | 1. Now it's time to deploy and test! Go to the **Actions** menu and click on **Deploy API**. Select `dev` as the **Deployment stage** and confirm by clicking **Deploy**. 178 | 179 | ## Module 3B: Test your Validation 180 | 181 | Use postman, you can try making requests to the **POST /customizations** API using invalid parameters and see the input validation kick in (if you get an unauthorize error message, could be caused by the expiration time of the Authentication token. You can easily refresh rthis token following these steps from module [01](../01-add-authentication/README.md#1E): 182 | 183 | ### Wrong parameters = Invalid request: 184 | 185 | Here are some example request bodies that fail: 186 | 187 | * Missing fields: 188 | 189 | ```javascript 190 | { 191 | "name":"Cherry-themed unicorn", 192 | "imageUrl":"https://en.wikipedia.org/wiki/Cherry#/media/File:Cherry_Stella444.jpg", 193 | "glasses": "3", 194 | "cape": "4" 195 | } 196 | ``` 197 | 198 | * The `imageUrl` not a valid URL: 199 | 200 | ```javascript 201 | { 202 | "name":"Cherry-themed unicorn", 203 | "imageUrl":"htt://en.wikipedia.org/wiki/Cherry#/media/File:Cherry_Stella444.jpg", 204 | "sock": "1" , 205 | "horn": "2" , 206 | "glasses": "3", 207 | "cape": "4" 208 | } 209 | ``` 210 | 211 | * The `cape ` parameter not a number (SQL injection attempt) 212 | 213 | ```javascript 214 | { 215 | "name":"Orange-themed unicorn", 216 | "imageUrl":"https://en.wikipedia.org/wiki/Orange_(fruit)#/media/File:Orange-Whole-%26-Split.jpg", 217 | "sock": "1", 218 | "horn": "2", 219 | "glasses": "3", 220 | "cape":"2); INSERT INTO Socks (NAME,PRICE) VALUES ('Bad color', 10000.00" 221 | } 222 | 223 | ``` 224 | 225 | 226 | You should get a 400 Bad Request response: 227 | 228 | ```javascript 229 | {"message": "Invalid request body"} 230 | ``` 231 | 232 | 233 | ### Correct parameters 234 | 235 | Testing the **POST /customizations** API with right parameters: 236 | 237 |
238 | If you have done module 1, use sample input here 239 | 240 | ```javascript 241 | { 242 | "name":"Cherry-themed unicorn", 243 | "imageUrl":"https://en.wikipedia.org/wiki/Cherry#/media/File:Cherry_Stella444.jpg", 244 | "sock": "1", 245 | "horn": "2", 246 | "glasses": "3", 247 | "cape": "4" 248 | } 249 | ``` 250 |
251 | 252 |
253 | If you have not done module 1, use sample input here 254 | 255 | ```javascript 256 | { 257 | "name":"Cherry-themed unicorn", 258 | "imageUrl":"https://en.wikipedia.org/wiki/Cherry#/media/File:Cherry_Stella444.jpg", 259 | "sock": "1", 260 | "horn": "2", 261 | "glasses": "3", 262 | "cape": "4", 263 | "company" : "1" 264 | } 265 | ``` 266 |
267 | 268 | 269 | The result should be: 270 | 271 | ```bash 272 | {"customUnicornId":} 273 | ``` 274 | 275 | ## Additional input validation options 276 | 277 | As you have now seen, API Gateway input validation gives you basic features such as type checks and regex matching. In a production application, this is often not enough and you may have additional constraints on the API input. 278 | 279 | To gain further protection, you should consider using the below in addition to the input validation features from API Gateway: 280 | 281 | * Add an AWS WAF ACL to your API Gateway - check out [**Module 6**](../06-waf/) 282 | * Add further input validation logic in your lambda function code itself 283 | 284 | 285 | 286 | ## Extra credit 287 | 288 | There is, at least, one more method that needs to be validated. Build your own json schema for that method and apply the same steps mentioned before and you should be able to validate these methods as well! 289 | 290 |
291 | Hint: In case you need some help, here is the model to be used: 292 | 293 | ```json 294 | { 295 | "title": "PartnerPOST", 296 | "$schema": "http://json-schema.org/draft-04/schema#", 297 | "type": "object", 298 | "required": [ 299 | "name" 300 | ], 301 | "properties": { 302 | "name": { 303 | "type": "string", 304 | "title": "Partner Schema", 305 | "pattern": "^[a-zA-Z0-9- ]+$" 306 | } 307 | } 308 | } 309 | ``` 310 |
311 | 312 | ## Next step 313 | You have now added basic input validation to your API and further reduced the risk of attackers using bad inputs to sabotage your API! 314 | 315 | Return to the workshop [landing page](../../README.md) to pick another module. 316 | -------------------------------------------------------------------------------- /docs/03-input-validation/images/06_api_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/03-input-validation/images/06_api_model.png -------------------------------------------------------------------------------- /docs/03-input-validation/images/06_customizations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/03-input-validation/images/06_customizations.png -------------------------------------------------------------------------------- /docs/03-input-validation/images/06_method_execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/03-input-validation/images/06_method_execution.png -------------------------------------------------------------------------------- /docs/03-input-validation/images/3A-after-injection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/03-input-validation/images/3A-after-injection.png -------------------------------------------------------------------------------- /docs/03-input-validation/images/3A-injection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/03-input-validation/images/3A-injection.png -------------------------------------------------------------------------------- /docs/03-input-validation/images/3B_invalid_request_postman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/03-input-validation/images/3B_invalid_request_postman.png -------------------------------------------------------------------------------- /docs/04-ssl-in-transit/README.md: -------------------------------------------------------------------------------- 1 | # Module 4: Use SSL in-transit for your DB connections 2 | 3 | Although we are using VPC and traffic is private within it, some regulations or compliance requirements might require encryption in transit. This encryption secures the data when communicating with the database. 4 | 5 | Go to *dbUtils.js* to add a new property to your database connection. Under the method ***getDbConfig***, within the resolve object (a JSON object), add a new line to the JSON: 6 | 7 | ``` 8 | ssl: "Amazon RDS", 9 | 10 | ``` 11 | The resolve should be like this: 12 | 13 |
14 | If you haven't gone through AWS Secrets Manager step

15 | 16 | ```javascript 17 | resolve({ 18 | ssl: "Amazon RDS", 19 | host: host, 20 | user: "admin", 21 | password: "Corp123!", 22 | database: "unicorn_customization", 23 | multipleStatements: true 24 | }); 25 | ``` 26 |

27 | 28 |
29 | If you have gone through AWS Secrets Manager step

30 | 31 | ```javascript 32 | client.getSecretValue({SecretId: secretName}, function (err, data) { 33 | if (err) { 34 | console.error(err); 35 | if (err.code === 'ResourceNotFoundException') 36 | reject("The requested secret " + secretName + " was not found"); 37 | else if (err.code === 'InvalidRequestException') 38 | reject("The request was invalid due to: " + err.message); 39 | else if (err.code === 'InvalidParameterException') 40 | reject("The request had invalid params: " + err.message); 41 | else 42 | reject(err.message); 43 | } 44 | else { 45 | if (data.SecretString !== "") { 46 | secret = data.SecretString; 47 | resolve({ 48 | ssl: "Amazon RDS", 49 | host: JSON.parse(secret).host, 50 | user: JSON.parse(secret).username, 51 | password: JSON.parse(secret).password, 52 | database: "unicorn_customization", 53 | multipleStatements: true 54 | }); 55 | } else { 56 | reject("Cannot parse DB credentials from secrets manager."); 57 | } 58 | } 59 | }); 60 | ``` 61 |

62 | 63 | Finally, deploy these changes: 64 | 65 | ```bash 66 | cd ~/environment/aws-serverless-security-workshop/src 67 | aws cloudformation package --output-template-file packaged.yaml --template-file template.yaml --s3-bucket $BUCKET --s3-prefix securityworkshop --region $REGION && aws cloudformation deploy --template-file packaged.yaml --stack-name CustomizeUnicorns --region $REGION --capabilities CAPABILITY_IAM --parameter-overrides InitResourceStack=Secure-Serverless 68 | ``` 69 | 70 | Once this is done, you should be able to connect to the database using SSL. 71 | 72 | ## Ensure SSL - Optional step 73 | 74 | You can require SSL connections for specific users accounts\. For example, you can use one of the following statements, depending on your MySQL version, to require SSL connections on the user account `encrypted_user`\. 75 | 76 | For MySQL 5\.7 and later: 77 | 78 | ``` 79 | ALTER USER 'encrypted_user'@'%' REQUIRE SSL; 80 | ``` 81 | 82 | For MySQL 5\.6 and earlier: 83 | 84 | ``` 85 | GRANT USAGE ON *.* TO 'encrypted_user'@'%' REQUIRE SSL; 86 | ``` 87 | 88 | For more information on SSL connections with MySQL, go to the [MySQL documentation](https://dev.mysql.com/doc/refman/5.6/en/secure-connections.html)\. 89 | 90 | To find the MySQL version of the Aurora database, go to the RDS console and find the **Engine version** under **Configuration** tab of the database cluster: 91 | 92 | ![](images/check-engine-version.png) 93 | 94 | ## Next step 95 | You have now further secured your data by enabling encryption in transit for your database connection! 96 | 97 | Return to the workshop [landing page](../../README.md) to pick another module. 98 | -------------------------------------------------------------------------------- /docs/04-ssl-in-transit/images/check-engine-version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/04-ssl-in-transit/images/check-engine-version.png -------------------------------------------------------------------------------- /docs/05-usage-plan/README.md: -------------------------------------------------------------------------------- 1 | # Module 5: Usage Plan 2 | 3 | You can leverage Usage Plans with Amazon API Gateway to set limits on request rate for consumers of your API to protect it from being abused by a particular misbehaving client. 4 | 5 | To tally the number of requests based on the caller, API Gateway uses API Keys to keep track of different consumers for your API. In our use case, requests coming from different companies can be calculated separately. 6 | 7 | ## Module 5A: Create an API Gateway usage plan 8 | 1. In the API Gateway console, go to **Usage Plans** tab, and click **Create** 9 | 1. Fill in the details for the usage plan 10 | 11 | * **Name**: ```Basic``` 12 | * **Description** : ```Basic usage plan for Unicorn customization partners``` 13 | * **Enable throttling**: check yes 14 | * **Throttling Rate** : ```1``` request per second 15 | * **Throttling Burst** : 1 16 | * **Enable Quota**: check yes and use ```100``` requests per ```month``` 17 | 18 | ![Create Usage Plan screenshot](images/create-usage-plan.png) 19 | 20 | Click **Next** 21 | 22 | 1. Associate the API we created previously with the usage plan. Pick `dev` stage. 23 | 24 | ![add stage to Usage plan](images/add-apig-stage.png) 25 | 26 | > The warning sign is expected because we haven't yet configured the API to require API Keys. This will be done in a later steps. 27 | 28 | 1. Click the checkmark to confirm. Then click **Next** 29 | 30 | ![add stage to Usage plan](images/5A-add-stage-to-plan.png) 31 | 32 | 33 | 1. We currently don't have any API keys set up. In this step, click **Create API Key and add to Usage Plan** to create an API key for the partner company 34 | 35 |
36 | If you have not done module 1, expand for instructions here 37 | 38 | * For Name, pick any name e.g. `cherry company`. 39 | * For API Key, select **Auto Generate** 40 | * Click **Save** 41 | 42 | 43 | 44 |
45 | 46 |
47 | If you have done module 1, expand for instructions here 48 | 49 | For our application, we are going to reuse the value of the ClientID of the customer as the value for the API Key, to keep down the number of random strings that customers have to remember. 50 | 51 | * For Name, use the company name you created in **Module 1: Auth**. 52 | * For API Key, select **Custom** so we can import the value 53 | * In the inputbox that comes up, use the same value as the ClientID of the company (if you forgot it, you can retrieve it from the Cognito console and look under **App clients** tab 54 | * Click **Save** 55 | 56 | ![](images/5A-create-API-key.png) 57 | 58 |
59 | 60 | 61 | 62 | 1. After the API key has been created, click **Done**. 63 | 64 | ![](images/5A-API-key-created.png) 65 | 66 | ## Module 5B: Update API Gateway to enforce API keys 67 | 68 | Now, we need to modify our API gateway so requests must have an API key present. 69 | 70 | 71 |
72 | If you have done module 1, expand for instructions here 73 | 74 | 75 | 1. In the API swagger definition in `template.yaml`, add the below lines to add an additional type of AWS security: 76 | 77 | ```yaml 78 | ApiKey: 79 | type: apiKey 80 | name: x-api-key 81 | in: header 82 | ``` 83 | 84 | 85 | 86 | 1. Next, for the APIs in the Swagger template for customizing unicorns and listing customization options (leave out the `/partners` APIs for now), add the below 87 | 88 | ```yaml 89 | - ApiKey: [] 90 | ``` 91 | to the `security` section in each API: 92 | 93 | 94 | 95 |
96 | 97 |
98 | If you have not done module 1, expand for instructions here 99 | 100 | 101 | 1. In the API swagger definition in `template.yaml`, find the line: 102 | 103 | ``` 104 | ### TODO: add authorizer 105 | ``` 106 | 107 | add the following lines below that: 108 | 109 | ```yaml 110 | securityDefinitions: 111 | ApiKey: 112 | type: apiKey 113 | name: x-api-key 114 | in: header 115 | ``` 116 | 117 | See screeenshot: 118 | 119 | 120 | ⚠ **Caution: Ensure the `securityDefinitions` section you pasted is at the same indentation level as `info` and `paths`** ⚠ 121 | 122 | 123 | 1. In the `paths` section of the Swagger template, change the occurrence of each of the below 124 | 125 | ```yaml 126 | # security: 127 | # - CustomAuthorizer: [] 128 | 129 | ``` 130 | 131 | into 132 | 133 | ```yaml 134 | security: 135 | - ApiKey: [] 136 | ``` 137 | 138 | See screeenshot: 139 | 140 | 141 | ⚠ **Caution: Ensure all 9 APIs are updated** ⚠ 142 | 143 |
144 | 145 | Now, deploy the changes and verify: 146 | 147 | 1. Validate the template in the terminal: 148 | 149 | ``` 150 | sam validate -t template.yaml 151 | ``` 152 | 153 | 1. Deploy the updates: 154 | 155 | ``` 156 | aws cloudformation package --output-template-file packaged.yaml --template-file template.yaml --s3-bucket $BUCKET --s3-prefix securityworkshop --region $REGION && aws cloudformation deploy --template-file packaged.yaml --stack-name CustomizeUnicorns --region $REGION --parameter-overrides InitResourceStack=Secure-Serverless --capabilities CAPABILITY_IAM 157 | ``` 158 | 159 | 1. Once the deployment completes, you can go the [API Gateway console](https://console.aws.amazon.com/apigateway/home), navigate to the **CustomizeUnicorns API** -> **Resources** --> Pick an method --> click on **Method Request**. 160 | 161 | You should now see the **API Key Required** field set to `true` 162 | 163 | 164 | 165 | 166 | ## Module 5C: Test request with API keys 167 | 168 | 1. Go back to Postman. Now the API is enforcing API keys, the request will fail if you don't include the API key header. 169 | 170 | Try sending an request using Postman like you did before. You should see the request fail with a **403 Forbidden** status code and a `{"message": "Forbidden"}` response. 171 | 172 | > If the response is **401 Unauthorized** and if you have completed module 1, most likely your access token is expired. Use Postman to request a new access token and try again. 173 | 174 | 1. You can add the API key request header by going to the **Header** tab, and put in 175 | * `x-api-key` for the header key 176 | * The value for the API Key that we added to the usage plan in module 5B: 177 | * If you have done module 1: this should be same as the Cognito app Client ID 178 | * If you have not done module 1: you can find the auto-generated API key value by going to the **API Keys** tab in the API gateway console --> click on the API key you created in module 5B --> click **Show** next to **API Key** 179 | 180 | 181 | 182 | 183 | You should now see the request go through 184 | 185 | 186 | 187 | 188 | ## Module 5D (Optional): Use the Lambda authorizer to provide the API key 189 | 190 | 191 | ⚠ **Caution: This optional module assumes you have completed Module 1** ⚠ 192 | 193 | If you have already completed module 1: to make the API consumer's life easier, instead of forcing them to add a separate `x-api-key` header to the request they are making, we can make API Gateway take the API Key from the lambda authorizer. Read more about the two sources of API keys supported by API gateway [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-key-source.html) 194 | 195 | To make this work: 196 | 197 | 1. In the API swagger definition in template.yaml, add the below lines 198 | 199 | ``` 200 | x-amazon-apigateway-api-key-source: AUTHORIZER 201 | ``` 202 | 203 | to the same level as the `securityDefinitions` or `paths` field: 204 | 205 | 206 | 207 | 1. We also need to make the Lambda authorizer return the API Key as part of the auth response. To do so, go to `authorizer/index.js`, find the following line in the code, and uncomment the second line: 208 | 209 | // Uncomment here to pass on the client ID as the api key in the auth response 210 | // authResponse.usageIdentifierKey = payload["client_id"]; 211 | 212 | 1. Validate the SAM template: 213 | 214 | ``` 215 | sam validate -t template.yaml 216 | ``` 217 | 218 | 1. Deploy the updates: 219 | 220 | ``` 221 | aws cloudformation package --output-template-file packaged.yaml --template-file template.yaml --s3-bucket $BUCKET --s3-prefix securityworkshop --region $REGION && aws cloudformation deploy --template-file packaged.yaml --stack-name CustomizeUnicorns --region $REGION --parameter-overrides InitResourceStack=Secure-Serverless --capabilities CAPABILITY_IAM 222 | ``` 223 | 224 | 1. Once the deployment finishes, test making API requests again with postman. You should now be able to remove the `x-api-key` request header and the request should be able to succeed. 225 | 226 | 227 | 228 | ## Module 5E (Optional): Test throttling behavior with postman 229 | 230 | ⚠ **Caution: This optional module assumes you have completed Module 1 and Module 5D! If you have not done those two, you would need to add the x-api-key header to each of the API in the collection first!** ⚠ 231 | 232 | You can use postman to send multiple API requests in sequence. 233 | 234 | 1. In postman, click on **Runner** 235 | 236 | 1. Pick the `List customization options` folder to run 237 | 238 | 1. Select the `dev` environment and set runner to run 10 iterations 239 | 240 | 241 | 242 | 1. In the test result, you should some requests getting throttled and receiving a 429 response: 243 | 244 | 245 | 246 | 247 | ## Extra credit 248 | 249 | If you want extra credit (karma points), here are some ideas: 250 | 251 | * Try viewing/downloading the usage data for a given client. 252 | 253 | > **Hint**: See [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-usage-plans-with-console.html#api-gateway-usage-plan-manage-usage) on some documentation 254 | 255 | * Try configure different throttling thresholds for different API methods 256 | 257 | > **Hint**: See [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html#apig-request-throttling-stage-and-method-level-limits) on some documentation 258 | 259 | ## Next Step 260 | You have now configured throttling for your API consumers using API Gateway Usage Plans! 261 | 262 | Return to the workshop [landing page](../../README.md) to pick another module. -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5A-API-key-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5A-API-key-created.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5A-add-stage-to-plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5A-add-stage-to-plan.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5A-auto-generate-API-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5A-auto-generate-API-key.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5A-create-API-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5A-create-API-key.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5B-add-API-key-to-swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5B-add-API-key-to-swagger.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5B-add-security-def-no-module-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5B-add-security-def-no-module-1.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5B-add-security-def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5B-add-security-def.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5B-confirm-usage-plan-requirement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5B-confirm-usage-plan-requirement.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5C-explicit-API-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5C-explicit-API-key.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5C-find-api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5C-find-api-key.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5D-api-source-authorizer-swagger-no-module-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5D-api-source-authorizer-swagger-no-module-1.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5D-api-source-authorizer-swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5D-api-source-authorizer-swagger.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5E-postman-throttle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5E-postman-throttle.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/5E-runner-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/5E-runner-config.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/add-apig-stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/add-apig-stage.png -------------------------------------------------------------------------------- /docs/05-usage-plan/images/create-usage-plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/05-usage-plan/images/create-usage-plan.png -------------------------------------------------------------------------------- /docs/06-waf/README.md: -------------------------------------------------------------------------------- 1 | # Module 6: WAF 2 | 3 | AWS WAF is a web application firewall that helps protect your web applications from common web exploits that could affect application availability, compromise security, or consume excessive resources. For example, you can reject requests that matches **SQL injection** and **Cross-Site Scripting (XSS)**. Additionally, you can filter web requests based on **IP address**, **geographic area**, **request size**, and/or string or **regular expression** patterns using the rules. You can put these conditions on HTTP headers or body of the request itself, allowing you to create complex rules to block attacks from specific user-agents, bad bots, or content scrapers. You can also take advantage of **Managed Rules** from **AWS Marketplace** to get immediate protections for your APIs from common threats, such as OWASP Top 10 security risks and Common Vulnerabilities and Exposures (CVE). 4 | 5 | 6 | In this module, you will create a WAF ACL and attach it to the API Gateway we created. 7 | 8 | ### Module 6 - Optional: attack your API with SQL injection! 9 | 10 | If you have completed **Module 3: Input validation on API Gateway**, your API now has some basic input validation in place for the JSON request body. However, it turns out our application is still vulnerable to SQL injection attacks as part of the request URI. This optional module shows how you can perform the attack. 11 | 12 |
13 | Click to expand for optional step instructions 14 | 15 | 16 | 1. In Postman, go to the **GET Custom_Unicorn** request. Change the request URL to include a SQL injection in the request URI: 17 | 18 | ``` 19 | {{base_url}}/customizations/1; drop table Custom_Unicorns; 20 | ``` 21 | 22 | and Click **Send**. 23 | 24 | ![screenshot](images/SQLi-attack-success.png) 25 | 26 | You may get a "`Error querying`" response back because the SQL injection messed up the database query so not all of it succeeded (you can check the Cloudwatch Logs for the **CustomizeUnicorns-CustomizeUnicornFunction** Lambda function on what SQL queries got executed). However, the injected query to drop the `Custom_Unicorns` table should have succeeded. 27 | 28 | 1. If you now try to submit some valid quests, such as LIST or POST customizations, you will now get error back, because the `Custom_Unicorns` table got dropped by our evil attack! 29 | 30 | 1. To recover from this, go to your cloud9 browser tab, connect to the database again through the mysql command line 31 | 32 | ``` 33 | cd ~/environment/aws-serverless-security-workshop/src 34 | mysql -h -u admin -p 35 | ``` 36 | 37 | If you have gone through Module 4 and set the DB to require the `admin` user to use 38 | 39 | type in the DB password (if you have gone through **Module 2: Secrets Manager**, your DB password may have been rotated by Secrets Manager. You can retrieve the new password by going to the Secrets Manager and click on the **Retrieve secret value** button 40 | 41 | 1. In the MySQL cli prompt, you can run the show tables command to verify the `Custom_Unicorns` table is gone: 42 | 43 | ``` 44 | use unicorn_customization; 45 | show tables; 46 | ``` 47 | See screenshot: 48 | 49 | ![screenshot](images/recreate-table.png) 50 | 51 | 1. Rerun the DB initialization script to recreate the `Custom_Unicorns` table: 52 | 53 | ``` 54 | drop table Capes, Glasses, Horns, Socks; 55 | source init/db/queries.sql; 56 | ``` 57 | 58 | > You should see the output includes this error message: 59 | >```ERROR 1062 (23000): Duplicate entry 'Placeholder company' for key 'NAME'``` 60 | > This is expected because we didn't want to overwrite the `company` table. You can ignore the error message 61 | 62 | 63 | 6. List the tables again to verify the `Custom_Unicorns` table is recreated. 64 | 65 | ``` 66 | show tables; 67 | ``` 68 | 69 |
70 | 71 | ### Module 6A: Create a WAF ACL 72 | 73 | Now let's start creating an AWS WAF to give us additional protection: 74 | 75 | 1. Go to the [AWS WAF Console](https://console.aws.amazon.com/wafv2/home#/wafhome) 76 | 77 | 1. The AWS WAF console has recently released a new version: see [Introducing AWS Managed Rules for AWS WAF 78 | ](https://aws.amazon.com/about-aws/whats-new/2019/11/introducing-aws-managed-rules-for-aws-waf/). However, this workshop has not been yet adapted to the new version. Therefore, we will be using the classic version of the WAF console. You can use the **Switch to AWS WAF Classic** button to switch to classic: 79 | 80 | ![](images/switch-waf-classic.png) 81 | 82 | 1. Click on **Create web ACL** on the WAF Classic console 83 | 84 | ![](images/classifc-waf-opening.png) 85 | 86 | 1. In Step 1 of the ACL creation wizard, fill in: 87 | 88 | * **Web ACL Name**: `ProtectUnicorn` 89 | * **CloudWatch metric name**: this should be automatically populated for you 90 | * **Region**: select the AWS region you chose for previous steps of the workshop 91 | * **Resource type to associate with web ACL**: Pick `API Gateway` 92 | * **Amazon API Gateway API**: Pick the API Gateway we deployed previously, `CustomizeUnicorns` 93 | * **Stage**: select `dev` 94 | 95 | ![screenshot](images/web-acl-name.png) 96 | 97 | and click **Next** 98 | 99 | ### Module 6B: Create WAF conditions 100 | 101 | 1. Next you will create 2 different conditions. Let's start with a condition to restrict the maximum size of request body: 102 | 103 | * Go to **Size constraint conditions** section, click **Create condition** 104 | * Give the condition a name, like `LargeBodyMatch` 105 | * In Filter settings, add a filer on 106 | * **Part of the request to filter on**: body 107 | * **Comparison operator**: Greater than 108 | * **Size (Bytes)**: 3000 109 | * Click **Add filter** 110 | * After the filter is added to the condition, click **Create** 111 | 112 | ![screenshot](images/large-body-condition.png) 113 | 114 | 115 | 1. Next, let's add a SQL injection condition. 116 | 117 | * Go to **SQL injection match conditions** section, click **Create condition** 118 | * Give the condition a name, like `SQLinjectionMatch` 119 | * Here, we want to add multiple rules to inspect multiple aspects of the request: request body, request URI and query strings 120 | * In the **Filter settings**, add 4 filters: 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
Part of the request to filter onTransformation
1BodyNone
2BodyURL decode
3URIURL decode
4Query stringURL decode
149 | * Click **Create** 150 | 151 | ![screenshot](images/sql-condition.png) 152 | 153 | 1. Click **Next** to advance to the **Create rules** page 154 | 155 | 156 | ### Module 6C: Create WAF rules 157 | 158 | 159 | 1. Next, we create **Rules** that are composed of one or more **Conditions**. Let's start by creating a rule based on the request body size condition: 160 | 161 | * Click **Create Rule** 162 | * Give it a name, like `LargeBodyMatchRule` 163 | * For **Rule type**, keep `Regular rule` 164 | * In Add conditions section, select 165 | * `does` 166 | * `match at least one of the filters in the size constraint condition ` 167 | * `LargeBodyMatch` -- the name of the condition we created for large request body in 6B 168 | 169 | * Then click **Create** 170 | 171 | ![screenshot](images/large-body-rule.png) 172 | 173 | 1. Next, we create the rule for SQL injection. 174 | 175 | * Click **Create Rule** 176 | * Give it a name, like `SQLinjectionRule` 177 | * For **Rule type**, keep `Regular rule` 178 | * In Add conditions section, select 179 | * `does` 180 | * `match at least one of the filters in the SQL injection match condition ` 181 | * `SQlInjectionMatch` -- the name of the condition we created for SQL injection in 6B 182 | * Then click **Create** 183 | 184 | ![screenshot](images/sql-rule.png) 185 | 186 | 1. Lastly, we can create a rate-based rule that prevents an overwhelming number of requests (either valid or invalid) from flooding our API: 187 | 188 | * Click **Create Rule** 189 | * Give it a name, like `RequestFloodRule` 190 | * For **Rule type**, select `Rate-based rule` 191 | * For **Rate limit**, use `2000` 192 | * Then click **Create** 193 | 194 | ![screenshot](images/request-flood-rule.png) 195 | 196 | 1. You should now see 3 rules in like below. Ensure you select `Block` if the request matches any of the rules. 197 | 198 | For **Default action**, select `Allow all requests that don't match any rules` 199 | 200 | ![screenshot](images/list-rules.png) 201 | 202 | 1. Click **Review and create** 203 | 204 | 1. In the next page, review the configuration and click **Confirm and Create** 205 | 206 | ![screenshot](images/review-acl.png) 207 | 208 | You have now added a WAF to our API gateway stage! 209 | 210 | ### Module 6D: Test requests with WAF protection 211 | 212 | 1. First, send some valid requests using Postman to make sure well-behaving requests are getting through. 213 | 214 | 1. Next, we can easily test the large request body rule by sending a few **POST /customizations** requests with a giant request body. If you don't receive an error immediately after applying WAF, you might need to wait a minute to for these changes to propagate. 215 | 216 | In Postman, choose the **POST create Custom_Unicorn** request and replace the request body with: 217 | 218 | 219 | 220 | ``` 221 | { 222 | "name":"my custom unicorn", 223 | "imageUrl":"https://abc.efg.com/YA3K7yOwfmKhD1SdZ0MDB9C97RnJ3vb74WmoPOGJb2crs04okE2TcghSVgMWBLZ0c7rYZA5sjPWdfU7GJsRnEexwqgVfq2c94jEYdBCyxrZA3bZY36MiBnQZDrMyMMq1I8WJ7U4otss7mNWyQON0suZFXGCV7g7Z15dh14FIemSrkw3MzBLjsoAGTaz4VW1Ftljt5FCyJG3GtCSRvIoBkJ1YNiqKDRuiyFud7RgxBTXJEj3VvpTtT5CfWKPKKwfal4q506gW6aBgTeZGlhIGWlCxuT6sIYPodrXX4xmfukCFR32wtk7VgEiqYpKKwey2uQnZNQJHqwbHFZakppNYDQAeJ6NqB0tLDhERX2KtEiXH7iEJgAXeMLd7PNWrhYIlycsbcVnNrSpCmnBwADM3uVrKVF78qNGN2DnazascF3rIFSZJMPvNocSIT4zlK8VmXxB8inJb56UEHsYn9LAfVQMFcXPU3xwmKljk2fz5lHHs6vPeDnqjDNMEm1sXFq3S4759GZXzQubDcjYHX3REqeUNPYokrMAFb28qkfQwXUvrAq4Ov5eMXhFeXMBwupfqsSpPz0CMhr8o0M8sjG7ilXvrMo0jVcEmUrfRshkTHs55gZsaXP2CXjSbK0Z6sNiIiNtngAmHBZ1UkjJplirwVYLYAarYbghRdQswrWki447NtS0iFibgOjGkDXpFYwxaqNehMhalLUnP5jfz125V7HNzQ0wX5jgm42yGEjBaGNcI8hcySPXNI4jvT8RlZYxMs8m0zeZxxHEXqVfqVFYEr63gsI33nVPXS5jnJs7x3KY4wBOmOmwzWBBb62dBYBzqMwtRKp560MsR7uOK2hMGTBSdQtNubetRtu5JClGhlqE7Zv4SQ44lraE87vat55nXTgma7xpAjpwiH6yDQW6x1EXxjfVccjvR44FJNtUFVBh0DqUwoThR57SaEuIrLlP7e2pysgBI9GsYVt5RnXVUMXSPaDknVY4dFLVEPSEoOVRAt1kMcW9R3v3jURBBvTXfsFLzXn2t4DAYA1QmhJDkt9xpUOs1sviBYqjpUdhmRaun14Fx7aQiuwKAsfyJyVPbgZgmxOkeWpkf4ohcKwGZI8T6UlVWrwiRVL6eVQOdb3stNitObefEF7c9G9THQURzwpm7DanLTcAmnjlTyZS2NOW84it8QxDZ4qFGuxSjmzoGUUai9FRSpmyTozJvjmwZUFF5Codn9UWN2RrrEfKbuufl5ErPGRdyNkL73Hw9t1RG3UObmc0sf34z0JsHaL07StwB9HIXm5SLu9aZIpGoRu4UU23YL8jgORSXVop3HdkFifVTBf2w2mXaL0r07MHwLXQC7olLdNSXvj9uqVySHhAvAnhYquF0dwartwByZWT3Zt4i5gueuCb2LgrJJTSYQeDz9HIA4oDSnWj9BaxXKDuBTyLPwAdB4ER7I2Kl7lgdKuknaHXh5z9f4ybonueDv3RBd2Hcny5256im7jE6rEIWtTIbTKCR0frmpWm2smmwfL2IQIJE0lp5kxfDroqNf5l1XrovOo9sTD4LYIf88mUI3cAbBwNnPDtSZIWTJ3XcoK8Rm88xb3xKUxCZsQNnxBJM1eWzrYe4rSzIKk887GwwBAK1NFustp8bxSS3F73e2Oh9ijpbFBaAgmlG7nb21pEMBAw3G0yK6Hb0YePZvtXCxPQXDzFg5ya5BvqVPZESIAlkxNCz7kr0lTYJgwnixLUyM6gW6BekQAznQdaTK8LHYN9xJZusRgcSsVmfmA59KZ4J4oKxSAF4G14yI1P1Hj3juHHP8A90OjHfDvzkxGgL81CduPkBhyTRlhXg5mfIcEVGCViySt3cQGSYj43uo3sJ0JD77G7s5rc8kcc9VKPJ7sm1KZwNhF5lj6Ew1IOLG45xhrOOcJ8IvWAutcbFScOU0bZqwXyAi1ZLagPeVkkUBVHQKlHDUaQsuWXYnGyEO51Q5rgv6zdeFJlc32bjO1KelHURGznCHQgMB4rUlQUff482NWtoIC97Sp2es71nH88vzJf3yEiALXPe9a2XIWq5iAJpOr5SFFYJApDQ84k7UTLp2eiv7pZObd8CoT1RM7D5HepHdULl2wuzOKzugK7rQSgFrdndZEstMwwl0Rd6QH7ecuyidcjebuxT021M98ngzHBnki0muGFpYtWeO88IrqFsvSdb7PwARLZQFERcRfkYmynJ2xLnYLsNGw3Zim8lMqBKj06OrY6obubc5oNxyk3tzYAuhJhouNj6qIMaQYSBxkmDQuMsjJ8ULTaODdJuBNxp6wYPNyR4150dKukBc3fifzhcXQZP5KRudVf1nsDLYvQILifTJzSa01vwyxEhqCAEFZXnGuhANOwPWYuB5iagGErZ1MfNmlBCqYycveVjU0M7JWxZBPvHzLDFb3K9p91lO9URPXsieywBkFiP9RY0kSdktrwF8gHBiqacxPKFoS80o4PRfjr18ZOkT3XKGFiaQ9N3ubU1JzfGa3wvguPwlt0B4xk50jVnI45qeJAMpWBwEC8niMO7DIVBwxN0ERVrLpoOwdsE97xisGz5utDMqGi7IUpHozeCne9HYYxRAbia9skHgxAdsu64O7MSunuKxNs48wP9ClaeFyu6Yq6K8pGfUz54hxuDozlMRsIeawrqzj4CFl7AlDAtHyBLLuZIoAYo4f5evKyTPkcrF32YhhtINxlKGBJzCWKr6CTr76sPrbIbCyca94ymUILS09e0OYM7hlUqbzL4BBcPAWdr76akCmemeWayWbeF5piZUN4Zx8du5QINRnBGZo6T1CQmIQYJEEKKaQYfIiykitvq9v4ITF7TukiP1STzPJpL9SIJcxOBZjFmVaCK9sJeFQasXgJu2pAgdW41h0e8t7ygrlR6VZG1mKvk6QmTSCNOhMVrM5R74ZMlBfsJzvrOFgzoed0qOJg3rV4Te3BthONwYme1h9f5vQGRsxUu3UQnbIx8tIgVCYOsAEt6Jjho58AYYyZSGC5QYwRmqX6qd2O2cl8razz8cECzrGgHHaUsWVynpnW7RyAhZ6WrjN5sXANcRtEoeyK7gSIp9M0KhrItJNh0a6M6TEfFLMKOXV19YjZkJoalv5nQoy9V7dexFzvmtEtwnPwClSSEr9HczxFkpMCKzoCJwnlNlgSqkZ2Pw4mQSt05OmNqiyhnDw1rzTQgdMv8YdbWoD9Wkiln0UXghv816dcV3ZZD2UtF5yIeU4oo39ghfHW8MoccOcp6iasAMaEiENwha9D2p9J2Z0SwsOiS9gtjwVh4KdMc4EKcJdHkQAJ2iL3ZTulpuHmBo5yczOkJh2k1EZ7qaamOccvuxCPQE3Yofh5ztwHCIMFoM9pqRJRrW2ZXY1VbHKotiNSrWXnzunOKRktCEIKHXb4kN3q3iDwpiW5Ndno3I9CDmb5HihMsTom5kUmQIwgJpWZkrSUkakNbIP0eUe9GgosjqsvGNax9is46zedXoMHqwF1Qg7MQfy23NtCAvndwkpwmaoaFmhObg9TpFmI6skEmDrPAw4pArL0LalgPFXiqxoVyOouPdgwk6U1gQaWLG0gWBRki1RPn0Ikw6j4MAvs0jIVetqBNkTnLmVPU7qxHxMv0jxiDta8xz12LfQeOSQmtvjn66W5GwBy4KAvtttUKzgApJQOEUq2ynPXoKmtzlY9UQ9TAapTHm1qQA2FoZOL4GE9lKPAY4VxjsDFk1WvyCvWVlAkVT8qIlhOmnPPw0l7o9DpnMt66Hls6OJuqIfwisBhidht2MMw5nZ76gDbSrYKq3lVfMow9MGz9xfEoxFyWxPNEFlN0VUsLFvA37NwwuiwueIt6HjnGWdYcLe1LqZLtUwtQjLuizq87OYT6WKrGxnjqMFfBhEbdGBRIzioPLlRQijBMTdh0iD2YXwU4gdRMTGyb4ZrYmry0aiKojCTEZ5wFFvnDnIeaz869chZdhSb7QxeLpOZSwjsaSSyAhSY2fU3S08GQnCM7z5qvNHzvFbyFDaCh5h1YNysdyPs6Sml5wGqu475hBQnGkQiHNYbTfNohqIMyPpHju1OlbqP8P77DLy39cFf7j8EE7ExhHwI4tiDjiV0ipIYOuTPQZGe12XBu1kAYDcy4I9YqnSIx184JIOayPTdjJOXJ6DzdBfkePpLOWhHVoYaYYGkhzZgcoaPpjxGFhkZF32s28oOKXLTZx98eE9DRd7riISYn8O7nLpIVNJlH5J7pDdobAdGshjfgY7I5SfeaiI6MiE7rDnnyBXDy0SFId8zmpWYmXyBVw5rIrvy1Q8f0JSHhP6NoPcPeF3wbMVOdJ5d5OBZZrO1QhLeWvpEMzBV03xCK9vP985NRzWvOuWkLQXHNANmGhekspm6DruDSAkgU9qpBgUonwsfszz5TiP3F2CKFDGp3BXTBbRmy9nizz3wULa6Ny3276ILHpiFHl7g6H1Bkpo9g5EROGz7PNwxOw0wBJ74UEyky31CYDzanN45kvbf3crM9V8V0Y8B4zD5VSwW1M3RqEYWcyrRkgArjpEEkaWhyyMC7dCk6DWrbdxlDS9iD6gNXMIA1frZu1UegacgPuCsakfz262CJ6Qdyk6xkN97zH8iZakMnx570lipWm4wSbAlTkQVL88NfLHAnaS3kLeSTLkZFtULiKGahy4HkusJsn55dbxu1h7AtWFF54FOhGzGa9yxxnSqbn56KYASpghTOecg0du6ttjEE7ajbYFlOnF1atHOvSKskY9WZdMuee5yBvKQKIwXvUtyrDF55v8ArlEBHl3WFJf08KoYQrF6yxIxDXxhjG3I32G2Qxlj7o6dunk7yEvkrFeKwYpwqHUYs1UlJwoxpEyIjdppOxOxMysILvdh9eSvCdiq2nufwBeLxQqWoHQKa1kDHDR8gGm4ASDcoy53fZB9WykV0ylvpbzJtsPrKIXyTEV8FLUx3FcUkCCCcBuh8t3hCMpMOuSe0EsSjBaInXtR2h0nL7MGq8lUicCIbeVBfkF4O", 224 | "sock":"1", 225 | "horn":"2", 226 | "glasses":"3", 227 | "cape":"2" 228 | } 229 | 230 | ``` 231 | 232 | You should see your requests getting blocked with a **403 Forbidden** response 233 | 234 | 💡 **Note:** It may take a minute for WAF changes to propagate. If your test request went through successfully, retry a few times until you start receiving 403 errors as WAF kick in effect. 💡 235 | 236 | 1. Next, let's try a request with a SQL injection attack in the request URI for a **GET /customizations/{id}** request 237 | 238 | In Postman, choose the **GET Custom_Unicorn** request and replace the URL with: 239 | 240 | ``` 241 | {{base_url}}/customizations/1; drop table Custom_Unicorns; 242 | ``` 243 | 244 | You should see your requests getting blocked with a **403 Forbidden** response 245 | 246 | 1. The WAF console gives you metrics and sample requests that are allowed/denied by the WAF rules. You can find this information by going to the WAF console, under **Web ACLs**, select the AWS region and then the WAF we just created. 247 | 248 | **Note:** It can take a few minutes before the metrics and sample requests start showing up in the WAF console. 249 | 250 | ![screenshot](images/request-sample.png) 251 | 252 | ## Extra credit 253 | 254 | Use a load test tool like [Artillery](https://artillery.io/docs/getting-started/) to test sending more than 2000 requests in 5 minutes to test the request flood rule. 255 | 256 | Note that you will need to configure Artillery to send the `Authorization` headers. 257 | 258 | If you have completed **Module 5: Usage Plan**, your API may be throttled first by the usage plan based on the API key. 259 | 260 | ## Want more? 261 | 262 | In this module, we only explored 3 types of AWS WAF rules: 263 | 264 | * SQL Injection 265 | * Request size constraint 266 | * Rate limiting 267 | 268 | There are a lot more other types of protection you can enable, based on the types of risks you want to defend against 269 | 270 | Check out the below to learn about other type of rules: 271 | 272 | * AWS WAF Security Automations: [https://aws.amazon.com/solutions/aws-waf-security-automations/](https://aws.amazon.com/solutions/aws-waf-security-automations/) 273 | * Managed WAF Rules from AWS Marketplace: [https://aws.amazon.com/marketplace/solutions/security/waf-managed-rules](https://aws.amazon.com/marketplace/solutions/security/waf-managed-rules) 274 | 275 | ## Next Step 276 | 277 | Return to the workshop [landing page](../../README.md) to pick another module. 278 | -------------------------------------------------------------------------------- /docs/06-waf/images/SQLi-attack-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/SQLi-attack-success.png -------------------------------------------------------------------------------- /docs/06-waf/images/classifc-waf-opening.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/classifc-waf-opening.png -------------------------------------------------------------------------------- /docs/06-waf/images/large-body-condition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/large-body-condition.png -------------------------------------------------------------------------------- /docs/06-waf/images/large-body-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/large-body-rule.png -------------------------------------------------------------------------------- /docs/06-waf/images/list-rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/list-rules.png -------------------------------------------------------------------------------- /docs/06-waf/images/recreate-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/recreate-table.png -------------------------------------------------------------------------------- /docs/06-waf/images/request-flood-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/request-flood-rule.png -------------------------------------------------------------------------------- /docs/06-waf/images/request-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/request-sample.png -------------------------------------------------------------------------------- /docs/06-waf/images/review-acl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/review-acl.png -------------------------------------------------------------------------------- /docs/06-waf/images/sql-condition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/sql-condition.png -------------------------------------------------------------------------------- /docs/06-waf/images/sql-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/sql-rule.png -------------------------------------------------------------------------------- /docs/06-waf/images/switch-waf-classic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/switch-waf-classic.png -------------------------------------------------------------------------------- /docs/06-waf/images/web-acl-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/06-waf/images/web-acl-name.png -------------------------------------------------------------------------------- /docs/07-dependency-vulnerability/README.md: -------------------------------------------------------------------------------- 1 | # Module 7: Dependency Vulnerability 2 | 3 | When building modern applications, it is common to use different libraries, modules and, in general, different dependencies. Even if we are including a simple dependency, we could end up with tens or even hundreds of sub-dependencies. Just take a look at this page: 4 | 5 | - [http://npm.anvaka.com/#!/view/2d/request](http://npm.anvaka.com/#!/view/2d/request) 6 | 7 | A simple module used by several applications like *request* could end up with 60 links! If you never thought about the impact a single vulnerable dependency can have, take a look at this [story](https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/). 8 | 9 | In this module, we will cover finding and removing publicly disclosed vulnerable dependencies. There are different tools that can help monitor dependency vulnerabilities. Depending on the programming language, below are some example tools to look into: 10 | 11 | - [npm-audit](https://docs.npmjs.com/cli/audit) 12 | - [OWASP Dependency Check](https://www.owasp.org/index.php/OWASP_Dependency_Check) 13 | - [Snyk](https://snyk.io/) 14 | - [Puresec](https://www.puresec.io) 15 | - [Twistlock](https://www.twistlock.com/) 16 | - [Protego](https://www.protego.io/) 17 | 18 | During this workshop we will use the first one to review our code. 19 | 20 | ## Dependency vulnerability with *npm audit* 21 | 22 | The tooling for dependency vulnerability checking may vary for different programming languages. With NodeJS, vulnerability checking is now a feature shipped with the `npm` package manager itself after npm acquired NSP (Node Security Platform). 23 | 24 | Running `npm audit` command will produce a report of security vulnerabilities, and if available, commands to apply patches to resolve vulnerabilities. In fact `npm audit` automatically runs when you install a package with `npm install`. 25 | 26 | 27 | 1. In the cloud9 environment, go to the node application directory where `package-lock.json` is: 28 | 29 | ``` 30 | cd ~/environment/aws-serverless-security-workshop/src/app 31 | ``` 32 | 33 | 1. Run the vulnerability audit: 34 | 35 | ``` 36 | npm audit 37 | ``` 38 | 39 | You should see something like this: 40 | 41 | ![](images/audit-result.png) 42 | 43 | So it turns out the `minimatch:2.0.10` dependency has a known vulnerability. Reading the link on the security advisory in the report can give you more detail on how it can be exploited. 44 | 45 | Before we attempt to patch this dependency as suggested by the report, we should ask first: is the application even using *minimatch*? This library compares two different expressions against regular expressions to find out if they match. 46 | 47 | In fact, our application is not even using the library thus we should remove it. This can often happen in software projects when a library got pulled into the code base to experiment with something, but later the code evolved and that dependency is no longer required. 48 | 49 | But how do we know for sure which dependencies are we using and which ones not so we can safely remove unused dependencies? 50 | 51 | ### Removing unused dependencies using static analysis 52 | 53 | We will install another tool to review our code and report which dependencies are included in our code and are not being used. Maybe they were used in a previous point in time, but not anymore. 54 | 55 | 1. Run the following command to install [depcheck](https://www.npmjs.com/package/depcheck?activeTab=readme): 56 | 57 | ```bash 58 | npm install -g depcheck 59 | ``` 60 | 61 | 2. Run the tool with the following commands: 62 | 63 | ```bash 64 | cd ~/environment/aws-serverless-security-workshop/src/app/ 65 | depcheck 66 | ``` 67 | 68 | The result should be something like this: 69 | 70 | ```bash 71 | $ depcheck 72 | Unused dependencies 73 | * babel-core 74 | * babel-plugin-transform-flow-strip-types 75 | * babel-preset-es2017 76 | * minimatch 77 | Missing dependencies 78 | * aws-sdk 79 | ``` 80 | 81 | Therefore, to mitigate this, we should remove these dependencies. Run the following commands: 82 | 83 | ```bash 84 | npm uninstall babel-core --save 85 | npm uninstall babel-preset-es2017 --save 86 | npm uninstall minimatch --save 87 | npm uninstall babel-plugin-transform-flow-strip-types --save 88 | ``` 89 | 90 | You may also have noticed that there are some **missing dependencies**! This is because we are using the `aws-sdk` package already installed in the [AWS Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) 91 | 92 | To be sure we removed unused dependencies, run `depcheck` again. 93 | 94 | Now your code is free of vulnerabilities from the dependency perspective! 95 | 96 | > These steps should be part of your CI/CD pipeline and implemented to be run on every deployment. 97 | 98 | ## Extra credit 99 | 100 | Before October 2019, we used to recommend a free tool called [Puresec Function Shield](https://www.puresec.io/function-shield) 101 | 102 | It performs additional runtime protection of your lambda function: 103 | 104 | * If not required, block outbound network traffic from your function. 105 | * Disable `/tmp` if it's not used 106 | * Disable the ability to launch child processes from within the Lambda container. 107 | 108 | However, it's no longer being maintained as of October 2019 and the project incorporated into a commercial product (see [https://github.com/puresec/FunctionShield](https://github.com/puresec/FunctionShield) ) 109 | 110 | To look at other commercial product offerings in this area, check out the [Lambda security partner page](https://aws.amazon.com/lambda/partners/?partner-solutions-cards.sort-by=item.additionalFields.partnerName&partner-solutions-cards.sort-order=asc&awsf.partner-solutions-filter-partner-type=use-case%23security-identity-compliance) 111 | 112 | ## Next Step 113 | 114 | Return to the workshop [landing page](../../README.md) to pick another module. 115 | 116 | -------------------------------------------------------------------------------- /docs/07-dependency-vulnerability/images/audit-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/07-dependency-vulnerability/images/audit-result.png -------------------------------------------------------------------------------- /docs/07-dependency-vulnerability/images/dependency-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/07-dependency-vulnerability/images/dependency-check.png -------------------------------------------------------------------------------- /docs/07-dependency-vulnerability/images/functionshield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/07-dependency-vulnerability/images/functionshield.png -------------------------------------------------------------------------------- /docs/07-dependency-vulnerability/images/vulnerable-dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/07-dependency-vulnerability/images/vulnerable-dependency.png -------------------------------------------------------------------------------- /docs/08-xray/README.md: -------------------------------------------------------------------------------- 1 | # Module 8: AWS X-Ray 2 | 3 | "Insufficient Logging & Monitoring" is one of the Top 10 Application Security Risks ranked by [OWASP](https://www.owasp.org/index.php/Main_Page) in 2017. 4 | 5 | AWS X-Ray gives you visibility into the data flow of your microservices architecture and a map of how your application’s underlying components are connected. It's a great tool to troubleshoot performance and debug errors. However, given the ephemeral nature of the infrastructure in a serverless application, this visibility into your application is also critical for the purpose of security: 6 | 7 | * It helps you understand the "norm" of the data flow, interdependencies, and performance characteristics of your distributed serverless components. Knowing that is a prerequisite of recognizing when things are not normal. 8 | * During an security incident or post analysis, X-Ray can give you insights into what your code is doing at runtime, what downstream dependency it's making calls to, where the code is spending its time 9 | 10 | ## Module 8A: Enable X-Ray for Lambda function 11 | 12 | 13 | In the Cloud9 IDE environment, go to the SAM template (`template.yaml`), find the [**Globals**](https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst) section, which contains settings that all resources in the SAM template share unless explicitly overwritten. 14 | 15 | ``` 16 | Globals: 17 | Function: 18 | Timeout: 30 19 | ... 20 | ``` 21 | 22 | Add `Tracing: Active` to the configuration section for lambda functions: 23 | 24 | ``` 25 | Globals: 26 | Function: 27 | Timeout: 30 28 | Tracing: Active 29 | ... 30 | ``` 31 | 32 | ## Module 8B: Capturing AWS SDK requests with XRay 33 | 34 | When our applications make calls to AWS services such as Secrets Manager, DynamoDB, S3 etc., the X-Ray SDK can help tracks the calls downstream and record the request timing, status, etc. about the AWS Service call. 35 | 36 | To enable this, you can instrument all AWS SDK clients by wrapping your `aws-sdk` require statement in a call to `AWSXRay.captureAWS` 37 | 38 | ### Capturing AWS SDK requests in the Lambda authorizer 39 | 40 | The Lambda authorizer you added in [**Module 1: Auth**](../01-add-authentication) uses the AWS SDK to look up values from a DynamoDB table. We can instrument the AWS SDK with X-Ray: 41 | 42 | 1. Install the XRay SDK in the `authorizer/` folder by running in a terminal 43 | 44 | ```bash 45 | cd ~/environment/aws-serverless-security-workshop/src/authorizer 46 | npm install aws-xray-sdk-core --save 47 | ``` 48 | 49 | 1. In `authorizer/index.js`, find the line where the AWS SDK is imported: 50 | 51 | ```javascript 52 | const AWS = require('aws-sdk'); 53 | ``` 54 | 55 | And replace it with: 56 | 57 | ```javascript 58 | const AWSXRay = require('aws-xray-sdk-core'); 59 | const AWS = AWSXRay.captureAWS(require('aws-sdk')); 60 | ``` 61 | 62 | ### Capturing AWS SDK requests in the backend lambda functions 63 | 64 |
65 | If you haven't gone through Module 2: Secrets

66 | 67 | The backend lambda functions currently doesn't use the AWS SDK, so no additional action needed! 68 | 69 |

70 | 71 |
72 | If you have gone through Module 2: Secrets

73 | 74 | If you have gone through [**Module 2: Secrets**](../02-add-secrets-manager), you would have added the AWS SDK to `dbUtils.js` so the code would retrieve the database username and password from [**AWS Secrets Manager**](https://aws.amazon.com/secrets-manager/) 75 | 76 | 1. Install the XRay SDK in the `app/` folder by running in a terminal 77 | 78 | ```bash 79 | cd ~/environment/aws-serverless-security-workshop/src/app 80 | npm install aws-xray-sdk-core --save 81 | ``` 82 | 83 | 1. In `app/dbUtils.js`, find the line where the AWS SDK is imported: 84 | 85 | ```javascript 86 | const AWS = require('aws-sdk'); 87 | ``` 88 | 89 | And replace it with: 90 | 91 | ```javascript 92 | const AWSXRay = require('aws-xray-sdk-core'); 93 | const AWS = AWSXRay.captureAWS(require('aws-sdk')); 94 | ``` 95 | 96 | 97 |

98 | 99 | 100 | ## Module 8C: Deploy lambda changes and test 101 | 102 | 1. In the terminal, validate the SAM template: 103 | 104 | ``` 105 | cd ~/environment/aws-serverless-security-workshop/src/ 106 | sam validate -t template.yaml 107 | ``` 108 | 109 | 1. Deploy the updates: 110 | 111 | ``` 112 | aws cloudformation package --output-template-file packaged.yaml --template-file template.yaml --s3-bucket $BUCKET --s3-prefix securityworkshop --region $REGION && aws cloudformation deploy --template-file packaged.yaml --stack-name CustomizeUnicorns --region $REGION --parameter-overrides InitResourceStack=Secure-Serverless --capabilities CAPABILITY_IAM 113 | ``` 114 | 115 | 1. Once the deployment finishes, test making API requests again with postman. 116 | 117 | 1. Go to the [**X-Ray console**](https://console.aws.amazon.com/xray/home), go to the **Service map** tab and refresh. You should start seeing some lambda requests getting captured! 118 | 119 | 120 | ## Module 8D: Enable X-Ray on API Gateway 121 | 122 | 1. Go to [API Gateway Console](https://console.aws.amazon.com/apigateway/home), and go to the `CustomizeUnicorns` API 123 | 124 | 1. Go to the **Stages** tab, click on the `dev` stage 125 | 126 | 1. Find the **Logs/Tracing** tab, check the box for **Enable X-Ray Tracing**, and **Save changes** 127 | 128 | ![enable xray in api gateway](images/8E-enable-apig.png) 129 | 130 | 1. Redeploy the API by clicking on the **Resources** tab on the left hand side --> **Actions** --> **Deploy API** -> Pick the `dev` stage --> **deploy**. 131 | 132 | 1. Test making a few making API requests with postman. 133 | 134 | 1. Go to the [**X-Ray console**](https://console.aws.amazon.com/xray/home), go to the **Service map** tab and refresh 135 | 136 | ![enable xray in api gateway](images/8E-service-map.png) 137 | 138 | 1. Explore the service map. Click on various components, and use **View traces** to see a list of request traces captured by X-Ray 139 | 140 | ![enable xray in api gateway](images/8E-traces.png) 141 | 142 | 1. Explore the individual traces by clicking into individual requests 143 | 144 | ![enable xray in api gateway](images/8E-single-traces.png) 145 | 146 | 147 | ## Next Step 148 | 149 | Return to the workshop [landing page](../../README.md) to pick another module. 150 | -------------------------------------------------------------------------------- /docs/08-xray/images/8E-enable-apig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/08-xray/images/8E-enable-apig.png -------------------------------------------------------------------------------- /docs/08-xray/images/8E-service-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/08-xray/images/8E-service-map.png -------------------------------------------------------------------------------- /docs/08-xray/images/8E-single-traces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/08-xray/images/8E-single-traces.png -------------------------------------------------------------------------------- /docs/08-xray/images/8E-traces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/08-xray/images/8E-traces.png -------------------------------------------------------------------------------- /docs/10-resource-cleanup/README.md: -------------------------------------------------------------------------------- 1 | # Resource clean up 2 | 3 | This page provides instructions for cleaning up the resources created during the preceding modules. 4 | 5 | ## Resource Cleanup Instructions 6 | 7 | 1. Delete Cognito User pool domain that you created if you created one in **Module 1: Auth** 8 | 9 |
10 | Click here to expand for detailed instructions

11 | 12 | 1. Go to the [Cognito Console](https://console.aws.amazon.com/cognito/home) 13 | 1. Go to **Manage User Pools** 14 | 1. Choose `CustomizeUnicorns-users` user pool 15 | 1. Go to **Domain name** under **App integration** 16 | 1. Click **Delete domain** 17 | 1. Confirm the deletion 18 | 19 |

20 | 21 | 1. Delete API Gateway Usage plan if you created one in **Module 5: Usage Plans** 22 | 23 |
24 | Click here to expand for detailed instructions

25 | 26 | 1. Go to the [API Gateway Console](https://console.aws.amazon.com/apigateway/home) 27 | 1. Go to **Usage plans** 28 | 1. Go to the `Basic` Usage Plan 29 | 1. In the **Details** tab under **Associated API Stages**, remove the `CustomizeUnicorns` API 30 | 1. On the upper right hand corner, click on **Actions** and choose **Delete Usage Plan** 31 | 32 |

33 | 34 | 35 | 1. Delete the secret from AWS Secrets Manager if you created one in **Module 2: Secrets** 36 | 37 |
38 | Click here to expand for detailed instructions

39 | 40 | 1. Go to the [Secrets Manager Console](https://console.aws.amazon.com/secretsmanager/home) 41 | 1. Select the `secure-serverless-db-secret` secret 42 | 1. In **Actions** select **Delete secret** 43 | 1. Enter `7` (minimum waiting period) for waiting period and click **Schedule deletion** 44 | 45 |

46 | 47 | 1. Delete the AWS WAF if you created one in **Module 6: WAF** 48 | 49 |
50 | Click here to expand for detailed instructions

51 | 52 | 1. Go to the [WAF Console](https://console.aws.amazon.com/waf/home) 53 | 1. In the navigation pane, choose **Web ACLs**. 54 | 1. Choose the `ProtectUnicorns` web ACL you created in the module 6 55 | 1. On the **Rules** tab in the right pane, choose Edit web ACL. 56 | 1. Remove all rules from the web ACL by choosing the **x** at the right of the row for each rule. This doesn't delete the rules from AWS WAF, it just removes the rules from this web ACL. 57 | 1. Choose **Update** 58 | 1. Dissasociate the API gateway from the WAF by going to the section **AWS resources using this web ACL** in the **Rules** tab and clicking the **x** at the right of the API gateway stage 59 | 1. On the **Web ACLs** page, confirm that the web ACL that you want to delete is selected, and then choose **Delete**. 60 | 1. In the navigation pane, choose **Rules**. 61 | 1. Go to each of the 3 rules we created, edit the rule to disassociate all the conditions for each rule 62 | 1. Delete the rules 63 | 1. Delete the 3 conditions we created in the workshop 64 |

65 | 66 | 67 | 1. Delete `CustomizeUnicorns` CloudFormation stack 68 | 69 |
70 | Click here to expand for detailed instructions

71 | 72 | 1. Go to the [CloudFormation Console](https://console.aws.amazon.com/cloudformation/home) 73 | 1. Select the `CustomizeUnicorns` Stack 74 | 1. Under **Actions**, choose **Delete Stack** 75 | 76 |

77 | 78 | 1. Empty the deployment s3 bucket: 79 | 80 |
81 | Click here to expand for detailed instructions

82 | 83 | 84 | 1. Go to the [S3 Console](https://console.aws.amazon.com/s3/home) 85 | 1. Search for bucket starting with `secure-serverless-deploymentss3bucket` 86 | 1. Click on the checkmark for the bucket and click on the **Empty** button 87 | 88 | ![](images/empty-s3-bucket.png) 89 | 90 | 1. Type in the bucket name to confirm the empty operation 91 |

92 | 93 | 1. Delete the `Secure-Serverless` resource setup CloudFormation stack 94 | 95 | 1. CloudWatch Logs 96 | AWS Lambda automatically creates a new log group per function in Amazon CloudWatch Logs and writes logs to it when your function is invoked. You should delete the log group for the lambda functions. (You can search for log groups starting with `/aws/lambda/CustomizeUnicorn` prefix. 97 | 98 | 1. Delete the RDS snapshot of the aurora database in the RDS console 99 | -------------------------------------------------------------------------------- /docs/10-resource-cleanup/images/empty-s3-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/10-resource-cleanup/images/empty-s3-bucket.png -------------------------------------------------------------------------------- /docs/images/cleanup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/cleanup.png -------------------------------------------------------------------------------- /docs/images/moduel1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/moduel1.png -------------------------------------------------------------------------------- /docs/images/module0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module0.png -------------------------------------------------------------------------------- /docs/images/module2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module2.png -------------------------------------------------------------------------------- /docs/images/module3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module3.png -------------------------------------------------------------------------------- /docs/images/module4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module4.png -------------------------------------------------------------------------------- /docs/images/module5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module5.png -------------------------------------------------------------------------------- /docs/images/module6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module6.png -------------------------------------------------------------------------------- /docs/images/module7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module7.png -------------------------------------------------------------------------------- /docs/images/module8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/docs/images/module8.png -------------------------------------------------------------------------------- /src/apiclient/css/main.css: -------------------------------------------------------------------------------- 1 | table, th, td { 2 | border: 1px solid black; 3 | } 4 | table { 5 | width: 20%; 6 | min-width: 225px; 7 | margin: 0 auto; 8 | } 9 | #status { 10 | margin: 0 auto; 11 | width: 20%; 12 | min-width: 225px; 13 | } 14 | 15 | #status2 { 16 | margin: 0 auto; 17 | width: 20%; 18 | min-width: 225px; 19 | } 20 | 21 | .panel-success > .panel-heading { 22 | background-image: none; 23 | background-color: #237abf; 24 | color: black; 25 | border: none; 26 | } 27 | .panel { 28 | background-image: none; 29 | color: black; 30 | border-color: #237abf;; 31 | } 32 | .panel-title { 33 | background-image: none; 34 | color: black; 35 | border-color: #237abf;; 36 | } 37 | 38 | #toggleButton{ 39 | min-width: 225px; 40 | color: white; 41 | background-color: blue; 42 | font-size: large; 43 | } -------------------------------------------------------------------------------- /src/apiclient/css/site.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background-color: white; 3 | border: none; 4 | color: black; 5 | padding: 20px; 6 | text-align: center; 7 | text-decoration: none; 8 | display: inline-block; 9 | font-size: 24px; 10 | margin: 4px 2px; 11 | } 12 | 13 | #outerHeader { 14 | background-color: #1abc9c; 15 | padding-top: 80px; 16 | padding-bottom: 80px; 17 | } 18 | 19 | header{ 20 | background-color: #1abc9c; 21 | color: white; 22 | height: 100px; 23 | overflow: hidden; 24 | } 25 | 26 | header p { 27 | font-size: 20px; 28 | } 29 | 30 | #title{ 31 | display: inline-block; 32 | } 33 | header img{ 34 | width: 80px; 35 | margin-right: 30px; 36 | display: inline-block; 37 | padding-bottom: 40px; 38 | } 39 | 40 | .rcorners { 41 | border-radius: 10px; 42 | } 43 | 44 | .button4 { 45 | border-radius: 12px; 46 | padding: 10px; 47 | background-color: #1abc9c; 48 | color: white; 49 | 50 | } 51 | 52 | /* Style the buttons that are used to open and close the accordion panel */ 53 | .accordion { 54 | background-color: #eee; 55 | color: #444; 56 | cursor: pointer; 57 | padding: 18px; 58 | width: 100%; 59 | text-align: left; 60 | border: none; 61 | outline: none; 62 | transition: 0.4s; 63 | } 64 | 65 | /* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */ 66 | .active, .accordion:hover { 67 | background-color: #ccc; 68 | } 69 | 70 | /* Style the accordion panel. Note: hidden by default */ 71 | .panel { 72 | padding: 0 18px; 73 | background-color: white; 74 | display: none; 75 | overflow: hidden; 76 | } -------------------------------------------------------------------------------- /src/apiclient/img/request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-security-workshop/710789ba9c51d42a31ed43842381b9a3e1980d39/src/apiclient/img/request.png -------------------------------------------------------------------------------- /src/apiclient/js/main.js: -------------------------------------------------------------------------------- 1 | async function module0getCustomizations() { 2 | 3 | var apiurl = $('#apicustomizations0').val(); 4 | var requestBody = ''; 5 | var token = $('#ResponseText0').val(); 6 | var apitype = $('#methodtype0').find(":selected").val(); 7 | var apikey = 'abcdef'; 8 | 9 | $('#send0').attr('disabled', true); 10 | 11 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 12 | $('#0customresponse').val(response); 13 | 14 | $('#send0').attr('disabled', false); 15 | } 16 | 17 | async function module1EgetToken() { 18 | 19 | var tokenURL = $('#tokenURL').val(); 20 | var clientID = $('#clientID').val(); 21 | var clientSecret = $('#clientSecret').val(); 22 | 23 | var response = await getToken(tokenURL, clientID, clientSecret); 24 | 25 | $('#ResponseText1E').val(response); 26 | 27 | } 28 | 29 | async function module1EgetClientInfo() { 30 | 31 | var apiurl = $('#apipartners').val(); 32 | var requestBody = $('#body1E').val(); 33 | var token = $('#ResponseText1E').val(); 34 | var apitype = 'POST'; 35 | var apikey = 'abcdef'; 36 | 37 | $('#getClient1E').attr('disabled', true); 38 | 39 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 40 | $('#1Epartnerresponse').val(response); 41 | 42 | $('#getClient1E').attr('disabled', false); 43 | } 44 | 45 | async function module1FgetToken() { 46 | 47 | var tokenURL = $('#tokenURL1F').val(); 48 | var clientID = $('#clientID1F').val(); 49 | var clientSecret = $('#clientSecret1F').val(); 50 | 51 | var response = await getToken(tokenURL, clientID, clientSecret); 52 | $('#ResponseText1F').val(response); 53 | 54 | } 55 | 56 | async function module1FgetCustomizations() { 57 | 58 | var apiurl = $('#apicustomizations').val(); 59 | var requestBody = $('#body1F').val(); 60 | var token = $('#ResponseText1F').val(); 61 | var apitype = $('#methodtype').find(":selected").val(); 62 | var apikey = 'abcdef'; 63 | 64 | $('#send1F').attr('disabled', true); 65 | 66 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 67 | $('#1Fcustomresponse').val(response); 68 | 69 | $('#send1F').attr('disabled', false); 70 | } 71 | 72 | async function module3DgetToken() { 73 | 74 | var tokenURL = $('#tokenURL3B').val(); 75 | var body = $('#body3DAVP').val(); 76 | 77 | var jsonbody = JSON.stringify(body); 78 | jsonbody = JSON.parse(jsonbody.replace(/\r?\n|\r/g, '')); 79 | 80 | $.ajax({ 81 | url: tokenURL, 82 | crossDomain: true, 83 | type: 'POST', 84 | headers: { 85 | 'Accept': '*/*', 86 | 'Content-Type': 'application/x-amz-json-1.1', 87 | 'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth' 88 | }, 89 | data: jsonbody, 90 | dataType: 'json', 91 | success: function (data) { 92 | $('#ResponseText3D').val(data.AuthenticationResult.AccessToken.toString()); 93 | }, 94 | error: function (data) { 95 | $('#ResponseText3D').val(JSON.stringify(data)); 96 | } 97 | }); 98 | } 99 | 100 | async function module3DnewPartner() { 101 | 102 | var apiurl = $('#apipartners3D').val(); 103 | var requestBody = $('#body3D').val(); 104 | var token = $('#ResponseText3D').val(); 105 | var apitype = $('#methodtype5').find(":selected").val(); 106 | var apikey = 'abcdef'; 107 | 108 | $('#send3D').attr('disabled', true); 109 | 110 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 111 | $('#3Dcustomresponse').val(response); 112 | 113 | $('#send3D').attr('disabled', false); 114 | 115 | } 116 | 117 | async function module5getToken() { 118 | 119 | var tokenURL = $('#tokenURL5').val(); 120 | var clientID = $('#clientID5').val(); 121 | var clientSecret = $('#clientSecret5').val(); 122 | 123 | var response = await getToken(tokenURL, clientID, clientSecret); 124 | $('#ResponseText5').val(response); 125 | } 126 | 127 | async function module5getCustomizations() { 128 | 129 | var apiurl = $('#apicustomizations5').val(); 130 | var requestBody = $('#body5').val(); 131 | var token = $('#ResponseText5').val(); 132 | var apitype = $('#methodtype5').find(":selected").val(); 133 | var apikey = 'abcdef'; 134 | 135 | $('#send5').attr('disabled', true); 136 | 137 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 138 | $('#5customresponse').val(response); 139 | 140 | $('#send5').attr('disabled', false); 141 | 142 | } 143 | 144 | async function module5BgetToken() { 145 | 146 | var tokenURL = $('#tokenURL5B').val(); 147 | var clientID = $('#clientID5B').val(); 148 | var clientSecret = $('#clientSecret5B').val(); 149 | 150 | var response = await getToken(tokenURL, clientID, clientSecret); 151 | $('#ResponseText5B').val(response); 152 | } 153 | 154 | async function module5BgetCustomizations() { 155 | 156 | var apiurl = $('#apicustomizations5B').val(); 157 | var requestBody = $('#body5B').val(); 158 | var token = $('#ResponseText5B').val(); 159 | var apitype = $('#methodtype5B').find(":selected").val(); 160 | var apikey = 'abcdef'; 161 | 162 | $('#send5B').attr('disabled', true); 163 | 164 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 165 | $('#5Bcustomresponse').val(response); 166 | 167 | $('#send5B').attr('disabled', false); 168 | 169 | } 170 | 171 | async function module9CgetToken() { 172 | 173 | var tokenURL = $('#tokenURL9C').val(); 174 | var clientID = $('#clientID9C').val(); 175 | var clientSecret = $('#clientSecret9C').val(); 176 | 177 | var response = await getToken(tokenURL, clientID, clientSecret); 178 | $('#ResponseText9C').val(response); 179 | } 180 | 181 | async function module9CgetCustomizations() { 182 | 183 | var apiurl = $('#apicustomizations9C').val(); 184 | var requestBody = $('#body9C').val(); 185 | var token = $('#ResponseText9C').val(); 186 | var apitype = $('#methodtype9C').find(":selected").val(); 187 | var apikey = $('#apikey9C').val(); 188 | 189 | $('#send9C').attr('disabled', true); 190 | 191 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 192 | $('#9Ccustomresponse').val(response); 193 | 194 | $('#send9C').attr('disabled', false); 195 | 196 | } 197 | 198 | async function module9EgetToken() { 199 | 200 | var tokenURL = $('#tokenURL9E').val(); 201 | var clientID = $('#clientID9E').val(); 202 | var clientSecret = $('#clientSecret9E').val(); 203 | 204 | var response = await getToken(tokenURL, clientID, clientSecret); 205 | $('#ResponseText9E').val(response); 206 | } 207 | 208 | async function module9EgetCustomizations() { 209 | 210 | var apiurl = $('#apicustomizations9E').val(); 211 | var requestBody = $('#body9E').val(); 212 | var token = $('#ResponseText9E').val(); 213 | var apitype = $('#methodtype9E').find(":selected").val(); 214 | var apikey = $('#apikey9E').val(); 215 | 216 | $('#send9E').attr('disabled', true); 217 | var responses = ''; 218 | 219 | for (let i = 0; i < 20; i++) { 220 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 221 | responses = responses + '\n\n' + response; 222 | } 223 | $('#9Ecustomresponse').val(responses); 224 | 225 | $('#send9E').attr('disabled', false); 226 | 227 | } 228 | 229 | async function module10getToken() { 230 | 231 | var tokenURL = $('#tokenURL10').val(); 232 | var clientID = $('#clientID10').val(); 233 | var clientSecret = $('#clientSecret10').val(); 234 | 235 | var response = await getToken(tokenURL, clientID, clientSecret); 236 | $('#ResponseText10').val(response); 237 | } 238 | 239 | async function module10getCustomizations() { 240 | 241 | var apiurl = $('#apicustomizations10').val(); 242 | var requestBody = ''; 243 | var token = $('#ResponseText10').val(); 244 | var apitype = $('#methodtype10').find(":selected").val(); 245 | var apikey = 'abcdef'; 246 | 247 | $('#send10').attr('disabled', true); 248 | 249 | var request = await sendRequest(apiurl, requestBody, token, apitype, apikey); 250 | $('#10customresponse').val(request); 251 | 252 | $('#send10').attr('disabled', false); 253 | 254 | } 255 | 256 | async function module10CgetToken() { 257 | 258 | var tokenURL = $('#tokenURL10C').val(); 259 | var clientID = $('#clientID10C').val(); 260 | var clientSecret = $('#clientSecret10C').val(); 261 | 262 | var response = await getToken(tokenURL, clientID, clientSecret); 263 | $('#ResponseText10C').val(response); 264 | } 265 | 266 | async function module10CgetCustomizations() { 267 | 268 | var apiurl = $('#apicustomizations10C').val(); 269 | var token = $('#ResponseText10C').val(); 270 | var requestBody = $('#body10C').val(); 271 | var apitype = $('#methodtype10C').find(":selected").val(); 272 | var apikey = 'abcdef'; 273 | 274 | $('#send10C').attr('disabled', true); 275 | 276 | var response = await sendRequest(apiurl, requestBody, token, apitype, apikey); 277 | $('#10Ccustomresponse').val(response); 278 | 279 | $('#send10C').attr('disabled', false); 280 | 281 | } 282 | 283 | async function getToken(tokenURL, clientID, clientSecret) { 284 | 285 | return await $.ajax({ 286 | url: tokenURL, 287 | crossDomain: true, 288 | type: 'POST', 289 | data: { 290 | grant_type: 'client_credentials', 291 | client_id: clientID, 292 | client_secret: clientSecret 293 | }, 294 | dataType: 'json' 295 | }) 296 | .then(function (data) { 297 | return data.access_token.toString(); 298 | }) 299 | .catch(function (data) { 300 | return JSON.stringify(data, null, 4); 301 | }); 302 | } 303 | 304 | async function sendRequest(apiurl, requestBody, token, apitype, apikey) { 305 | 306 | return await $.ajax({ 307 | url: apiurl, 308 | crossDomain: true, 309 | type: apitype, 310 | headers: { 311 | 'Authorization': token, 312 | 'Content-Type': 'application/json', 313 | 'x-api-key': apikey 314 | }, 315 | data: requestBody, 316 | dataType: 'json' 317 | }) 318 | .then(function (data) { 319 | return JSON.stringify(data, null, 4); 320 | }) 321 | .catch(function (data) { 322 | return JSON.stringify(data, null, 4); 323 | }); 324 | } 325 | 326 | var acc = document.getElementsByClassName("accordion"); 327 | var i; 328 | 329 | for (i = 0; i < acc.length; i++) { 330 | acc[i].addEventListener("click", function () { 331 | this.classList.toggle("active"); 332 | var panel = this.nextElementSibling; 333 | if (panel.style.display === "block") { 334 | panel.style.display = "none"; 335 | } else { 336 | panel.style.display = "block"; 337 | } 338 | }); 339 | } 340 | 341 | jQuery('#BaseURL').on('input', function () { 342 | $('#apipartners').val($('#BaseURL').val() + 'partners'); 343 | $('#apicustomizations0').val($('#BaseURL').val() + 'socks'); 344 | $('#apicustomizations').val($('#BaseURL').val() + 'customizations'); 345 | $('#apicustomizations3B').val($('#BaseURL').val() + 'customizations'); 346 | $('#apicustomizations3C').val($('#BaseURL').val() + 'customizations/1'); 347 | $('#apicustomizations3D').val($('#BaseURL').val() + 'customizations'); 348 | $('#apicustomizations3E').val($('#BaseURL').val() + 'customizations/1'); 349 | $('#apicustomizations5').val($('#BaseURL').val() + 'customizations'); 350 | $('#apicustomizations5B').val($('#BaseURL').val() + 'customizations'); 351 | $('#apicustomizations9C').val($('#BaseURL').val() + 'socks'); 352 | $('#apicustomizations9E').val($('#BaseURL').val() + 'socks'); 353 | $('#apicustomizations10').val($('#BaseURL').val() + 'customizations'); 354 | $('#apicustomizations10C').val($('#BaseURL').val() + 'customizations'); 355 | }); 356 | 357 | jQuery('#tokenURL').on('input', function () { 358 | $('#tokenURL1F').val($('#tokenURL').val()); 359 | $('#tokenURL3B').val($('#tokenURL').val()); 360 | $('#tokenURL3C').val($('#tokenURL').val()); 361 | $('#tokenURL3D').val($('#tokenURL').val()); 362 | $('#tokenURL3E').val($('#tokenURL').val()); 363 | $('#tokenURL5').val($('#tokenURL').val()); 364 | $('#tokenURL5B').val($('#tokenURL').val()); 365 | $('#tokenURL9C').val($('#tokenURL').val()); 366 | $('#tokenURL9E').val($('#tokenURL').val()); 367 | $('#tokenURL10').val($('#tokenURL').val()); 368 | $('#tokenURL10C').val($('#tokenURL').val()); 369 | }); 370 | 371 | 372 | jQuery('#username3D').on('input', function () { 373 | build3Dbody(); 374 | }); 375 | 376 | jQuery('#password3D').on('input', function () { 377 | build3Dbody(); 378 | }); 379 | 380 | jQuery('#clientID3D').on('input', function () { 381 | build3Dbody(); 382 | }); 383 | 384 | function build3Dbody() { 385 | var body = { 386 | "AuthFlow":"USER_PASSWORD_AUTH", 387 | "ClientId": $('#clientID3D').val(), 388 | "AuthParameters":{ 389 | "USERNAME":$('#username3D').val(), 390 | "PASSWORD":$('#password3D').val() 391 | }, 392 | "ClientMetadata":{} 393 | } 394 | $('#body3DAVP').val(JSON.stringify(body, null, 4)); 395 | } 396 | 397 | 398 | $('#methodtype').change(function () { 399 | $('#body1F').val(''); 400 | }); 401 | 402 | $('#methodtype3B').change(function () { 403 | $('#body3B').val(''); 404 | }); 405 | 406 | $('#methodtype10C').change(function () { 407 | $('#body10C').val(''); 408 | }); 409 | 410 | $('#clientID1F').change(function () { 411 | $('#clientID3B').val($('#clientID1F').val()); 412 | $('#clientID3C').val($('#clientID1F').val()); 413 | $('#clientID3D').val($('#clientID1F').val()); 414 | $('#clientID3E').val($('#clientID1F').val()); 415 | $('#clientID5').val($('#clientID1F').val()); 416 | $('#clientID5B').val($('#clientID1F').val()); 417 | $('#clientID9C').val($('#clientID1F').val()); 418 | $('#clientID9E').val($('#clientID1F').val()); 419 | $('#clientID10').val($('#clientID1F').val()); 420 | $('#clientID10C').val($('#clientID1F').val()); 421 | }); 422 | 423 | $('#clientSecret1F').change(function () { 424 | $('#clientSecret3B').val($('#clientSecret1F').val()); 425 | $('#clientSecret3C').val($('#clientSecret1F').val()); 426 | $('#clientSecret3D').val($('#clientSecret1F').val()); 427 | $('#clientSecret3E').val($('#clientSecret1F').val()); 428 | $('#clientSecret5').val($('#clientSecret1F').val()); 429 | $('#clientSecret5B').val($('#clientSecret1F').val()); 430 | $('#clientSecret9C').val($('#clientSecret1F').val()); 431 | $('#clientSecret9E').val($('#clientSecret1F').val()); 432 | $('#clientSecret10').val($('#clientSecret1F').val()); 433 | $('#clientSecret10C').val($('#clientSecret1F').val()); 434 | }); -------------------------------------------------------------------------------- /src/app/assets/rds-ca-2019-root.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEBjCCAu6gAwIBAgIJAMc0ZzaSUK51MA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD 3 | VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi 4 | MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h 5 | em9uIFJEUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkw 6 | ODIyMTcwODUwWhcNMjQwODIyMTcwODUwWjCBjzELMAkGA1UEBhMCVVMxEDAOBgNV 7 | BAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoMGUFtYXpv 8 | biBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxIDAeBgNV 9 | BAMMF0FtYXpvbiBSRFMgUm9vdCAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC 10 | AQ8AMIIBCgKCAQEArXnF/E6/Qh+ku3hQTSKPMhQQlCpoWvnIthzX6MK3p5a0eXKZ 11 | oWIjYcNNG6UwJjp4fUXl6glp53Jobn+tWNX88dNH2n8DVbppSwScVE2LpuL+94vY 12 | 0EYE/XxN7svKea8YvlrqkUBKyxLxTjh+U/KrGOaHxz9v0l6ZNlDbuaZw3qIWdD/I 13 | 6aNbGeRUVtpM6P+bWIoxVl/caQylQS6CEYUk+CpVyJSkopwJlzXT07tMoDL5WgX9 14 | O08KVgDNz9qP/IGtAcRduRcNioH3E9v981QO1zt/Gpb2f8NqAjUUCUZzOnij6mx9 15 | McZ+9cWX88CRzR0vQODWuZscgI08NvM69Fn2SQIDAQABo2MwYTAOBgNVHQ8BAf8E 16 | BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUc19g2LzLA5j0Kxc0LjZa 17 | pmD/vB8wHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJKoZIhvcN 18 | AQELBQADggEBAHAG7WTmyjzPRIM85rVj+fWHsLIvqpw6DObIjMWokpliCeMINZFV 19 | ynfgBKsf1ExwbvJNzYFXW6dihnguDG9VMPpi2up/ctQTN8tm9nDKOy08uNZoofMc 20 | NUZxKCEkVKZv+IL4oHoeayt8egtv3ujJM6V14AstMQ6SwvwvA93EP/Ug2e4WAXHu 21 | cbI1NAbUgVDqp+DRdfvZkgYKryjTWd/0+1fS8X1bBZVWzl7eirNVnHbSH2ZDpNuY 22 | 0SBd8dj5F6ld3t58ydZbrTHze7JJOd8ijySAp4/kiu9UfZWuTPABzDa/DSdz9Dk/ 23 | zPW4CXXvhLmE02TA9/HeCw3KEHIwicNuEfw= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /src/app/customUnicornAnalytics.js: -------------------------------------------------------------------------------- 1 | import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb"; // ES Modules import 2 | import dbUtil from "./dbUtils.js"; 3 | import httpUtil from "./httpUtil.js"; 4 | const demandForecastDDBTable = process.env["DEMAND_FORECAST_DDB_TABLE"]; 5 | 6 | const ddbClient = new DynamoDBClient({ region: process.env.AWS_REGION }); 7 | 8 | 9 | export const lambda_handler = async (event) => { 10 | try { 11 | const hornCount = await dbUtil.countBodyPartOptions("Horns"); 12 | const sockCount = await dbUtil.countBodyPartOptions("Socks"); 13 | const glassCount = await dbUtil.countBodyPartOptions("Glasses"); 14 | const capeCount = await dbUtil.countBodyPartOptions("Capes"); 15 | const recordTimeStamp = new Date().toISOString(); 16 | 17 | console.info(" hornCount:["+ JSON.stringify(hornCount)+"] Socks:["+ sockCount+"] Glasses:["+ glassCount+"] Capes:["+ capeCount+"] recordTimeStamp:["+ recordTimeStamp+"]"); 18 | 19 | const putItemParam = { 20 | TableName: demandForecastDDBTable, 21 | Item: { 22 | 'HornCount': { S : hornCount[1].toString() }, 23 | 'SockCount': { S : sockCount[1].toString() }, 24 | 'GlassCount': { S : glassCount[1].toString() }, 25 | 'CapeCount': { S : capeCount[1].toString() }, 26 | 'RecordTimeStamp': { S : recordTimeStamp.toString() } 27 | } 28 | } 29 | 30 | return await ddbClient.send(new PutItemCommand(putItemParam)); 31 | } 32 | catch (e) { 33 | console.error(e); 34 | return 500; 35 | } 36 | }; -------------------------------------------------------------------------------- /src/app/customizeUnicorn.js: -------------------------------------------------------------------------------- 1 | import dbUtil from "./dbUtils.js"; 2 | import httpUtil from "./httpUtil.js"; 3 | // import { permissions } from "./permissions.js"; 4 | 5 | export async function lambda_handler(event, context) { 6 | console.log("received input event: \n" + JSON.stringify(event, null, 2)); 7 | 8 | let id = (event.pathParameters || {}).id || false; 9 | 10 | if (id) { 11 | id = decodeURI(id); 12 | var resource = id; 13 | } 14 | 15 | var company; 16 | 17 | // use the copmany id from auth context 18 | if ("authorizer" in event["requestContext"] && "CompanyID" in event["requestContext"]["authorizer"]) { 19 | company = event["requestContext"]["authorizer"]["CompanyID"]; 20 | } 21 | 22 | var principalId = event["requestContext"]["authorizer"]["principalId"]; 23 | var action = event["requestContext"]["resourcePath"]; 24 | var httpMethod = event["requestContext"]["httpMethod"]; 25 | 26 | 27 | if (event.httpMethod === "GET") { 28 | // individual customization 29 | if (id) { 30 | try { 31 | // const isAllowed = await permissions.isAuthorized(principalId, action, httpMethod, resource); 32 | // if (isAllowed) { 33 | const unicornData = await dbUtil.getCustomUnicorn(id, company); 34 | 35 | 36 | console.log("successfully retrieved: " + JSON.stringify(unicornData, null, 2)); 37 | 38 | if (unicornData.length == 0) { 39 | return httpUtil.returnNotFound("Unicorn customization " + id + " does not exist."); 40 | } 41 | else { 42 | var resultRow = unicornData[0]; 43 | 44 | if (company !== undefined) { 45 | delete resultRow["COMPANY"]; 46 | return httpUtil.returnOK(resultRow); 47 | } else { 48 | return httpUtil.returnOK(resultRow); 49 | } 50 | } 51 | // } //permissions.isAuthorized 52 | // else { 53 | // return httpUtil.returnFail("Unauthorized"); 54 | // } 55 | } 56 | catch(e){ 57 | console.error(e); 58 | return httpUtil.returnFail("Error retrieving unicorn customization"); 59 | } 60 | } 61 | // LIST request 62 | else { 63 | 64 | try { 65 | // console.log("Listing AVP policies to get unicornIds for this partner"); 66 | // const policies = await permissions.listPolicies(principalId) 67 | // var unicornIds = [] 68 | 69 | // if ('policies' in policies && policies['policies'].length > 0) { 70 | // policies['policies'].forEach((policy) => unicornIds.push(policy['resource']['entityId'])); 71 | // } 72 | 73 | // var results = await dbUtil.listCustomUnicorn(company, unicornIds) 74 | var results = await dbUtil.listCustomUnicorn(company); 75 | 76 | console.log("successfully retrieved " + results.length + " custom unicorns."); 77 | 78 | results = results.map(item => { 79 | delete item["COMPANY"]; 80 | return item; 81 | }); 82 | return httpUtil.returnOK(results); 83 | 84 | } catch(e){ 85 | console.error(e); 86 | return httpUtil.returnFail("Error retrieving unicorn customizations"); 87 | } 88 | } 89 | // create unicorn customization 90 | } else if (event.httpMethod === "POST") { 91 | const request = JSON.parse(event["body"]); 92 | 93 | if ("company" in request) { 94 | company = request['company']; 95 | } 96 | 97 | const name = request['name']; 98 | 99 | if (company === undefined) { 100 | console.log("no company specified"); 101 | return httpUtil.returnBadInput("Company not valid"); 102 | } 103 | 104 | const imageUrl = request['imageUrl']; 105 | const sock = request['sock']; 106 | const horn = request['horn']; 107 | const glasses = request['glasses']; 108 | const cape = request['cape']; 109 | try { 110 | const db_results = await dbUtil.createCustomUnicorn(name, company, imageUrl, sock, horn, glasses, cape); 111 | console.log("successfully inserted custom unicorn."); 112 | 113 | // create an AVP policy 114 | // console.log("creating AVP policy for the unicorn."); 115 | // await permissions.createTemplateLinkedPolicy(principalId, db_results['customUnicornId']) 116 | 117 | return httpUtil.returnOK(db_results); 118 | } 119 | catch(e) { 120 | console.error(e); 121 | return httpUtil.returnFail("Error creating unicorn"); 122 | } 123 | // delete unicorn customization 124 | } else if (event.httpMethod === "DELETE") { 125 | try { 126 | // check if allowed to delete 127 | // const isAllowed = await permissions.isAuthorized(principalId, action, httpMethod, resource) 128 | // if (isAllowed) { 129 | const results = await dbUtil.deleteCustomUnicorn(id, company); 130 | 131 | console.log("successfully deleted custom unicorn " + results); 132 | // await permissions.deletePolicy(principalId, resource) 133 | return httpUtil.returnOK(results); 134 | // } 135 | // else { 136 | // return httpUtil.returnFail("Unauthorized"); 137 | // } 138 | } catch(e) { 139 | console.error(e); 140 | return httpUtil.returnFail("Error deleting unicorn customization"); 141 | } 142 | } else { 143 | console.log("Error: unsupported HTTP method (" + event.httpMethod + ")"); 144 | return {statusCode: 501}; 145 | } 146 | }; 147 | 148 | -------------------------------------------------------------------------------- /src/app/dbUtils.js: -------------------------------------------------------------------------------- 1 | import mysql from 'mysql'; 2 | 3 | const CUSTOM_UNICORN_TABLE = "Custom_Unicorns"; 4 | const PARTNER_COMPANY_TABLE = "Companies"; 5 | 6 | /* 7 | * Host 8 | */ 9 | 10 | const host = "secure-aurora-cluster.cluster-xxxxxxx.xxxxxxx.rds.amazonaws.com" 11 | 12 | class Database { 13 | query(sql, connection, args) { 14 | return new Promise((resolve, reject) => { 15 | connection.query(sql, args, (errorQuerying, rows) => { 16 | connection.end(errClosing => { 17 | if (errClosing) { 18 | console.log("error closing connection"); 19 | console.error(errClosing); 20 | } 21 | if (errorQuerying) { 22 | return reject(errorQuerying); 23 | } 24 | resolve(rows); 25 | }); 26 | }); 27 | }); 28 | } 29 | 30 | close(connection) { 31 | return new Promise((resolve, reject) => { 32 | connection.end(err => { 33 | if (err) 34 | return reject(err); 35 | resolve(); 36 | }); 37 | }); 38 | } 39 | 40 | connectToDb(dbConfig) { 41 | return new Promise((resolve, reject) => { 42 | resolve(mysql.createConnection(dbConfig)); 43 | }); 44 | }; 45 | 46 | getDbConfig() { 47 | console.log("getDbConfig()"); 48 | return new Promise((resolve, reject) => { 49 | resolve({ 50 | host: host, 51 | user: "admin", 52 | password: "Corp123!", 53 | database: "unicorn_customization", 54 | multipleStatements: true 55 | }); 56 | }); 57 | }; 58 | } 59 | 60 | function executeDBquery(query) { 61 | const dbConn = new Database(); 62 | return dbConn.getDbConfig() 63 | .then(dbConn.connectToDb) 64 | .then(dbConn.query.bind(this, query)); 65 | } 66 | 67 | export const databaseFunctions = { 68 | countBodyPartOptions: async function (bodyPart) { 69 | const query = `SELECT count(*) FROM ${bodyPart}`; 70 | console.log("query for DB: " + query); 71 | const results = await executeDBquery(query); 72 | console.log(JSON.stringify(results)); 73 | let count = results[0]["count(*)"]; 74 | console.log(bodyPart + " count: " + count); 75 | return [bodyPart, count]; 76 | }, 77 | 78 | listBodyPartOptions: async function (bodyPart) { 79 | const query = `SELECT * FROM ${bodyPart}`; 80 | console.log("query for DB: " + query); 81 | return await executeDBquery(query); 82 | }, 83 | 84 | addPartnerCompany: async function (companyName) { 85 | const insertQuery = `INSERT INTO ${PARTNER_COMPANY_TABLE} (NAME) VALUES ('${companyName}')`; 86 | console.log("query for insert:" + insertQuery); 87 | const results = await executeDBquery(insertQuery); 88 | console.log(JSON.stringify(results, null, 2)); 89 | let insertId = results.insertId; 90 | console.log("insert id: " + insertId); 91 | return { "companyId": insertId }; 92 | }, 93 | 94 | createCustomUnicorn: async function (name, company, imageUrl, sock, horn, glasses, cape) { 95 | const dbConn = new Database(); 96 | const insertQuery = `INSERT INTO ${CUSTOM_UNICORN_TABLE} (NAME, COMPANY, IMAGEURL, SOCK, HORN, GLASSES, CAPE) VALUES ('${name}',${company},'${imageUrl}',${sock},${horn},${glasses},${cape})`; 97 | console.log("query for insert:" + insertQuery); 98 | const results = await dbConn.getDbConfig() 99 | .then(dbConn.connectToDb) 100 | .then(dbConn.query.bind(this, insertQuery)); 101 | console.log(JSON.stringify(results, null, 2)); 102 | let insertId = results.insertId; 103 | if (insertId === undefined) 104 | { 105 | insertId = results[0].insertId; 106 | } 107 | console.log("insert id: " + insertId); 108 | return { "customUnicornId": insertId }; 109 | }, 110 | 111 | listCustomUnicorn: async function (company, unicornIds = []) { 112 | let query = `SELECT * FROM ${CUSTOM_UNICORN_TABLE}`; 113 | console.log("query for compa" + company) 114 | if (company !== null && company !== undefined && company !== "") { 115 | query += ` WHERE COMPANY = ${company}`; 116 | } 117 | //if (unicornIds.length > 0) { 118 | // query += " AND ID IN (" + unicornIds.join(",") + ")"; 119 | //} 120 | console.log("query for DB: " + query); 121 | return await executeDBquery(query); 122 | }, 123 | 124 | getCustomUnicorn: async function (id, company) { 125 | let query = `SELECT * FROM ${CUSTOM_UNICORN_TABLE} WHERE ID = ${id}`; 126 | if (company !== null && company !== undefined && company !== "") { 127 | query += ` AND COMPANY = ${company}`; 128 | } 129 | console.log("query for DB: " + query); 130 | return await executeDBquery(query); 131 | }, 132 | 133 | deleteCustomUnicorn: async function (id, company) { 134 | let query = `DELETE FROM ${CUSTOM_UNICORN_TABLE} WHERE ID = ${id}`; 135 | if (company !== null && company !== undefined && company !== "") { 136 | query += ` AND COMPANY = ${company}`; 137 | } 138 | console.log("query for DB: " + query); 139 | const results = await executeDBquery(query); 140 | if (results.affectedRows == 1) { 141 | return { "id": id }; 142 | } else { 143 | return {}; 144 | } 145 | } 146 | } 147 | 148 | export default databaseFunctions; 149 | -------------------------------------------------------------------------------- /src/app/httpUtil.js: -------------------------------------------------------------------------------- 1 | export const returnFail = (message) => { 2 | return { 3 | statusCode: 500, 4 | headers: { 5 | "Access-Control-Allow-Headers" : "Content-Type", 6 | "Access-Control-Allow-Origin": "*", 7 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 8 | }, 9 | body: JSON.stringify(message) 10 | }; 11 | }; 12 | 13 | export const returnBadInput = (message) => { 14 | return { 15 | statusCode: 400, 16 | headers: { 17 | "Access-Control-Allow-Headers" : "Content-Type", 18 | "Access-Control-Allow-Origin": "*", 19 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 20 | }, 21 | body: JSON.stringify(message) 22 | }; 23 | }; 24 | 25 | export const returnNotFound = (message) => { 26 | return { 27 | statusCode: 404, 28 | headers: { 29 | "Access-Control-Allow-Headers" : "Content-Type", 30 | "Access-Control-Allow-Origin": "*", 31 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 32 | }, 33 | body: JSON.stringify(message) 34 | }; 35 | }; 36 | 37 | export const returnAccessDenied = (message) => { 38 | return { 39 | statusCode: 403, 40 | headers: { 41 | "Access-Control-Allow-Headers" : "Content-Type", 42 | "Access-Control-Allow-Origin": "*", 43 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 44 | }, 45 | body: JSON.stringify(message) 46 | }; 47 | }; 48 | 49 | export const returnOK = (message) => { 50 | return { 51 | statusCode: 200, 52 | headers: { 53 | "Access-Control-Allow-Headers" : "Content-Type", 54 | "Access-Control-Allow-Origin": "*", 55 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 56 | }, 57 | body: JSON.stringify(message) 58 | }; 59 | }; 60 | 61 | const exportObject = { 62 | returnFail, 63 | returnBadInput, 64 | returnNotFound, 65 | returnAccessDenied, 66 | returnOK 67 | }; 68 | 69 | export default exportObject; -------------------------------------------------------------------------------- /src/app/managePartners.js: -------------------------------------------------------------------------------- 1 | import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb"; // ES Modules import 2 | import { CognitoIdentityProviderClient, CreateUserPoolClientCommand } from "@aws-sdk/client-cognito-identity-provider"; 3 | import dbUtil from "./dbUtils.js"; 4 | import httpUtil from "./httpUtil.js"; 5 | 6 | const SCOPES = ['WildRydes/CustomizeUnicorn']; 7 | const companyDDBTable = process.env["PARTNER_DDB_TABLE"]; 8 | 9 | const ddbClient = new DynamoDBClient({ region: process.env.AWS_REGION }); 10 | const cognitoClient = new CognitoIdentityProviderClient({ region: process.env.AWS_REGION }); 11 | 12 | export const lambda_handler = async (event) => { 13 | console.log("received input event: \n" + JSON.stringify(event, null, 2)); 14 | 15 | let id = (event.pathParameters || {}).id || false; 16 | 17 | if (!("authorizer" in event["requestContext"])) { 18 | console.log("Error: unsupported HTTP method (" + event.httpMethod + ")"); 19 | return httpUtil.returnAccessDenied("You must implement the custom authorizers before you can call this API."); 20 | } 21 | 22 | if (event.httpMethod === "POST") { 23 | try { 24 | const request = JSON.parse(event["body"]); 25 | const company = request["name"]; 26 | 27 | let companyId; 28 | let clientId; 29 | let clientSecret; 30 | 31 | const results = await dbUtil.addPartnerCompany(company); 32 | console.log("successfully added partner company."); 33 | companyId = results["companyId"]; 34 | 35 | const createUserPoolClientParams = { 36 | ClientName: company, 37 | UserPoolId: process.env["USER_POOL_ID"], 38 | GenerateSecret: true, 39 | RefreshTokenValidity: 1, 40 | AllowedOAuthFlows: ['client_credentials'], 41 | AllowedOAuthScopes: SCOPES, 42 | AllowedOAuthFlowsUserPoolClient: true 43 | }; 44 | const createUserPoolClientResponse = await cognitoClient.send(new CreateUserPoolClientCommand(createUserPoolClientParams)); 45 | clientId = createUserPoolClientResponse.UserPoolClient.ClientId; 46 | clientSecret = createUserPoolClientResponse.UserPoolClient.ClientSecret; 47 | console.log("successfully created cognito client: " + clientId); 48 | 49 | const putItemParam = { 50 | TableName: companyDDBTable, 51 | Item: { 52 | 'ClientID': { S: clientId }, 53 | 'CompanyID': { S: companyId.toString() } 54 | } 55 | }; 56 | console.log("DDB params: " + JSON.stringify(putItemParam)); 57 | await ddbClient.send(new PutItemCommand(putItemParam)); 58 | console.log("success writing to ddb ID mapping"); 59 | 60 | let returnMessage = { "ClientID": clientId, "ClientSecret": clientSecret }; 61 | return httpUtil.returnOK(returnMessage); 62 | } catch (e) { 63 | console.error(e); 64 | console.log("error code: " + e.code); 65 | if (e.code === "ER_DUP_ENTRY") { 66 | return httpUtil.returnBadInput("Company already registered"); 67 | } else { 68 | return httpUtil.returnFail("Error Encountered"); 69 | } 70 | } 71 | } else { 72 | console.log("Error: unsupported HTTP method (" + event.httpMethod + ")"); 73 | return { statusCode: 501 }; 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "babel-core": "*", 4 | "babel-plugin-transform-flow-strip-types": "*", 5 | "babel-preset-es2017": "*", 6 | "minimatch": "^2.0.10", 7 | "mysql": "^2.16.0" 8 | }, 9 | "name": "customize-unicorn", 10 | "version": "1.0.0", 11 | "type": "module", 12 | "description": "lambda functions that allows third party companies to customize unicorn to place ads", 13 | "main": "main.js", 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "author": "Ignacio García, Angela Wang", 19 | "license": "Apache-2.0" 20 | } 21 | -------------------------------------------------------------------------------- /src/app/permissions.js: -------------------------------------------------------------------------------- 1 | import { VerifiedPermissionsClient, IsAuthorizedCommand, CreatePolicyCommand, ListPoliciesCommand, DeletePolicyCommand } from "@aws-sdk/client-verifiedpermissions"; // ES Modules import 2 | 3 | const clientIsAuth = new VerifiedPermissionsClient({}); 4 | const clientCreatePolicy = new VerifiedPermissionsClient({}); 5 | const clientListPolicies = new VerifiedPermissionsClient({}); 6 | const clientDeletePolicy = new VerifiedPermissionsClient({}); 7 | 8 | const policyStoreId = process.env.AVP_POLICY_STORE_ID 9 | const policyTemplateId = process.env.AVP_POLICY_TEMPLATE_ID 10 | 11 | // define all actions for readbility 12 | const ACTIONS = { 13 | 'GET:/customizations/{id}': 'GetUnicorn', 14 | 'DELETE:/customizations/{id}': 'DeleteUnicorn' 15 | } 16 | 17 | export const permissions = { 18 | 19 | isAuthorized: async function (principal, action, httpMethod, resource, entities = null) { 20 | action = ACTIONS[httpMethod + ':' + action] || "unknown_action" 21 | var params = { 22 | policyStoreId: policyStoreId, 23 | principal: { 24 | entityId: principal, 25 | entityType: 'WildRydes::User' 26 | }, 27 | action: { 28 | actionId: action, 29 | actionType: 'WildRydes::Action' 30 | }, 31 | resource: { 32 | entityId: resource, 33 | entityType: 'WildRydes::Unicorn' 34 | } 35 | } 36 | console.log('AVP params:' + JSON.stringify(params)) 37 | 38 | const command = new IsAuthorizedCommand(params); 39 | const response = await clientIsAuth.send(command); 40 | 41 | console.log('AVP response:' + JSON.stringify(response)) 42 | 43 | if (response['decision'] === 'ALLOW') { 44 | return true 45 | } 46 | return false 47 | }, 48 | 49 | createTemplateLinkedPolicy: async function(principal, resource) { 50 | var params = { 51 | definition: { 52 | templateLinked: { 53 | policyTemplateId: policyTemplateId, 54 | principal: { 55 | entityId: principal, 56 | entityType: 'WildRydes::User' 57 | }, 58 | resource: { 59 | entityId: resource.toString(), 60 | entityType: 'WildRydes::Unicorn' 61 | } 62 | } 63 | }, 64 | policyStoreId: policyStoreId 65 | }; 66 | 67 | console.log('AVP params:' + JSON.stringify(params)); 68 | const commandCreatePol = new CreatePolicyCommand(params); 69 | const responseCreatePol = await clientCreatePolicy.send(commandCreatePol); 70 | console.log('AVP response:' + JSON.stringify(responseCreatePol)); 71 | return responseCreatePol; 72 | }, 73 | 74 | listPolicies: async function (principal, resource = null) { 75 | var params = { 76 | policyStoreId: policyStoreId, 77 | filter: { 78 | policyTemplateId: policyTemplateId, 79 | policyType: "TEMPLATE_LINKED", 80 | principal: { 81 | identifier: { 82 | entityId: principal, 83 | entityType: 'WildRydes::User' 84 | } 85 | } 86 | } 87 | } 88 | 89 | if (resource) { 90 | params['filter']['resource'] = { 91 | identifier: { 92 | entityId: resource.toString(), 93 | entityType: 'WildRydes::Unicorn' 94 | } 95 | } 96 | } 97 | 98 | console.log('AVP params:' + JSON.stringify(params)) 99 | const commandListPol = new ListPoliciesCommand(params); 100 | const responseListPol = await clientListPolicies.send(commandListPol); 101 | console.log('AVP response:' + JSON.stringify(responseListPol)) 102 | return responseListPol 103 | }, 104 | 105 | deletePolicy: async function(principal, resource) { 106 | const policies = await this.listPolicies(principal, resource); 107 | var responseDeletePolicy = {} 108 | 109 | if ('policies' in policies && policies['policies'].length > 0) { 110 | var policyId = policies['policies'][0]['policyId'] 111 | 112 | var params = { 113 | policyStoreId: policyStoreId, 114 | policyId: policyId 115 | } 116 | 117 | console.log('AVP params:' + JSON.stringify(params)) 118 | const commandDeletePolicy = new DeletePolicyCommand(params); 119 | responseDeletePolicy = await clientDeletePolicy.send(commandDeletePolicy); 120 | console.log('AVP response:' + JSON.stringify(responseDeletePolicy)) 121 | } 122 | else { 123 | console.log('No AVP policies found') 124 | } 125 | 126 | return responseDeletePolicy; 127 | } 128 | } 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/app/unicornParts.js: -------------------------------------------------------------------------------- 1 | import dbUtil from "./dbUtils.js"; 2 | import httpUtil from "./httpUtil.js"; 3 | 4 | export const lambda_handler = async (event) => { 5 | 6 | //console.log("received input event: \n" + JSON.stringify(event, null, 2)); 7 | 8 | if (event['httpMethod'] == 'GET') { 9 | 10 | var bodyPartToQuery = null; 11 | 12 | switch (event["resource"]) { 13 | case "/horns": 14 | bodyPartToQuery = "Horns"; 15 | break; 16 | case "/socks": 17 | bodyPartToQuery = "Socks"; 18 | break; 19 | case "/glasses": 20 | bodyPartToQuery = "Glasses"; 21 | break; 22 | case "/capes": 23 | bodyPartToQuery = "Capes"; 24 | break; 25 | } 26 | console.log("body part to query: " + bodyPartToQuery); 27 | 28 | if (bodyPartToQuery === null) { 29 | let response = { 30 | statusCode: 400, 31 | headers: { 32 | "Access-Control-Allow-Headers" : "Content-Type", 33 | "Access-Control-Allow-Origin": "*", 34 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 35 | }, 36 | body: "Unsupported body part" 37 | }; 38 | return response; 39 | } 40 | 41 | 42 | var horns = await dbUtil.listBodyPartOptions(bodyPartToQuery); 43 | console.log("successfully retrieved " + horns.length + " records."); 44 | 45 | let response = { 46 | statusCode: 200, 47 | headers: { 48 | "Access-Control-Allow-Headers" : "Content-Type", 49 | "Access-Control-Allow-Origin": "*", 50 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 51 | }, 52 | body: JSON.stringify(horns) 53 | }; 54 | console.log(response); 55 | return response; 56 | 57 | } else { 58 | let response = { 59 | statusCode: 400, 60 | headers: { 61 | "Access-Control-Allow-Headers" : "Content-Type", 62 | "Access-Control-Allow-Origin": "*", 63 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET" 64 | }, 65 | body: "Unsupported method" 66 | }; 67 | return response; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /src/authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customize-unicorn-authorizer", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "jsonwebtoken": "^8.0.1", 14 | "jwk-to-pem": "^1.2.6", 15 | "request": "^2.83.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/init/db/queries.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS unicorn_customization; 2 | USE unicorn_customization; 3 | 4 | CREATE TABLE IF NOT EXISTS Companies ( 5 | ID int NOT NULL AUTO_INCREMENT, 6 | NAME varchar(255) NOT NULL UNIQUE, 7 | PRIMARY KEY (ID) 8 | ); 9 | 10 | CREATE TABLE IF NOT EXISTS Socks ( 11 | ID int NOT NULL AUTO_INCREMENT, 12 | NAME varchar(255) NOT NULL, 13 | PRICE decimal(5,2) NOT NULL, 14 | PRIMARY KEY (ID) 15 | ); 16 | CREATE TABLE IF NOT EXISTS Horns ( 17 | ID int NOT NULL AUTO_INCREMENT, 18 | NAME varchar(255) NOT NULL, 19 | PRICE decimal(5,2) NOT NULL, 20 | PRIMARY KEY (ID) 21 | ); 22 | CREATE TABLE IF NOT EXISTS Glasses ( 23 | ID int NOT NULL AUTO_INCREMENT, 24 | NAME varchar(255) NOT NULL, 25 | PRICE decimal(5,2) NOT NULL, 26 | PRIMARY KEY (ID) 27 | ); 28 | CREATE TABLE IF NOT EXISTS Capes ( 29 | ID int NOT NULL AUTO_INCREMENT, 30 | NAME varchar(255) NOT NULL, 31 | PRICE decimal(5,2) NOT NULL, 32 | PRIMARY KEY (ID) 33 | ); 34 | 35 | CREATE TABLE IF NOT EXISTS Custom_Unicorns ( 36 | ID int NOT NULL AUTO_INCREMENT, 37 | NAME varchar(255) NOT NULL, 38 | -- AD INT NOT NULL, 39 | COMPANY INT NOT NULL, 40 | IMAGEURL varchar(255) NOT NULL, 41 | SOCK INT NOT NULL, 42 | HORN INT NOT NULL, 43 | GLASSES INT NOT NULL, 44 | CAPE INT NOT NULL, 45 | -- FOREIGN KEY (AD) REFERENCES Ads(ID), 46 | FOREIGN KEY (COMPANY) REFERENCES Companies(ID), 47 | FOREIGN KEY (SOCK) REFERENCES Socks(ID), 48 | FOREIGN KEY (HORN) REFERENCES Horns(ID), 49 | FOREIGN KEY (GLASSES) REFERENCES Glasses(ID), 50 | FOREIGN KEY (CAPE) REFERENCES Capes(ID), 51 | PRIMARY KEY (ID) 52 | ); 53 | 54 | 55 | INSERT INTO Socks (NAME,PRICE) VALUES 56 | ("Basic", 0.00), 57 | ("Branded", 1.00); 58 | 59 | INSERT INTO Horns (NAME,PRICE) VALUES 60 | ("White", 0.00), 61 | ("Red", 1.00), 62 | ("Blue", 1.00), 63 | ("Purple", 1.00), 64 | ("Green", 1.00), 65 | ("Yellow", 1.00), 66 | ("Silver", 2.00), 67 | ("Gold", 3.00); 68 | 69 | INSERT INTO Glasses (NAME,PRICE) VALUES 70 | ("Basic", 1.00), 71 | ("Elvis Presley style", 2.50), 72 | ("John Lennon style", 2.50), 73 | ("Kanye West style",2.50), 74 | ("Hearts", 2.00), 75 | ("Stars", 2.00), 76 | ("Butterfly", 2.00); 77 | 78 | INSERT INTO Capes (NAME,PRICE) VALUES 79 | ("White", 0.00), 80 | ("Rainbow", 2.00), 81 | ("Branded on White", 3.00), 82 | ("Branded on Rainbow", 4.00); 83 | 84 | INSERT INTO Companies (NAME) VALUES ("Placeholder company"); 85 | 86 | 87 | /* 88 | 89 | INSERT INTO Custom_Unicorns (NAME, COMPANY, IMAGEURL, SOCK, HORN, GLASSES, CAPE) VALUES ("Cool new phone",1, "https://mybucket.s3.amazonaws.com/myimage", 2,1,2,4); 90 | 91 | SELECT * FROM Custom_Unicorns; 92 | 93 | */ -------------------------------------------------------------------------------- /src/init/init-template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Initial resource setup for serverless security workshop 3 | 4 | Parameters: 5 | DbPassword: 6 | Type: String 7 | NoEcho: true 8 | 9 | Resources: 10 | PubPrivateVPC: 11 | Type: 'AWS::EC2::VPC' 12 | Properties: 13 | CidrBlock: 10.0.0.0/16 14 | Tags: 15 | - Key: Name 16 | Value: Secure-Serverless 17 | 18 | 19 | PublicSubnet1: 20 | Type: 'AWS::EC2::Subnet' 21 | Properties: 22 | VpcId: !Ref PubPrivateVPC 23 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 24 | CidrBlock: 10.0.1.0/24 25 | MapPublicIpOnLaunch: true 26 | Tags: 27 | - Key: Name 28 | Value: pub-subnet-1-Secure-Serverless 29 | 30 | PublicSubnet2: 31 | Type: 'AWS::EC2::Subnet' 32 | Properties: 33 | VpcId: !Ref PubPrivateVPC 34 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 35 | CidrBlock: 10.0.2.0/24 36 | MapPublicIpOnLaunch: true 37 | Tags: 38 | - Key: Name 39 | Value: pub-subnet-3-Secure-Serverless 40 | 41 | PrivateSubnet1: 42 | Type: 'AWS::EC2::Subnet' 43 | Properties: 44 | VpcId: !Ref PubPrivateVPC 45 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 46 | CidrBlock: 10.0.3.0/24 47 | MapPublicIpOnLaunch: false 48 | Tags: 49 | - Key: Name 50 | Value: priv-subnet-1-Secure-Serverless 51 | 52 | PrivateSubnet2: 53 | Type: 'AWS::EC2::Subnet' 54 | Properties: 55 | VpcId: !Ref PubPrivateVPC 56 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 57 | CidrBlock: 10.0.4.0/24 58 | MapPublicIpOnLaunch: false 59 | Tags: 60 | - Key: Name 61 | Value: priv-subnet-2-Secure-Serverless 62 | 63 | 64 | InternetGateway: 65 | Type: 'AWS::EC2::InternetGateway' 66 | 67 | GatewayToInternet: 68 | Type: 'AWS::EC2::VPCGatewayAttachment' 69 | Properties: 70 | VpcId: !Ref PubPrivateVPC 71 | InternetGatewayId: !Ref InternetGateway 72 | 73 | PublicRouteTable: 74 | Type: 'AWS::EC2::RouteTable' 75 | Properties: 76 | VpcId: !Ref PubPrivateVPC 77 | 78 | PublicRoute: 79 | Type: 'AWS::EC2::Route' 80 | DependsOn: GatewayToInternet 81 | Properties: 82 | RouteTableId: !Ref PublicRouteTable 83 | DestinationCidrBlock: 0.0.0.0/0 84 | GatewayId: !Ref InternetGateway 85 | 86 | PublicSubnet1RouteTableAssociation: 87 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 88 | Properties: 89 | SubnetId: !Ref PublicSubnet1 90 | RouteTableId: !Ref PublicRouteTable 91 | 92 | PublicSubnet2RouteTableAssociation: 93 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 94 | Properties: 95 | SubnetId: !Ref PublicSubnet2 96 | RouteTableId: !Ref PublicRouteTable 97 | 98 | NatGateway: 99 | Type: "AWS::EC2::NatGateway" 100 | DependsOn: NatPublicIP 101 | Properties: 102 | AllocationId: !GetAtt NatPublicIP.AllocationId 103 | SubnetId: !Ref PublicSubnet1 104 | 105 | NatPublicIP: 106 | Type: "AWS::EC2::EIP" 107 | DependsOn: PubPrivateVPC 108 | Properties: 109 | Domain: vpc 110 | 111 | PrivateRouteTable: 112 | Type: 'AWS::EC2::RouteTable' 113 | Properties: 114 | VpcId: !Ref PubPrivateVPC 115 | 116 | PrivateRoute: 117 | Type: 'AWS::EC2::Route' 118 | Properties: 119 | RouteTableId: !Ref PrivateRouteTable 120 | DestinationCidrBlock: 0.0.0.0/0 121 | NatGatewayId: !Ref NatGateway 122 | 123 | PrivateSubnet1RouteTableAssociation: 124 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 125 | Properties: 126 | SubnetId: !Ref PrivateSubnet1 127 | RouteTableId: !Ref PrivateRouteTable 128 | 129 | PrivateSubnet2RouteTableAssociation: 130 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 131 | Properties: 132 | SubnetId: !Ref PrivateSubnet2 133 | RouteTableId: !Ref PrivateRouteTable 134 | 135 | Cloud9Environment: 136 | Type: AWS::Cloud9::EnvironmentEC2 137 | Properties: 138 | Description: Use Cloud 9 as the default environment to launch your operations. 139 | InstanceType: t2.micro 140 | Name: Secure-Serverless-Cloud9 141 | SubnetId: !Ref PublicSubnet1 142 | 143 | DeploymentsS3Bucket: 144 | Type: AWS::S3::Bucket 145 | 146 | 147 | AuroraSubnetGroup: 148 | Type: "AWS::RDS::DBSubnetGroup" 149 | Properties: 150 | DBSubnetGroupDescription: Subnet for Serverless Aurora 151 | DBSubnetGroupName: secure-serverless-aurora 152 | SubnetIds: 153 | - !Ref PrivateSubnet1 154 | - !Ref PrivateSubnet2 155 | 156 | AuroraSecurityGroup: 157 | Type: AWS::EC2::SecurityGroup 158 | Properties: 159 | GroupDescription: Serverless Aurora Access trhough the VPC 160 | VpcId: 161 | Ref: PubPrivateVPC 162 | SecurityGroupIngress: 163 | - IpProtocol: tcp 164 | FromPort: 3306 165 | ToPort: 3306 166 | CidrIp: 10.0.0.0/16 167 | 168 | # should we start with a broad SG and narrow it down as part of workshop? 169 | # move this to the sam template instead? 170 | LambdaSecurityGroup: 171 | Type: AWS::EC2::SecurityGroup 172 | Properties: 173 | GroupDescription: SecurityGroup for lambda function 174 | VpcId: 175 | Ref: PubPrivateVPC 176 | SecurityGroupEgress: 177 | - Description: Access to Aurora MYSQL 178 | FromPort: 3306 179 | IpProtocol: tcp 180 | DestinationSecurityGroupId: !Ref AuroraSecurityGroup 181 | ToPort: 3306 182 | - Description: Access to Secrets Manager 183 | FromPort: 80 184 | IpProtocol: tcp 185 | CidrIp: 0.0.0.0/0 186 | ToPort: 80 187 | - Description: Access to Secrets Manager SSL 188 | FromPort: 443 189 | IpProtocol: tcp 190 | CidrIp: 0.0.0.0/0 191 | ToPort: 443 192 | - Description: Access to Secrets Manager SSL 193 | FromPort: 53 194 | IpProtocol: udp 195 | CidrIp: 0.0.0.0/0 196 | ToPort: 53 197 | 198 | AuroraDBInstance: 199 | Type: AWS::RDS::DBInstance 200 | Properties: 201 | DBInstanceClass: db.t3.medium 202 | Engine: aurora-mysql 203 | DBClusterIdentifier: !Ref AuroraDBCluster 204 | 205 | AuroraDBCluster: 206 | Type: AWS::RDS::DBCluster 207 | DependsOn: AuroraSubnetGroup 208 | DeletionPolicy: Delete 209 | Properties: 210 | MasterUsername: admin 211 | MasterUserPassword: !Ref DbPassword 212 | Engine: aurora-mysql 213 | DBSubnetGroupName: !Ref AuroraSubnetGroup 214 | VpcSecurityGroupIds: 215 | - !Ref AuroraSecurityGroup 216 | 217 | Outputs: 218 | 219 | AuroraEndpoint: 220 | Description: Aurora endpoint for aurora database 221 | Value: !GetAtt AuroraDBCluster.Endpoint.Address 222 | 223 | DeploymentS3Bucket: 224 | Description: S3 Bucket to place your SAM deployments 225 | Value: !Ref DeploymentsS3Bucket 226 | 227 | LambdaSecurityGroup: 228 | Description: SecurityGroup for lambda function 229 | Value: !Ref LambdaSecurityGroup 230 | Export: 231 | Name: 232 | !Sub ${AWS::StackName}-LambdaSecurityGroup 233 | PublicSubnet1: 234 | Description: PublicSubnet1 235 | Value: !Ref PublicSubnet1 236 | Export: 237 | Name: 238 | !Sub ${AWS::StackName}-PublicSubnet1 239 | PublicSubnet2: 240 | Description: PublicSubnet2 241 | Value: !Ref PublicSubnet2 242 | Export: 243 | Name: 244 | !Sub ${AWS::StackName}-PublicSubnet2 245 | PrivateSubnet1: 246 | Description: PrivateSubnet1 247 | Value: !Ref PrivateSubnet1 248 | Export: 249 | Name: 250 | !Sub ${AWS::StackName}-PrivateSubnet1 251 | PrivateSubnet2: 252 | Description: PrivateSubnet2 253 | Value: !Ref PrivateSubnet2 254 | Export: 255 | Name: 256 | !Sub ${AWS::StackName}-PrivateSubnet2 257 | -------------------------------------------------------------------------------- /src/retired/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Cloud9 Bootstrap Script 4 | # 5 | # Tested on Amazon Linux 2 6 | # 7 | # 1. Installs JQ 8 | # 2. Creates Environment Variables 9 | # 3. NPM Installs and Deploys Application 10 | # 11 | # Usually takes less than one minute to complete 12 | 13 | set -euxo pipefail 14 | 15 | RED='\033[0;31m' 16 | YELLOW='\033[1;33m' 17 | NC='\033[0m' 18 | 19 | function _logger() { 20 | echo -e "$(date) ${YELLOW}[*] $@ ${NC}" 21 | } 22 | 23 | function install_utility_tools() { 24 | _logger "[+] Installing jq" 25 | sudo yum install -y jq 26 | } 27 | 28 | function setstackname() { 29 | _logger "[+] Setting StackName" 30 | export stack_name=$(aws cloudformation list-stacks --query 'StackSummaries[].StackName'| grep mod | sed 's/"//g') 31 | echo $stack_name 32 | } 33 | 34 | 35 | function setclustername() { 36 | _logger "[+] Setting Auora Cluster name" 37 | sed -i "s/secure-aurora-cluster.cluster-xxxxxxx.xxxxxxx.rds.amazonaws.com/$AuroraEndpoint/g" /home/ec2-user/environment/aws-serverless-security-workshop/src/app/dbUtils.js 38 | } 39 | 40 | function setregion() { 41 | _logger "[+] Setting region" 42 | echo export "REGION=$(curl --silent http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)" >> ~/.bashrc 43 | echo "REGION=$(curl --silent http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)" >>/home/ec2-user/environment/aws-serverless-security-workshop/scratch.txt 44 | } 45 | 46 | function setcfoutput() { 47 | # load outputs to env vars 48 | _logger "[+] get Cloudformation outputs and set variables" 49 | for output in $(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[].OutputKey' --output text) 50 | do 51 | export $output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text) 52 | echo "$output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text)" >> ~/.bashrc 53 | echo "$output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text)" >> /home/ec2-user/environment/aws-serverless-security-workshop/scratch.txt 54 | #eval "echo $output : \"\$$output\"" 55 | done 56 | 57 | } 58 | 59 | function deployapp() { 60 | _logger "[+] Deploying app" 61 | cd ~/environment/aws-serverless-security-workshop/src/app 62 | npm install 63 | cd ~/environment/aws-serverless-security-workshop/src 64 | sam deploy --stack-name CustomizeUnicorns --s3-bucket $DeploymentS3Bucket --capabilities CAPABILITY_IAM || true 65 | cd ~/environment/aws-serverless-security-workshop/ 66 | 67 | } 68 | 69 | function getapiurl(){ 70 | sam_stack_name="CustomizeUnicorns" 71 | echo " " >> /home/ec2-user/environment/aws-serverless-security-workshop/scratch.txt 72 | echo "-------------------------------------------" >> /home/ec2-user/environment/aws-serverless-security-workshop/scratch.txt 73 | echo "API Gateway URL:" >> /home/ec2-user/environment/aws-serverless-security-workshop/scratch.txt 74 | echo "$(aws cloudformation describe-stacks --stack-name $sam_stack_name --query 'Stacks[].Outputs[].OutputValue' --output text)" >> /home/ec2-user/environment/aws-serverless-security-workshop/scratch.txt 75 | 76 | } 77 | 78 | function main() { 79 | install_utility_tools 80 | setstackname 81 | setcfoutput 82 | setclustername 83 | setregion 84 | deployapp 85 | getapiurl 86 | 87 | exec ${SHELL} 88 | } 89 | 90 | main 91 | -------------------------------------------------------------------------------- /src/test-events/Customize_Unicorns.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "7e41857e-3481-4390-821c-395ef8670d36", 4 | "name": "Customize_Unicorns", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "List customization options", 10 | "item": [ 11 | { 12 | "name": "Socks-List", 13 | "request": { 14 | "method": "GET", 15 | "header": [], 16 | "body": { 17 | "mode": "raw", 18 | "raw": "" 19 | }, 20 | "url": "{{base_url}}/socks" 21 | }, 22 | "response": [] 23 | }, 24 | { 25 | "name": "Horns-List", 26 | "request": { 27 | "method": "GET", 28 | "header": [], 29 | "body": { 30 | "mode": "raw", 31 | "raw": "" 32 | }, 33 | "url": "{{base_url}}/horns" 34 | }, 35 | "response": [] 36 | }, 37 | { 38 | "name": "Glasses-List", 39 | "request": { 40 | "method": "GET", 41 | "header": [], 42 | "body": { 43 | "mode": "raw", 44 | "raw": "" 45 | }, 46 | "url": "{{base_url}}/glasses" 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "Capes-List", 52 | "request": { 53 | "method": "GET", 54 | "header": [], 55 | "body": { 56 | "mode": "raw", 57 | "raw": "" 58 | }, 59 | "url": "{{base_url}}/capes" 60 | }, 61 | "response": [] 62 | } 63 | ], 64 | "description": "A set of read-only APIs that lists unicorn customization options. Intended to be used by 3rd party partner companies of Wild Rydes.", 65 | "event": [ 66 | { 67 | "listen": "prerequest", 68 | "script": { 69 | "id": "17f97895-d515-44f2-b09a-ee2a57c931cf", 70 | "type": "text/javascript", 71 | "exec": [ 72 | "" 73 | ] 74 | } 75 | }, 76 | { 77 | "listen": "test", 78 | "script": { 79 | "id": "4a8b9906-16d2-4065-a50d-5f38dd3ccee7", 80 | "type": "text/javascript", 81 | "exec": [ 82 | "" 83 | ] 84 | } 85 | } 86 | ] 87 | }, 88 | { 89 | "name": "Customization APIs", 90 | "item": [ 91 | { 92 | "name": "DELETE Custom_Unicorn", 93 | "request": { 94 | "method": "DELETE", 95 | "header": [], 96 | "body": {}, 97 | "url": "{{base_url}}/customizations/1" 98 | }, 99 | "response": [] 100 | }, 101 | { 102 | "name": "GET Custom_Unicorn", 103 | "request": { 104 | "method": "GET", 105 | "header": [], 106 | "body": {}, 107 | "url": "{{base_url}}/customizations/1" 108 | }, 109 | "response": [] 110 | }, 111 | { 112 | "name": "POST create Custom_Unicorn", 113 | "request": { 114 | "method": "POST", 115 | "header": [ 116 | { 117 | "key": "Content-Type", 118 | "value": "application/json" 119 | } 120 | ], 121 | "body": { 122 | "mode": "raw", 123 | "raw": "{\"name\":\"Great Custom Unicorn\", \"imageUrl\":\"https://mom/myimage\", \"sock\":\"1\", \"horn\": \"2\", \"glasses\":\"1\", \"cape\":\"1\", \"company\":\"1\"}" 124 | }, 125 | "url": "{{base_url}}/customizations" 126 | }, 127 | "response": [] 128 | }, 129 | { 130 | "name": "LIST Custom_Unicorn", 131 | "request": { 132 | "method": "GET", 133 | "header": [], 134 | "body": {}, 135 | "url": "{{base_url}}/customizations" 136 | }, 137 | "response": [] 138 | } 139 | ], 140 | "description": "THE CRUD APIs related to creating, describing and deleting Unicorn customizations. Intended to be used by 3rd party partner companies of Wild Rydes." 141 | }, 142 | { 143 | "name": "Manage Partner", 144 | "item": [ 145 | { 146 | "name": "POST Create Partner", 147 | "request": { 148 | "method": "POST", 149 | "header": [ 150 | { 151 | "key": "Content-Type", 152 | "value": "application/json" 153 | } 154 | ], 155 | "body": { 156 | "mode": "raw", 157 | "raw": "{\n\t\"name\": \"This Good Company\"\n}" 158 | }, 159 | "url": "{{base_url}}/partners" 160 | }, 161 | "response": [] 162 | } 163 | ] 164 | } 165 | ], 166 | "event": [ 167 | { 168 | "listen": "prerequest", 169 | "script": { 170 | "id": "61d4f653-a4a3-49ed-b200-441d64fca425", 171 | "type": "text/javascript", 172 | "exec": [ 173 | "" 174 | ] 175 | } 176 | }, 177 | { 178 | "listen": "test", 179 | "script": { 180 | "id": "e8aada25-a7bb-49bc-97f5-dabf1f44c945", 181 | "type": "text/javascript", 182 | "exec": [ 183 | "" 184 | ] 185 | } 186 | } 187 | ], 188 | "variable": [ 189 | { 190 | "id": "979f9454-2a9b-4fcc-a938-05189a640013", 191 | "key": "base_url", 192 | "value": "", 193 | "type": "string" 194 | } 195 | ] 196 | } --------------------------------------------------------------------------------