Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Header change detection construct #1422

Merged
merged 12 commits into from
Jan 24, 2025
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,
TheOrangePuff marked this conversation as resolved.
Show resolved Hide resolved
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
Loading