Skip to content

Commit

Permalink
Merge pull request #1422 from aligent/feature/header-change-detection
Browse files Browse the repository at this point in the history
Header change detection construct
  • Loading branch information
TheOrangePuff authored Jan 24, 2025
2 parents a19f579 + 3e74aae commit ac75b8f
Show file tree
Hide file tree
Showing 14 changed files with 2,084 additions and 15 deletions.
18 changes: 18 additions & 0 deletions packages/header-change-detection/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "**/node_modules/**"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
64 changes: 64 additions & 0 deletions packages/header-change-detection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Aligent Header Change Detection Service

## Overview

Creates a Lambda function that periodically scans security headers and sends the results to SNS.

### Diagram

![diagram](docs/diagram.jpg)

This service aims to comply with PCI DSS to cover the requirements outlined by section 11.6.1.

**11.6.1**: A change- and tamper-detection mechanism is deployed as follows:
> - 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.
> - The mechanism is configured to evaluate the received HTTP headers and payment pages.
> - The mechanism functions are performed as follows:
> - At least weekly
> OR
> - 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)
## Default config

By default, the following headers are monitored:

- Content-Security-Policy
- Content-Security-Policy-Report-Only
- Reporting-Endpoints
- Strict-Transport-Security
- X-Frame-Options
- X-Content-Type-Options
- Cross-Origin-Opener-Policy
- Cross-Origin-Embedder-Policy
- Cross-Origin-Resource-Policy
- Referrer-Policy
- Permission-Policy
- Cache-Control
- Set-Cookie

## Usage

To include this in your CDK stack, add the following:

```typescript
// Import required packages
import { SnsTopic } from "aws-cdk-lib/aws-events-targets";
import { Topic } from "aws-cdk-lib/aws-sns";
import { HeaderChangeDetection } from "@aligent/cdk-header-change-detection";

// Create a new SNS topic
const topic = new Topic(this, 'Topic');
const snsTopic = new SnsTopic(topic);

// Pass the required props
new HeaderChangeDetection(this, 'HeaderChangeDetection', { snsTopic });
```

## Local development

[NPM link](https://docs.npmjs.com/cli/v7/commands/npm-link) can be used to develop the module locally.
1. Pull this repository locally
2. `cd` into this repository
3. run `npm link`
4. `cd` into the downstream repo (target project, etc) and run `npm link '@aligent/cdk-header-change-detection'`
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`.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/header-change-detection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {
HeaderChangeDetection,
HeaderChangeDetectionProps,
} from "./lib/header-change-detection";

export { HeaderChangeDetection, HeaderChangeDetectionProps };
11 changes: 11 additions & 0 deletions packages/header-change-detection/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: "header-change-detection",
preset: "../../jest.preset.js",
testEnvironment: "node",
transform: {
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }],
},
moduleFileExtensions: ["ts", "js", "html"],
coverageDirectory: "../../coverage/packages/header-change-detection",
};
120 changes: 120 additions & 0 deletions packages/header-change-detection/lib/header-change-detection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { DockerImage, Duration } from "aws-cdk-lib";
import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
import { Rule, RuleProps, Schedule } from "aws-cdk-lib/aws-events";
import { LambdaFunction, SnsTopic } from "aws-cdk-lib/aws-events-targets";
import { Architecture, Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";
import { join } from "path";
import { Esbuild } from "@aligent/cdk-esbuild";

export interface HeaderChangeDetectionProps {
/**
* List of URLs to monitor for header changes
*/
urls: string[];

/**
* Optional list of additional headers to monitor
*
* @default []
*/
additionalHeaders?: string[];

/**
* Optionally disable all the default headers
*
* @default false
*/
disableDefaults?: boolean;

/**
* SNS Topic to send change detection notifications to
*/
snsTopic: SnsTopic;

/**
* The schedule for performing the header check
*
* @default Schedule.rate(Duration.hours(1))
*/
schedule?: Schedule;

/**
* Optionally pass any rule properties
*/
ruleProps?: Partial<RuleProps>;
}

const command = [
"sh",
"-c",
'echo "Docker build not supported. Please install esbuild."',
];

const defaultHeaders = [
"content-security-policy",
"content-security-policy-report-only",
"reporting-endpoints",
"strict-transport-security",
"x-frame-options",
"x-content-type-options",
"cross-origin-opener-policy",
"cross-origin-embedder-policy",
"cross-origin-resource-policy",
"referrer-policy",
"permission-policy",
"cache-control",
"set-cookie",
];

export class HeaderChangeDetection extends Construct {
constructor(scope: Construct, id: string, props: HeaderChangeDetectionProps) {
super(scope, id);

const headers = props.disableDefaults ? [] : defaultHeaders;

headers.push(
...(props.additionalHeaders?.map(header => header.toLowerCase()) || [])
);

const table = new Table(this, "Table", {
partitionKey: {
name: "Url",
type: AttributeType.STRING,
},
billingMode: BillingMode.PAY_PER_REQUEST,
});

const schedule = new Rule(this, "EventRule", {
schedule: props.schedule || Schedule.rate(Duration.hours(1)),
...props.ruleProps,
});

const lambda = new Function(this, "HeaderCheck", {
architecture: Architecture.X86_64,
runtime: Runtime.NODEJS_20_X,
handler: "header-check.handler",
code: Code.fromAsset(join(__dirname, "lambda"), {
bundling: {
command,
image: DockerImage.fromRegistry("busybox"),
local: new Esbuild({
entryPoints: [join(__dirname, "lambda/header-check.ts")],
}),
},
}),
environment: {
URLS: props.urls.join(","),
HEADERS: headers.join(","),
TABLE: table.tableName,
TOPIC_ARN: props.snsTopic.topic.topicArn,
},
});

schedule.addTarget(new LambdaFunction(lambda));

table.grantWriteData(lambda);
table.grantReadData(lambda);
props.snsTopic.topic.grantPublish(lambda);
}
}
Loading

0 comments on commit ac75b8f

Please sign in to comment.