@aligent/cdk-header-change-detection 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @aligent/cdk-header-change-detection
2
+
3
+ ## 1.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1523](https://github.com/aligent/cdk-constructs/pull/1523) [`2550ceb`](https://github.com/aligent/cdk-constructs/commit/2550cebd411cf2cfd5b92deba17e18a5a3d3d012) Thanks [@TheOrangePuff](https://github.com/TheOrangePuff)! - Fixes changeset publishing to work correctly by switching from dist-based builds to in-place compilation within packages
8
+
9
+ ## 1.7.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#1512](https://github.com/aligent/cdk-constructs/pull/1512) [`a9a6231`](https://github.com/aligent/cdk-constructs/commit/a9a62319e4528ac2d23f3af96e96cb2427f242f8) Thanks [@TheOrangePuff](https://github.com/TheOrangePuff)! - Adds changeset package to handle release management
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Aligent Header Change Detection Service
2
+
3
+ ## Overview
4
+
5
+ Creates a Lambda function that periodically scans security headers and sends the results to SNS.
6
+
7
+ ### Diagram
8
+
9
+ ![diagram](docs/diagram.jpg)
10
+
11
+ This service aims to comply with PCI DSS to cover the requirements outlined by section 11.6.1.
12
+
13
+ **11.6.1**: A change- and tamper-detection mechanism is deployed as follows:
14
+
15
+ > - To alert personnel to unauthorized modification (including indicators of compromise, changes, additions, and deletions) to the security-impacting HTTP headers and the script contents of payment pages as received by the consumer browser.
16
+ > - The mechanism is configured to evaluate the received HTTP headers and payment pages.
17
+ > - The mechanism functions are performed as follows:
18
+ > - At least weekly
19
+ > OR
20
+ > - Periodically (at the frequency defined in the entity’s targeted risk analysis, which is performed according to all elements specified in Requirement 12.3.1)
21
+
22
+ ## Default config
23
+
24
+ By default, the following headers are monitored:
25
+
26
+ - Content-Security-Policy
27
+ - Content-Security-Policy-Report-Only
28
+ - Reporting-Endpoints
29
+ - Strict-Transport-Security
30
+ - X-Frame-Options
31
+ - X-Content-Type-Options
32
+ - Cross-Origin-Opener-Policy
33
+ - Cross-Origin-Embedder-Policy
34
+ - Cross-Origin-Resource-Policy
35
+ - Referrer-Policy
36
+ - Permission-Policy
37
+ - Cache-Control
38
+
39
+ ## Usage
40
+
41
+ To include this in your CDK stack, add the following:
42
+
43
+ ```typescript
44
+ // Import required packages
45
+ import { SnsTopic } from "aws-cdk-lib/aws-events-targets";
46
+ import { Topic } from "aws-cdk-lib/aws-sns";
47
+ import { HeaderChangeDetection } from "@aligent/cdk-header-change-detection";
48
+
49
+ // Create a new SNS topic
50
+ const topic = new Topic(this, "Topic");
51
+ const snsTopic = new SnsTopic(topic);
52
+
53
+ // Pass the required props
54
+ new HeaderChangeDetection(this, "HeaderChangeDetection", { snsTopic });
55
+ ```
56
+
57
+ ## Local development
58
+
59
+ [NPM link](https://docs.npmjs.com/cli/v7/commands/npm-link) can be used to develop the module locally.
60
+
61
+ 1. Pull this repository locally
62
+ 2. `cd` into this repository
63
+ 3. run `npm link`
64
+ 4. `cd` into the downstream repo (target project, etc) and run `npm link '@aligent/cdk-header-change-detection'`
65
+ The downstream repository should now include a symlink to this module. Allowing local changes to be tested before pushing. You may want to update the version notation of the package in the downstream repository's `package.json`.
Binary file
package/index.js CHANGED
@@ -3,4 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HeaderChangeDetection = void 0;
4
4
  const header_change_detection_1 = require("./lib/header-change-detection");
5
5
  Object.defineProperty(exports, "HeaderChangeDetection", { enumerable: true, get: function () { return header_change_detection_1.HeaderChangeDetection; } });
6
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9wYWNrYWdlcy9oZWFkZXItY2hhbmdlLWRldGVjdGlvbi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyRUFHdUM7QUFFOUIsc0dBSlAsK0NBQXFCLE9BSU8iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBIZWFkZXJDaGFuZ2VEZXRlY3Rpb24sXG4gIEhlYWRlckNoYW5nZURldGVjdGlvblByb3BzLFxufSBmcm9tIFwiLi9saWIvaGVhZGVyLWNoYW5nZS1kZXRlY3Rpb25cIjtcblxuZXhwb3J0IHsgSGVhZGVyQ2hhbmdlRGV0ZWN0aW9uLCBIZWFkZXJDaGFuZ2VEZXRlY3Rpb25Qcm9wcyB9O1xuIl19
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyRUFHdUM7QUFFOUIsc0dBSlAsK0NBQXFCLE9BSU8iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBIZWFkZXJDaGFuZ2VEZXRlY3Rpb24sXG4gIEhlYWRlckNoYW5nZURldGVjdGlvblByb3BzLFxufSBmcm9tIFwiLi9saWIvaGVhZGVyLWNoYW5nZS1kZXRlY3Rpb25cIjtcblxuZXhwb3J0IHsgSGVhZGVyQ2hhbmdlRGV0ZWN0aW9uLCBIZWFkZXJDaGFuZ2VEZXRlY3Rpb25Qcm9wcyB9O1xuIl19
@@ -74,4 +74,4 @@ class HeaderChangeDetection extends constructs_1.Construct {
74
74
  }
75
75
  }
76
76
  exports.HeaderChangeDetection = HeaderChangeDetection;
77
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVhZGVyLWNoYW5nZS1kZXRlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9wYWNrYWdlcy9oZWFkZXItY2hhbmdlLWRldGVjdGlvbi9saWIvaGVhZGVyLWNoYW5nZS1kZXRlY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNkNBQW9EO0FBQ3BELDJEQUE2RTtBQUM3RSx1REFBbUU7QUFDbkUsdUVBQTBFO0FBQzFFLHVEQUErRTtBQUMvRSwyQ0FBdUM7QUFDdkMsK0JBQTRCO0FBQzVCLHNEQUErQztBQW9EL0MsTUFBTSxPQUFPLEdBQUc7SUFDZCxJQUFJO0lBQ0osSUFBSTtJQUNKLDREQUE0RDtDQUM3RCxDQUFDO0FBRUYsTUFBTSxjQUFjLEdBQUc7SUFDckIseUJBQXlCO0lBQ3pCLHFDQUFxQztJQUNyQyxxQkFBcUI7SUFDckIsMkJBQTJCO0lBQzNCLGlCQUFpQjtJQUNqQix3QkFBd0I7SUFDeEIsNEJBQTRCO0lBQzVCLDhCQUE4QjtJQUM5Qiw4QkFBOEI7SUFDOUIsaUJBQWlCO0lBQ2pCLG1CQUFtQjtJQUNuQixlQUFlO0NBQ2hCLENBQUM7QUFFRixNQUFhLHFCQUFzQixTQUFRLHNCQUFTO0lBQ2xELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBaUM7O1FBQ3pFLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUM7UUFFNUQsT0FBTyxDQUFDLElBQUksQ0FDVixHQUFHLENBQUMsQ0FBQSxNQUFBLEtBQUssQ0FBQyxpQkFBaUIsMENBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDLEtBQUksRUFBRSxDQUFDLENBQ3hFLENBQUM7UUFFRixNQUFNLEtBQUssR0FBRyxJQUFJLG9CQUFLLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRTtZQUNyQyxZQUFZLEVBQUU7Z0JBQ1osSUFBSSxFQUFFLEtBQUs7Z0JBQ1gsSUFBSSxFQUFFLDRCQUFhLENBQUMsTUFBTTthQUMzQjtZQUNELFdBQVcsRUFBRSwwQkFBVyxDQUFDLGVBQWU7U0FDekMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxRQUFRLEdBQUcsSUFBSSxpQkFBSSxDQUFDLElBQUksRUFBRSxXQUFXLEVBQUU7WUFDM0MsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRLElBQUkscUJBQVEsQ0FBQyxJQUFJLENBQUMsc0JBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDNUQsR0FBRyxLQUFLLENBQUMsU0FBUztTQUNuQixDQUFDLENBQUM7UUFFSCxNQUFNLE1BQU0sR0FBRyxJQUFJLHFCQUFRLENBQUMsSUFBSSxFQUFFLGFBQWEsRUFBRTtZQUMvQyxZQUFZLEVBQUUseUJBQVksQ0FBQyxNQUFNO1lBQ2pDLE9BQU8sRUFBRSxvQkFBTyxDQUFDLFdBQVc7WUFDNUIsT0FBTyxFQUFFLHNCQUFzQjtZQUMvQixPQUFPLEVBQUUsS0FBSyxDQUFDLGFBQWEsSUFBSSxzQkFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDcEQsSUFBSSxFQUFFLGlCQUFJLENBQUMsU0FBUyxDQUFDLElBQUEsV0FBSSxFQUFDLFNBQVMsRUFBRSxRQUFRLENBQUMsRUFBRTtnQkFDOUMsUUFBUSxFQUFFO29CQUNSLE9BQU87b0JBQ1AsS0FBSyxFQUFFLHlCQUFXLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQztvQkFDMUMsS0FBSyxFQUFFLElBQUkscUJBQU8sQ0FBQzt3QkFDakIsV0FBVyxFQUFFLENBQUMsSUFBQSxXQUFJLEVBQUMsU0FBUyxFQUFFLHdCQUF3QixDQUFDLENBQUM7cUJBQ3pELENBQUM7aUJBQ0g7YUFDRixDQUFDO1lBQ0YsV0FBVyxFQUFFO2dCQUNYLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUM7Z0JBQzFCLE9BQU8sRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQztnQkFDMUIsS0FBSyxFQUFFLEtBQUssQ0FBQyxTQUFTO2dCQUN0QixTQUFTLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsUUFBUTtnQkFDeEMsb0JBQW9CLEVBQUUsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUM7YUFDdEU7U0FDRixDQUFDLENBQUM7UUFFSCxRQUFRLENBQUMsU0FBUyxDQUFDLElBQUksbUNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO1FBRS9DLEtBQUssQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDN0IsS0FBSyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM1QixLQUFLLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDNUMsQ0FBQztDQUNGO0FBcERELHNEQW9EQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IERvY2tlckltYWdlLCBEdXJhdGlvbiB9IGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0IHsgQXR0cmlidXRlVHlwZSwgQmlsbGluZ01vZGUsIFRhYmxlIH0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1keW5hbW9kYlwiO1xuaW1wb3J0IHsgUnVsZSwgUnVsZVByb3BzLCBTY2hlZHVsZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtZXZlbnRzXCI7XG5pbXBvcnQgeyBMYW1iZGFGdW5jdGlvbiwgU25zVG9waWMgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWV2ZW50cy10YXJnZXRzXCI7XG5pbXBvcnQgeyBBcmNoaXRlY3R1cmUsIENvZGUsIEZ1bmN0aW9uLCBSdW50aW1lIH0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1sYW1iZGFcIjtcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCI7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IEVzYnVpbGQgfSBmcm9tIFwiQGFsaWdlbnQvY2RrLWVzYnVpbGRcIjtcblxuZXhwb3J0IGludGVyZmFjZSBIZWFkZXJDaGFuZ2VEZXRlY3Rpb25Qcm9wcyB7XG4gIC8qKlxuICAgKiBMaXN0IG9mIFVSTHMgdG8gbW9uaXRvciBmb3IgaGVhZGVyIGNoYW5nZXNcbiAgICovXG4gIHVybHM6IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBPcHRpb25hbCBsaXN0IG9mIGFkZGl0aW9uYWwgaGVhZGVycyB0byBtb25pdG9yXG4gICAqXG4gICAqIEBkZWZhdWx0IFtdXG4gICAqL1xuICBhZGRpdGlvbmFsSGVhZGVycz86IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBPcHRpb25hbGx5IGRpc2FibGUgYWxsIHRoZSBkZWZhdWx0IGhlYWRlcnNcbiAgICpcbiAgICogQGRlZmF1bHQgZmFsc2VcbiAgICovXG4gIGRpc2FibGVEZWZhdWx0cz86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFNOUyBUb3BpYyB0byBzZW5kIGNoYW5nZSBkZXRlY3Rpb24gbm90aWZpY2F0aW9ucyB0b1xuICAgKi9cbiAgc25zVG9waWM6IFNuc1RvcGljO1xuXG4gIC8qKlxuICAgKiBUaGUgc2NoZWR1bGUgZm9yIHBlcmZvcm1pbmcgdGhlIGhlYWRlciBjaGVja1xuICAgKlxuICAgKiBAZGVmYXVsdCBTY2hlZHVsZS5yYXRlKER1cmF0aW9uLmhvdXJzKDEpKVxuICAgKi9cbiAgc2NoZWR1bGU/OiBTY2hlZHVsZTtcblxuICAvKipcbiAgICogT3B0aW9uYWxseSBwYXNzIGFueSBydWxlIHByb3BlcnRpZXNcbiAgICovXG4gIHJ1bGVQcm9wcz86IFBhcnRpYWw8UnVsZVByb3BzPjtcblxuICAvKipcbiAgICogT3B0aW9uYWxseSBhY2NlcHQgSFRUUCBzdGF0dXMgY29kZXMgb3RoZXIgdGhhbiAyMDBcbiAgICpcbiAgICogQGRlZmF1bHQgW1wiMjAwXCJdXG4gICAqL1xuICBhY2NlcHRlZEh0dHBTdGF0dXM/OiBzdHJpbmdbXTtcblxuICAvKipcbiAgICogRm9yIGV4dGVuZGVkIExhbWJkYSB0aW1lb3V0LiBEZWZhdWx0OiAxMCBzZWNvbmRzXG4gICAqL1xuICBsYW1iZGFUaW1lb3V0PzogRHVyYXRpb247XG59XG5cbmNvbnN0IGNvbW1hbmQgPSBbXG4gIFwic2hcIixcbiAgXCItY1wiLFxuICAnZWNobyBcIkRvY2tlciBidWlsZCBub3Qgc3VwcG9ydGVkLiBQbGVhc2UgaW5zdGFsbCBlc2J1aWxkLlwiJyxcbl07XG5cbmNvbnN0IGRlZmF1bHRIZWFkZXJzID0gW1xuICBcImNvbnRlbnQtc2VjdXJpdHktcG9saWN5XCIsXG4gIFwiY29udGVudC1zZWN1cml0eS1wb2xpY3ktcmVwb3J0LW9ubHlcIixcbiAgXCJyZXBvcnRpbmctZW5kcG9pbnRzXCIsXG4gIFwic3RyaWN0LXRyYW5zcG9ydC1zZWN1cml0eVwiLFxuICBcIngtZnJhbWUtb3B0aW9uc1wiLFxuICBcIngtY29udGVudC10eXBlLW9wdGlvbnNcIixcbiAgXCJjcm9zcy1vcmlnaW4tb3BlbmVyLXBvbGljeVwiLFxuICBcImNyb3NzLW9yaWdpbi1lbWJlZGRlci1wb2xpY3lcIixcbiAgXCJjcm9zcy1vcmlnaW4tcmVzb3VyY2UtcG9saWN5XCIsXG4gIFwicmVmZXJyZXItcG9saWN5XCIsXG4gIFwicGVybWlzc2lvbi1wb2xpY3lcIixcbiAgXCJjYWNoZS1jb250cm9sXCIsXG5dO1xuXG5leHBvcnQgY2xhc3MgSGVhZGVyQ2hhbmdlRGV0ZWN0aW9uIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IEhlYWRlckNoYW5nZURldGVjdGlvblByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIGNvbnN0IGhlYWRlcnMgPSBwcm9wcy5kaXNhYmxlRGVmYXVsdHMgPyBbXSA6IGRlZmF1bHRIZWFkZXJzO1xuXG4gICAgaGVhZGVycy5wdXNoKFxuICAgICAgLi4uKHByb3BzLmFkZGl0aW9uYWxIZWFkZXJzPy5tYXAoaGVhZGVyID0+IGhlYWRlci50b0xvd2VyQ2FzZSgpKSB8fCBbXSlcbiAgICApO1xuXG4gICAgY29uc3QgdGFibGUgPSBuZXcgVGFibGUodGhpcywgXCJUYWJsZVwiLCB7XG4gICAgICBwYXJ0aXRpb25LZXk6IHtcbiAgICAgICAgbmFtZTogXCJVcmxcIixcbiAgICAgICAgdHlwZTogQXR0cmlidXRlVHlwZS5TVFJJTkcsXG4gICAgICB9LFxuICAgICAgYmlsbGluZ01vZGU6IEJpbGxpbmdNb2RlLlBBWV9QRVJfUkVRVUVTVCxcbiAgICB9KTtcblxuICAgIGNvbnN0IHNjaGVkdWxlID0gbmV3IFJ1bGUodGhpcywgXCJFdmVudFJ1bGVcIiwge1xuICAgICAgc2NoZWR1bGU6IHByb3BzLnNjaGVkdWxlIHx8IFNjaGVkdWxlLnJhdGUoRHVyYXRpb24uaG91cnMoMSkpLFxuICAgICAgLi4ucHJvcHMucnVsZVByb3BzLFxuICAgIH0pO1xuXG4gICAgY29uc3QgbGFtYmRhID0gbmV3IEZ1bmN0aW9uKHRoaXMsIFwiSGVhZGVyQ2hlY2tcIiwge1xuICAgICAgYXJjaGl0ZWN0dXJlOiBBcmNoaXRlY3R1cmUuWDg2XzY0LFxuICAgICAgcnVudGltZTogUnVudGltZS5OT0RFSlNfMjJfWCxcbiAgICAgIGhhbmRsZXI6IFwiaGVhZGVyLWNoZWNrLmhhbmRsZXJcIixcbiAgICAgIHRpbWVvdXQ6IHByb3BzLmxhbWJkYVRpbWVvdXQgfHwgRHVyYXRpb24uc2Vjb25kcygxMCksXG4gICAgICBjb2RlOiBDb2RlLmZyb21Bc3NldChqb2luKF9fZGlybmFtZSwgXCJsYW1iZGFcIiksIHtcbiAgICAgICAgYnVuZGxpbmc6IHtcbiAgICAgICAgICBjb21tYW5kLFxuICAgICAgICAgIGltYWdlOiBEb2NrZXJJbWFnZS5mcm9tUmVnaXN0cnkoXCJidXN5Ym94XCIpLFxuICAgICAgICAgIGxvY2FsOiBuZXcgRXNidWlsZCh7XG4gICAgICAgICAgICBlbnRyeVBvaW50czogW2pvaW4oX19kaXJuYW1lLCBcImxhbWJkYS9oZWFkZXItY2hlY2sudHNcIildLFxuICAgICAgICAgIH0pLFxuICAgICAgICB9LFxuICAgICAgfSksXG4gICAgICBlbnZpcm9ubWVudDoge1xuICAgICAgICBVUkxTOiBwcm9wcy51cmxzLmpvaW4oXCIsXCIpLFxuICAgICAgICBIRUFERVJTOiBoZWFkZXJzLmpvaW4oXCIsXCIpLFxuICAgICAgICBUQUJMRTogdGFibGUudGFibGVOYW1lLFxuICAgICAgICBUT1BJQ19BUk46IHByb3BzLnNuc1RvcGljLnRvcGljLnRvcGljQXJuLFxuICAgICAgICBBQ0NFUFRFRF9IVFRQX1NUQVRVUzogKHByb3BzLmFjY2VwdGVkSHR0cFN0YXR1cyB8fCBbXCIyMDBcIl0pLmpvaW4oXCIsXCIpLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIHNjaGVkdWxlLmFkZFRhcmdldChuZXcgTGFtYmRhRnVuY3Rpb24obGFtYmRhKSk7XG5cbiAgICB0YWJsZS5ncmFudFdyaXRlRGF0YShsYW1iZGEpO1xuICAgIHRhYmxlLmdyYW50UmVhZERhdGEobGFtYmRhKTtcbiAgICBwcm9wcy5zbnNUb3BpYy50b3BpYy5ncmFudFB1Ymxpc2gobGFtYmRhKTtcbiAgfVxufVxuIl19
77
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVhZGVyLWNoYW5nZS1kZXRlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJoZWFkZXItY2hhbmdlLWRldGVjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBb0Q7QUFDcEQsMkRBQTZFO0FBQzdFLHVEQUFtRTtBQUNuRSx1RUFBMEU7QUFDMUUsdURBQStFO0FBQy9FLDJDQUF1QztBQUN2QywrQkFBNEI7QUFDNUIsc0RBQStDO0FBb0QvQyxNQUFNLE9BQU8sR0FBRztJQUNkLElBQUk7SUFDSixJQUFJO0lBQ0osNERBQTREO0NBQzdELENBQUM7QUFFRixNQUFNLGNBQWMsR0FBRztJQUNyQix5QkFBeUI7SUFDekIscUNBQXFDO0lBQ3JDLHFCQUFxQjtJQUNyQiwyQkFBMkI7SUFDM0IsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4Qiw0QkFBNEI7SUFDNUIsOEJBQThCO0lBQzlCLDhCQUE4QjtJQUM5QixpQkFBaUI7SUFDakIsbUJBQW1CO0lBQ25CLGVBQWU7Q0FDaEIsQ0FBQztBQUVGLE1BQWEscUJBQXNCLFNBQVEsc0JBQVM7SUFDbEQsWUFBWSxLQUFnQixFQUFFLEVBQVUsRUFBRSxLQUFpQzs7UUFDekUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQztRQUU1RCxPQUFPLENBQUMsSUFBSSxDQUNWLEdBQUcsQ0FBQyxDQUFBLE1BQUEsS0FBSyxDQUFDLGlCQUFpQiwwQ0FBRSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUMsS0FBSSxFQUFFLENBQUMsQ0FDeEUsQ0FBQztRQUVGLE1BQU0sS0FBSyxHQUFHLElBQUksb0JBQUssQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFO1lBQ3JDLFlBQVksRUFBRTtnQkFDWixJQUFJLEVBQUUsS0FBSztnQkFDWCxJQUFJLEVBQUUsNEJBQWEsQ0FBQyxNQUFNO2FBQzNCO1lBQ0QsV0FBVyxFQUFFLDBCQUFXLENBQUMsZUFBZTtTQUN6QyxDQUFDLENBQUM7UUFFSCxNQUFNLFFBQVEsR0FBRyxJQUFJLGlCQUFJLENBQUMsSUFBSSxFQUFFLFdBQVcsRUFBRTtZQUMzQyxRQUFRLEVBQUUsS0FBSyxDQUFDLFFBQVEsSUFBSSxxQkFBUSxDQUFDLElBQUksQ0FBQyxzQkFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM1RCxHQUFHLEtBQUssQ0FBQyxTQUFTO1NBQ25CLENBQUMsQ0FBQztRQUVILE1BQU0sTUFBTSxHQUFHLElBQUkscUJBQVEsQ0FBQyxJQUFJLEVBQUUsYUFBYSxFQUFFO1lBQy9DLFlBQVksRUFBRSx5QkFBWSxDQUFDLE1BQU07WUFDakMsT0FBTyxFQUFFLG9CQUFPLENBQUMsV0FBVztZQUM1QixPQUFPLEVBQUUsc0JBQXNCO1lBQy9CLE9BQU8sRUFBRSxLQUFLLENBQUMsYUFBYSxJQUFJLHNCQUFRLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztZQUNwRCxJQUFJLEVBQUUsaUJBQUksQ0FBQyxTQUFTLENBQUMsSUFBQSxXQUFJLEVBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxFQUFFO2dCQUM5QyxRQUFRLEVBQUU7b0JBQ1IsT0FBTztvQkFDUCxLQUFLLEVBQUUseUJBQVcsQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDO29CQUMxQyxLQUFLLEVBQUUsSUFBSSxxQkFBTyxDQUFDO3dCQUNqQixXQUFXLEVBQUUsQ0FBQyxJQUFBLFdBQUksRUFBQyxTQUFTLEVBQUUsd0JBQXdCLENBQUMsQ0FBQztxQkFDekQsQ0FBQztpQkFDSDthQUNGLENBQUM7WUFDRixXQUFXLEVBQUU7Z0JBQ1gsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQztnQkFDMUIsT0FBTyxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDO2dCQUMxQixLQUFLLEVBQUUsS0FBSyxDQUFDLFNBQVM7Z0JBQ3RCLFNBQVMsRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxRQUFRO2dCQUN4QyxvQkFBb0IsRUFBRSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQzthQUN0RTtTQUNGLENBQUMsQ0FBQztRQUVILFFBQVEsQ0FBQyxTQUFTLENBQUMsSUFBSSxtQ0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7UUFFL0MsS0FBSyxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM3QixLQUFLLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzVCLEtBQUssQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUM1QyxDQUFDO0NBQ0Y7QUFwREQsc0RBb0RDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRG9ja2VySW1hZ2UsIER1cmF0aW9uIH0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgeyBBdHRyaWJ1dGVUeXBlLCBCaWxsaW5nTW9kZSwgVGFibGUgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWR5bmFtb2RiXCI7XG5pbXBvcnQgeyBSdWxlLCBSdWxlUHJvcHMsIFNjaGVkdWxlIH0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1ldmVudHNcIjtcbmltcG9ydCB7IExhbWJkYUZ1bmN0aW9uLCBTbnNUb3BpYyB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtZXZlbnRzLXRhcmdldHNcIjtcbmltcG9ydCB7IEFyY2hpdGVjdHVyZSwgQ29kZSwgRnVuY3Rpb24sIFJ1bnRpbWUgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWxhbWJkYVwiO1xuaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCB7IGpvaW4gfSBmcm9tIFwicGF0aFwiO1xuaW1wb3J0IHsgRXNidWlsZCB9IGZyb20gXCJAYWxpZ2VudC9jZGstZXNidWlsZFwiO1xuXG5leHBvcnQgaW50ZXJmYWNlIEhlYWRlckNoYW5nZURldGVjdGlvblByb3BzIHtcbiAgLyoqXG4gICAqIExpc3Qgb2YgVVJMcyB0byBtb25pdG9yIGZvciBoZWFkZXIgY2hhbmdlc1xuICAgKi9cbiAgdXJsczogc3RyaW5nW107XG5cbiAgLyoqXG4gICAqIE9wdGlvbmFsIGxpc3Qgb2YgYWRkaXRpb25hbCBoZWFkZXJzIHRvIG1vbml0b3JcbiAgICpcbiAgICogQGRlZmF1bHQgW11cbiAgICovXG4gIGFkZGl0aW9uYWxIZWFkZXJzPzogc3RyaW5nW107XG5cbiAgLyoqXG4gICAqIE9wdGlvbmFsbHkgZGlzYWJsZSBhbGwgdGhlIGRlZmF1bHQgaGVhZGVyc1xuICAgKlxuICAgKiBAZGVmYXVsdCBmYWxzZVxuICAgKi9cbiAgZGlzYWJsZURlZmF1bHRzPzogYm9vbGVhbjtcblxuICAvKipcbiAgICogU05TIFRvcGljIHRvIHNlbmQgY2hhbmdlIGRldGVjdGlvbiBub3RpZmljYXRpb25zIHRvXG4gICAqL1xuICBzbnNUb3BpYzogU25zVG9waWM7XG5cbiAgLyoqXG4gICAqIFRoZSBzY2hlZHVsZSBmb3IgcGVyZm9ybWluZyB0aGUgaGVhZGVyIGNoZWNrXG4gICAqXG4gICAqIEBkZWZhdWx0IFNjaGVkdWxlLnJhdGUoRHVyYXRpb24uaG91cnMoMSkpXG4gICAqL1xuICBzY2hlZHVsZT86IFNjaGVkdWxlO1xuXG4gIC8qKlxuICAgKiBPcHRpb25hbGx5IHBhc3MgYW55IHJ1bGUgcHJvcGVydGllc1xuICAgKi9cbiAgcnVsZVByb3BzPzogUGFydGlhbDxSdWxlUHJvcHM+O1xuXG4gIC8qKlxuICAgKiBPcHRpb25hbGx5IGFjY2VwdCBIVFRQIHN0YXR1cyBjb2RlcyBvdGhlciB0aGFuIDIwMFxuICAgKlxuICAgKiBAZGVmYXVsdCBbXCIyMDBcIl1cbiAgICovXG4gIGFjY2VwdGVkSHR0cFN0YXR1cz86IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBGb3IgZXh0ZW5kZWQgTGFtYmRhIHRpbWVvdXQuIERlZmF1bHQ6IDEwIHNlY29uZHNcbiAgICovXG4gIGxhbWJkYVRpbWVvdXQ/OiBEdXJhdGlvbjtcbn1cblxuY29uc3QgY29tbWFuZCA9IFtcbiAgXCJzaFwiLFxuICBcIi1jXCIsXG4gICdlY2hvIFwiRG9ja2VyIGJ1aWxkIG5vdCBzdXBwb3J0ZWQuIFBsZWFzZSBpbnN0YWxsIGVzYnVpbGQuXCInLFxuXTtcblxuY29uc3QgZGVmYXVsdEhlYWRlcnMgPSBbXG4gIFwiY29udGVudC1zZWN1cml0eS1wb2xpY3lcIixcbiAgXCJjb250ZW50LXNlY3VyaXR5LXBvbGljeS1yZXBvcnQtb25seVwiLFxuICBcInJlcG9ydGluZy1lbmRwb2ludHNcIixcbiAgXCJzdHJpY3QtdHJhbnNwb3J0LXNlY3VyaXR5XCIsXG4gIFwieC1mcmFtZS1vcHRpb25zXCIsXG4gIFwieC1jb250ZW50LXR5cGUtb3B0aW9uc1wiLFxuICBcImNyb3NzLW9yaWdpbi1vcGVuZXItcG9saWN5XCIsXG4gIFwiY3Jvc3Mtb3JpZ2luLWVtYmVkZGVyLXBvbGljeVwiLFxuICBcImNyb3NzLW9yaWdpbi1yZXNvdXJjZS1wb2xpY3lcIixcbiAgXCJyZWZlcnJlci1wb2xpY3lcIixcbiAgXCJwZXJtaXNzaW9uLXBvbGljeVwiLFxuICBcImNhY2hlLWNvbnRyb2xcIixcbl07XG5cbmV4cG9ydCBjbGFzcyBIZWFkZXJDaGFuZ2VEZXRlY3Rpb24gZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogSGVhZGVyQ2hhbmdlRGV0ZWN0aW9uUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgY29uc3QgaGVhZGVycyA9IHByb3BzLmRpc2FibGVEZWZhdWx0cyA/IFtdIDogZGVmYXVsdEhlYWRlcnM7XG5cbiAgICBoZWFkZXJzLnB1c2goXG4gICAgICAuLi4ocHJvcHMuYWRkaXRpb25hbEhlYWRlcnM/Lm1hcChoZWFkZXIgPT4gaGVhZGVyLnRvTG93ZXJDYXNlKCkpIHx8IFtdKVxuICAgICk7XG5cbiAgICBjb25zdCB0YWJsZSA9IG5ldyBUYWJsZSh0aGlzLCBcIlRhYmxlXCIsIHtcbiAgICAgIHBhcnRpdGlvbktleToge1xuICAgICAgICBuYW1lOiBcIlVybFwiLFxuICAgICAgICB0eXBlOiBBdHRyaWJ1dGVUeXBlLlNUUklORyxcbiAgICAgIH0sXG4gICAgICBiaWxsaW5nTW9kZTogQmlsbGluZ01vZGUuUEFZX1BFUl9SRVFVRVNULFxuICAgIH0pO1xuXG4gICAgY29uc3Qgc2NoZWR1bGUgPSBuZXcgUnVsZSh0aGlzLCBcIkV2ZW50UnVsZVwiLCB7XG4gICAgICBzY2hlZHVsZTogcHJvcHMuc2NoZWR1bGUgfHwgU2NoZWR1bGUucmF0ZShEdXJhdGlvbi5ob3VycygxKSksXG4gICAgICAuLi5wcm9wcy5ydWxlUHJvcHMsXG4gICAgfSk7XG5cbiAgICBjb25zdCBsYW1iZGEgPSBuZXcgRnVuY3Rpb24odGhpcywgXCJIZWFkZXJDaGVja1wiLCB7XG4gICAgICBhcmNoaXRlY3R1cmU6IEFyY2hpdGVjdHVyZS5YODZfNjQsXG4gICAgICBydW50aW1lOiBSdW50aW1lLk5PREVKU18yMl9YLFxuICAgICAgaGFuZGxlcjogXCJoZWFkZXItY2hlY2suaGFuZGxlclwiLFxuICAgICAgdGltZW91dDogcHJvcHMubGFtYmRhVGltZW91dCB8fCBEdXJhdGlvbi5zZWNvbmRzKDEwKSxcbiAgICAgIGNvZGU6IENvZGUuZnJvbUFzc2V0KGpvaW4oX19kaXJuYW1lLCBcImxhbWJkYVwiKSwge1xuICAgICAgICBidW5kbGluZzoge1xuICAgICAgICAgIGNvbW1hbmQsXG4gICAgICAgICAgaW1hZ2U6IERvY2tlckltYWdlLmZyb21SZWdpc3RyeShcImJ1c3lib3hcIiksXG4gICAgICAgICAgbG9jYWw6IG5ldyBFc2J1aWxkKHtcbiAgICAgICAgICAgIGVudHJ5UG9pbnRzOiBbam9pbihfX2Rpcm5hbWUsIFwibGFtYmRhL2hlYWRlci1jaGVjay50c1wiKV0sXG4gICAgICAgICAgfSksXG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgICAgIGVudmlyb25tZW50OiB7XG4gICAgICAgIFVSTFM6IHByb3BzLnVybHMuam9pbihcIixcIiksXG4gICAgICAgIEhFQURFUlM6IGhlYWRlcnMuam9pbihcIixcIiksXG4gICAgICAgIFRBQkxFOiB0YWJsZS50YWJsZU5hbWUsXG4gICAgICAgIFRPUElDX0FSTjogcHJvcHMuc25zVG9waWMudG9waWMudG9waWNBcm4sXG4gICAgICAgIEFDQ0VQVEVEX0hUVFBfU1RBVFVTOiAocHJvcHMuYWNjZXB0ZWRIdHRwU3RhdHVzIHx8IFtcIjIwMFwiXSkuam9pbihcIixcIiksXG4gICAgICB9LFxuICAgIH0pO1xuXG4gICAgc2NoZWR1bGUuYWRkVGFyZ2V0KG5ldyBMYW1iZGFGdW5jdGlvbihsYW1iZGEpKTtcblxuICAgIHRhYmxlLmdyYW50V3JpdGVEYXRhKGxhbWJkYSk7XG4gICAgdGFibGUuZ3JhbnRSZWFkRGF0YShsYW1iZGEpO1xuICAgIHByb3BzLnNuc1RvcGljLnRvcGljLmdyYW50UHVibGlzaChsYW1iZGEpO1xuICB9XG59XG4iXX0=
package/package.json CHANGED
@@ -1,35 +1,39 @@
1
1
  {
2
2
  "name": "@aligent/cdk-header-change-detection",
3
+ "version": "1.7.1",
3
4
  "main": "index.js",
4
5
  "license": "MIT",
5
- "homepage": "https://github.com/aligent/cdk-constructs/packages/header-change-detection-stack#readme",
6
+ "homepage": "https://github.com/aligent/aws-cdk-constructs/tree/main/packages/header-change-detection#readme",
6
7
  "repository": {
7
8
  "type": "git",
8
- "url": "https://github.com/aligent/aws-cdk-header-change-detection-stack"
9
+ "url": "git+https://github.com/aligent/aws-cdk-constructs.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/aligent/aws-cdk-constructs/issues"
9
13
  },
10
14
  "types": "index.d.ts",
11
15
  "scripts": {
12
- "build": "tsc"
16
+ "build": "tsc",
17
+ "test": "npx nx test header-change-detection",
18
+ "lint": "npx nx lint header-change-detection"
13
19
  },
14
20
  "devDependencies": {
15
21
  "@types/jest": "^29.5.10",
16
22
  "@types/node": "^20.6.3",
17
- "aws-cdk": "^2.113.0",
23
+ "aws-cdk": "^2.1019.1",
18
24
  "jest": "^29.7.0",
19
25
  "ts-jest": "^29.1.1",
20
26
  "ts-node": "^10.9.1",
21
27
  "typescript": "^5.3.2"
22
28
  },
23
29
  "dependencies": {
24
- "@aws-sdk/client-dynamodb": "^3.726.1",
25
- "@aws-sdk/client-sns": "3.732.0",
30
+ "@aws-sdk/client-dynamodb": "^3.830.0",
31
+ "@aws-sdk/client-sns": "3.830.0",
26
32
  "axios": "^1.8.3",
27
33
  "source-map-support": "^0.5.21"
28
34
  },
29
35
  "peerDependencies": {
30
36
  "aws-cdk-lib": "^2.168.0",
31
37
  "constructs": "^10.4.2"
32
- },
33
- "type": "commonjs",
34
- "version": "1.6.1"
35
- }
38
+ }
39
+ }
@@ -1,360 +0,0 @@
1
- // We know the environment variables will exist so safe to ignore this
2
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
-
4
- import axios from "axios";
5
- import {
6
- DynamoDBClient,
7
- BatchGetItemCommand,
8
- BatchGetItemCommandInput,
9
- KeysAndAttributes,
10
- UpdateItemCommandInput,
11
- UpdateItemCommand,
12
- AttributeValueUpdate,
13
- UpdateItemCommandOutput,
14
- } from "@aws-sdk/client-dynamodb";
15
- import { PublishCommand, PublishInput, SNSClient } from "@aws-sdk/client-sns";
16
-
17
- const URLS = process.env.URLS;
18
- const HEADERS = process.env.HEADERS;
19
- const TABLE = process.env.TABLE!;
20
-
21
- const config = "";
22
- const DB_CLIENT = new DynamoDBClient(config);
23
-
24
- const securityHeaders = HEADERS?.split(",") || [];
25
-
26
- // Accept status 200 default
27
- const ACCEPTED_HTTP_STATUS = (process.env.ACCEPTED_HTTP_STATUS || "200")
28
- .split(",")
29
- .map(code => parseInt(code.trim(), 10));
30
-
31
- console.log("ACCEPTED_HTTP_STATUS:", ACCEPTED_HTTP_STATUS);
32
-
33
- type Headers = Map<string, string | undefined>;
34
-
35
- // A map of URLs and their headers
36
- type URLHeaders = Map<string, Headers>;
37
-
38
- export const handler = async () => {
39
- const urls = URLS?.split(",") || [];
40
-
41
- // Fetch stored headers
42
- const [storedUrlHeaders, currentUrlHeaders] = await Promise.all([
43
- getStoredValues(urls),
44
- fetchHeaders(urls),
45
- ]);
46
-
47
- // Find any differences between the headers
48
- const headerDifferences = new Map<string, Difference[]>();
49
- let differencesDetected = false;
50
- const dbUpdates = urls.map(url => {
51
- const currentHeaders = currentUrlHeaders.get(url);
52
- const storedHeaders =
53
- storedUrlHeaders.get(url) || new Map<string, string | undefined>();
54
-
55
- if (!currentHeaders)
56
- throw new Error(`Could not get current headers for ${url}`);
57
-
58
- // Check all headers that we care about
59
- headerDifferences.set(
60
- url,
61
- compareHeaders(securityHeaders, storedHeaders, currentHeaders)
62
- );
63
-
64
- const headersToUpdate: Headers = new Map<string, string | undefined>();
65
- headerDifferences.get(url)?.forEach(difference => {
66
- headersToUpdate.set(difference.header, difference.currentValue);
67
- differencesDetected = true;
68
- });
69
-
70
- return updateStoredValues(url, headersToUpdate);
71
- });
72
-
73
- await Promise.all(dbUpdates);
74
-
75
- if (differencesDetected)
76
- await sendToSns(formatDifferences(headerDifferences));
77
- };
78
-
79
- /**
80
- * Fetch security headers for the given urls
81
- *
82
- * @param urls list of urls to fetch headers from
83
- */
84
- const fetchHeaders = async (urls: string[]): Promise<URLHeaders> => {
85
- const currentUrlHeaders: URLHeaders = new Map<string, Headers>();
86
-
87
- // Make an axios request for each url
88
- await Promise.all(
89
- urls.map(url =>
90
- axios.get(url, { validateStatus: () => true }).then(response => {
91
- if (!ACCEPTED_HTTP_STATUS.includes(response.status)) {
92
- console.warn(
93
- `Skipping ${url} — status ${response.status} not allowed`
94
- );
95
- return;
96
- }
97
-
98
- const headers: Headers = new Map<string, string | undefined>();
99
-
100
- Object.entries(response.headers).forEach(([headerName, value]) => {
101
- if (securityHeaders?.includes(headerName))
102
- headers.set(headerName, value as string);
103
- });
104
-
105
- currentUrlHeaders.set(url, headers);
106
- })
107
- )
108
- );
109
-
110
- return currentUrlHeaders;
111
- };
112
-
113
- /**
114
- * Get values stored in DynamoDB table from a list of string keys.
115
- * Assumes the call is made to the header change detection table.
116
- * This table has a primary key of Url (string) with an unknown
117
- * number of string fields.
118
- *
119
- * @param keys array of strings
120
- */
121
- const getStoredValues = async (keys: string[]): Promise<URLHeaders> => {
122
- if (keys.length === 0) {
123
- console.log("No keys were passed");
124
- return new Map<string, Headers>();
125
- }
126
-
127
- // Construct the command input
128
- const primaryKeys = keys.map(url => {
129
- return {
130
- Url: {
131
- S: url,
132
- },
133
- };
134
- });
135
- const requestItems = {
136
- [TABLE]: {
137
- Keys: primaryKeys,
138
- },
139
- };
140
-
141
- return dynamoBatchRequest(requestItems);
142
- };
143
-
144
- /**
145
- * Update stored headers for the given url.
146
- * If the header no longer has a value, delete it. Otherwise update it.
147
- *
148
- * @param url the url to update - this is the primary key
149
- * @param headers record of headers to update
150
- */
151
- const updateStoredValues = async (
152
- url: string,
153
- headers: Headers
154
- ): Promise<UpdateItemCommandOutput | undefined> => {
155
- // Convert headers to attribute value update attributes
156
- const attributes: Record<string, AttributeValueUpdate> = {};
157
- headers.forEach((value, headerName) => {
158
- // If the value exists update it, otherwise remove it
159
- if (value) {
160
- attributes[headerName] = {
161
- Value: {
162
- S: Array.isArray(value) ? value.join("; ") : value,
163
- },
164
- Action: "PUT",
165
- };
166
- } else {
167
- attributes[headerName] = {
168
- Action: "DELETE",
169
- };
170
- }
171
- });
172
-
173
- if (Object.values(attributes).length === 0) {
174
- console.log(`No attribute value changes for ${url}`);
175
- return;
176
- }
177
-
178
- return dynamoUpdateRequest(url, attributes);
179
- };
180
-
181
- /**
182
- * Recursive function to get multiple items from a DynamoDB table
183
- *
184
- * @param requestItems
185
- * @returns Promise<URLHeaders>
186
- */
187
- const dynamoBatchRequest = async (
188
- requestItems: Record<string, KeysAndAttributes> | undefined
189
- ): Promise<URLHeaders> => {
190
- console.log(
191
- `Starting batch request with items: ${JSON.stringify(requestItems)}`
192
- );
193
-
194
- // Validate that request items has values
195
- if (Object.keys(requestItems || {})?.length === 0)
196
- return new Map<string, Headers>();
197
-
198
- const batchGetInput: BatchGetItemCommandInput = {
199
- RequestItems: requestItems,
200
- };
201
- const batchGetCommand = new BatchGetItemCommand(batchGetInput);
202
-
203
- // Fetch stored stored headers
204
- const response = await DB_CLIENT.send(batchGetCommand);
205
- const responses = response.Responses?.[TABLE];
206
-
207
- if (!responses) return new Map<string, Headers>();
208
-
209
- console.log(
210
- `Got following data from dynamo table: ${JSON.stringify(responses)}`
211
- );
212
-
213
- const storedUrlHeaders: URLHeaders = new Map<string, Headers>();
214
- Object.values(responses).forEach(headers => {
215
- const urlHeaders: Headers = new Map<string, string | undefined>();
216
-
217
- let url = "";
218
- Object.entries(headers).forEach(([headerName, value]) => {
219
- if (headerName === "Url") {
220
- url = value.S!;
221
- } else {
222
- urlHeaders.set(headerName, value.S!);
223
- }
224
- });
225
- storedUrlHeaders.set(url, urlHeaders);
226
- });
227
-
228
- // Process any remaining keys
229
- const nextUrlHeaders = await dynamoBatchRequest(response.UnprocessedKeys);
230
-
231
- // Merge data into one object and return
232
- return new Map<string, Headers>([...storedUrlHeaders, ...nextUrlHeaders]);
233
- };
234
-
235
- /**
236
- * Send an update command to DynamoDB
237
- *
238
- * @param url the url to update - this is the primary key
239
- * @param attributes Record<string, AttributeValueUpdate>
240
- */
241
- const dynamoUpdateRequest = async (
242
- url: string,
243
- attributes: Record<string, AttributeValueUpdate>
244
- ): Promise<UpdateItemCommandOutput> => {
245
- console.log(
246
- `Updating ${url} in table with: ${JSON.stringify(Object.entries(attributes))}`
247
- );
248
-
249
- const updateItemInput: UpdateItemCommandInput = {
250
- TableName: TABLE,
251
- Key: {
252
- Url: {
253
- S: url,
254
- },
255
- },
256
- AttributeUpdates: attributes,
257
- };
258
- const updateItemCommand = new UpdateItemCommand(updateItemInput);
259
-
260
- return DB_CLIENT.send(updateItemCommand);
261
- };
262
-
263
- interface Difference {
264
- header: string;
265
- storedValue: string | undefined;
266
- currentValue: string | undefined;
267
- }
268
-
269
- /**
270
- * Compare values of two lists of headers. Return any headers that have differences
271
- * along with their stored and current values.
272
- *
273
- * @param headers list of headers we want to compare
274
- * @param stored list of headers that were last found on the site
275
- * @param current list of headers currently on the site
276
- * @returns
277
- */
278
- const compareHeaders = (
279
- headers: string[],
280
- stored: Headers,
281
- current: Headers
282
- ): Difference[] => {
283
- const differences: Difference[] = [];
284
-
285
- headers.forEach(header => {
286
- const currentValue = current.get(header);
287
- const storedValue = stored.get(header);
288
-
289
- if (currentValue !== storedValue) {
290
- differences.push({
291
- header,
292
- storedValue: storedValue,
293
- currentValue: currentValue,
294
- });
295
- }
296
- });
297
-
298
- return differences;
299
- };
300
-
301
- /**
302
- * Format the differences so they can be easily read in an email.
303
- *
304
- * Outputs a string that looks like this:
305
- *
306
- * Headers differences found:
307
- * == https://aligent.com.au/ ===
308
- *
309
- * Header: example-header-name
310
- * Stored Value: No stored value
311
- * Current Value: example-value
312
- *
313
- * === https://aligent.com.au/contact ===
314
- *
315
- * Header: example-header-name
316
- * Stored Value: No stored value
317
- * Current Value: example-value
318
- *
319
- * Header: example-header-name-2
320
- * Stored Value: previous-value
321
- * Current Value: new-example-value
322
- *
323
- * @param differences Map<string, Difference[]> where the key is the URL
324
- */
325
- const formatDifferences = (differences: Map<string, Difference[]>): string => {
326
- const message = Array.from(differences.keys()).reduce((text, url) => {
327
- console.log(text, url);
328
- // Skip the url if there are no differences
329
- if (differences.get(url)?.length === 0) {
330
- return text;
331
- }
332
-
333
- // Format headers nicely
334
- const headers = differences.get(url)?.reduce((headerText, header) => {
335
- return (headerText += `\r\nHeader: ${header.header}\r\nStored Value: ${header.storedValue}\r\nCurrent Value: ${header.currentValue}\r\n`);
336
- }, "");
337
-
338
- return `${text}\r\n=== ${url} ===\r\n ${headers}`;
339
- }, "");
340
-
341
- return `Header differences found${message}`;
342
- };
343
-
344
- const TOPIC_ARN = process.env.TOPIC_ARN!;
345
- const SNS_CLIENT = new SNSClient();
346
-
347
- /**
348
- * Send a message to the SNS topic
349
- *
350
- * @param message string to send to sns
351
- */
352
- const sendToSns = async (message: string) => {
353
- const publishInput: PublishInput = {
354
- TopicArn: TOPIC_ARN,
355
- Message: message,
356
- Subject: "Security Header Change detected",
357
- };
358
- const publishCommand = new PublishCommand(publishInput);
359
- await SNS_CLIENT.send(publishCommand);
360
- };