}
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 | 
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 | 
19 |
20 | Click **Next**
21 |
22 | 1. Associate the API we created previously with the usage plan. Pick `dev` stage.
23 |
24 | 
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 | 
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 | 
57 |
58 |
59 |
60 |
61 |
62 | 1. After the API key has been created, click **Done**.
63 |
64 | 
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 | 
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 | 
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 | 
81 |
82 | 1. Click on **Create web ACL** on the WAF Classic console
83 |
84 | 
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 | 
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 | 
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 | Part of the request to filter on |
126 | Transformation |
127 |
128 |
129 | 1 |
130 | Body |
131 | None |
132 |
133 |
134 | 2 |
135 | Body |
136 | URL decode |
137 |
138 |
139 | 3 |
140 | URI |
141 | URL decode |
142 |
143 |
144 | 4 |
145 | Query string |
146 | URL decode |
147 |
148 |
149 | * Click **Create**
150 |
151 | 
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 | 
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 | 
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 | 
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 | 
201 |
202 | 1. Click **Review and create**
203 |
204 | 1. In the next page, review the configuration and click **Confirm and Create**
205 |
206 | 
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 | 
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 | 
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 | 
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 | 
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 | 
141 |
142 | 1. Explore the individual traces by clicking into individual requests
143 |
144 | 
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 | 
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 | }
--------------------------------------------------------------------------------