Skip to content

Commit 39d34e0

Browse files
committed
Implement warmer and key rotation
1 parent b1f00a8 commit 39d34e0

File tree

10 files changed

+213
-45
lines changed

10 files changed

+213
-45
lines changed

src/api/lambda.ts

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,41 @@ const realHandler = awsLambdaFastify(app, {
1010
callbackWaitsForEmptyEventLoop: false,
1111
binaryMimeTypes: ["application/octet-stream", "application/vnd.apple.pkpass"],
1212
});
13+
1314
type WarmerEvent = { action: "warmer" };
15+
16+
/**
17+
* Validates the origin verification header against the current and previous keys.
18+
* @returns {boolean} `true` if the request is valid, otherwise `false`.
19+
*/
20+
const validateOriginHeader = (
21+
originHeader: string | undefined,
22+
currentKey: string,
23+
previousKey: string | undefined,
24+
previousKeyExpiresAt: string | undefined,
25+
): boolean => {
26+
// 1. A header must exist to be valid.
27+
if (!originHeader) {
28+
return false;
29+
}
30+
31+
// 2. Check against the current key first for an early return on the happy path.
32+
if (originHeader === currentKey) {
33+
return true;
34+
}
35+
36+
// 3. If it's not the current key, check the previous key during the rotation window.
37+
if (previousKey && previousKeyExpiresAt) {
38+
const isExpired = new Date() >= new Date(previousKeyExpiresAt);
39+
if (originHeader === previousKey && !isExpired) {
40+
return true;
41+
}
42+
}
43+
44+
// 4. If all checks fail, the header is invalid.
45+
return false;
46+
};
47+
1448
const handler = async (
1549
event: APIGatewayEvent | WarmerEvent,
1650
context: Context,
@@ -19,12 +53,32 @@ const handler = async (
1953
return { instanceId };
2054
}
2155
event = event as APIGatewayEvent;
22-
if (process.env.ORIGIN_VERIFY_KEY) {
23-
// check that the request has the right header (coming from cloudfront)
24-
if (
25-
!event.headers ||
26-
!(event.headers["x-origin-verify"] === process.env.ORIGIN_VERIFY_KEY)
27-
) {
56+
57+
const currentKey = process.env.ORIGIN_VERIFY_KEY;
58+
const previousKey = process.env.PREVIOUS_ORIGIN_VERIFY_KEY;
59+
const previousKeyExpiresAt =
60+
process.env.PREVIOUS_ORIGIN_VERIFY_KEY_EXPIRES_AT;
61+
62+
// Log an error if the previous key has expired but is still configured.
63+
if (previousKey && previousKeyExpiresAt) {
64+
if (new Date() >= new Date(previousKeyExpiresAt)) {
65+
console.error(
66+
"Expired previous origin verify key is still present in the environment. Expired at:",
67+
previousKeyExpiresAt,
68+
);
69+
}
70+
}
71+
72+
// Proceed with verification only if a current key is set.
73+
if (currentKey) {
74+
const isValid = validateOriginHeader(
75+
event.headers?.["x-origin-verify"],
76+
currentKey,
77+
previousKey,
78+
previousKeyExpiresAt,
79+
);
80+
81+
if (!isValid) {
2882
const newError = new ValidationError({
2983
message: "Request is not valid.",
3084
});
@@ -38,9 +92,11 @@ const handler = async (
3892
isBase64Encoded: false,
3993
};
4094
}
95+
4196
delete event.headers["x-origin-verify"];
4297
}
43-
// else proceed with handler logic
98+
99+
// If verification is disabled or passed, proceed with the real handler logic.
44100
return await realHandler(event, context).catch((e) => {
45101
console.error(e);
46102
const newError = new InternalServerError({
@@ -58,5 +114,5 @@ const handler = async (
58114
});
59115
};
60116

61-
await app.ready(); // needs to be placed after awsLambdaFastify call because of the decoration: https://github.com/fastify/aws-lambda-fastify/blob/master/index.js#L9
117+
await app.ready();
62118
export { handler };

terraform/envs/prod/.terraform.lock.hcl

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

terraform/envs/prod/main.tf

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ module "sqs_queues" {
4545
core_sqs_consumer_lambda_name = module.lambdas.core_sqs_consumer_lambda_name
4646
}
4747

48-
module "lambda_warmer" {
49-
source = "github.com/acm-uiuc/terraform-modules/lambda-warmer?ref=v1.0.1"
50-
function_to_warm = module.lambdas.core_api_lambda_name
51-
}
5248
module "dynamo" {
5349
source = "../../modules/dynamo"
5450
ProjectId = var.ProjectId
@@ -74,14 +70,15 @@ module "alarms" {
7470
}
7571

7672
module "lambdas" {
77-
source = "../../modules/lambdas"
78-
ProjectId = var.ProjectId
79-
RunEnvironment = "prod"
80-
LinkryKvArn = aws_cloudfront_key_value_store.linkry_kv.arn
81-
CurrentOriginVerifyKey = module.origin_verify.current_origin_verify_key
82-
PreviousOriginVerifyKey = module.origin_verify.previous_origin_verify_key
83-
LogRetentionDays = 30
84-
EmailDomain = var.EmailDomain
73+
source = "../../modules/lambdas"
74+
ProjectId = var.ProjectId
75+
RunEnvironment = "prod"
76+
LinkryKvArn = aws_cloudfront_key_value_store.linkry_kv.arn
77+
CurrentOriginVerifyKey = module.origin_verify.current_origin_verify_key
78+
PreviousOriginVerifyKey = module.origin_verify.previous_origin_verify_key
79+
PreviousOriginVerifyKeyExpiresAt = module.origin_verify.previous_invalid_time
80+
LogRetentionDays = 30
81+
EmailDomain = var.EmailDomain
8582
}
8683

8784
module "frontend" {

terraform/envs/qa/.terraform.lock.hcl

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

terraform/envs/qa/main.tf

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,44 +46,39 @@ locals {
4646
}
4747
}
4848

49-
50-
module "lambda_warmer" {
51-
source = "github.com/acm-uiuc/terraform-modules/lambda-warmer?ref=v1.0.1"
52-
function_to_warm = module.lambdas.core_api_lambda_name
53-
}
5449
module "dynamo" {
5550
source = "../../modules/dynamo"
5651
ProjectId = var.ProjectId
5752
}
5853

59-
resource "random_password" "origin_verify_key" {
60-
length = 16
61-
special = false
62-
keepers = {
63-
force_recreation = formatdate("DD-MMM-YYYY", plantimestamp())
64-
}
54+
module "origin_verify" {
55+
source = "../../modules/origin_verify"
56+
ProjectId = var.ProjectId
6557
}
58+
6659
resource "aws_cloudfront_key_value_store" "linkry_kv" {
6760
name = "${var.ProjectId}-cloudfront-linkry-kv"
6861
}
6962

7063

7164
module "lambdas" {
72-
source = "../../modules/lambdas"
73-
ProjectId = var.ProjectId
74-
RunEnvironment = "dev"
75-
LinkryKvArn = aws_cloudfront_key_value_store.linkry_kv.arn
76-
OriginVerifyKey = random_password.origin_verify_key.result
77-
LogRetentionDays = 30
78-
EmailDomain = var.EmailDomain
65+
source = "../../modules/lambdas"
66+
ProjectId = var.ProjectId
67+
RunEnvironment = "dev"
68+
LinkryKvArn = aws_cloudfront_key_value_store.linkry_kv.arn
69+
CurrentOriginVerifyKey = module.origin_verify.current_origin_verify_key
70+
PreviousOriginVerifyKey = module.origin_verify.previous_origin_verify_key
71+
PreviousOriginVerifyKeyExpiresAt = module.origin_verify.previous_invalid_time
72+
LogRetentionDays = 30
73+
EmailDomain = var.EmailDomain
7974
}
8075

8176
module "frontend" {
8277
source = "../../modules/frontend"
8378
BucketPrefix = local.bucket_prefix
8479
CoreLambdaHost = module.lambdas.core_function_url
8580
CoreSlowLambdaHost = module.lambdas.core_slow_function_url
86-
OriginVerifyKey = random_password.origin_verify_key.result
81+
OriginVerifyKey = module.origin_verify.current_origin_verify_key
8782
ProjectId = var.ProjectId
8883
CoreCertificateArn = var.CoreCertificateArn
8984
CorePublicDomain = var.CorePublicDomain

terraform/envs/qa/tfplan

72.6 KB
Binary file not shown.

terraform/modules/frontend/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ locals {
2121
api_cache_behaviors = {
2222
"/api/v1/syncIdentity" = {
2323
target_origin_id = "SlowLambdaFunction"
24-
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6"
24+
cache_policy_id = aws_cloudfront_cache_policy.no_cache.id
2525
},
2626
"/api/v1/organizations" = {
2727
target_origin_id = "LambdaFunction"

terraform/modules/lambdas/main.tf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ resource "aws_lambda_function" "api_lambda" {
341341
"AWS_CRT_NODEJS_BINARY_RELATIVE_PATH" = "node_modules/aws-crt/dist/bin/linux-arm64-glibc/aws-crt-nodejs.node"
342342
ORIGIN_VERIFY_KEY = var.CurrentOriginVerifyKey
343343
PREVIOUS_ORIGIN_VERIFY_KEY = var.PreviousOriginVerifyKey
344+
PREVIOUS_ORIGIN_VERIFY_KEY_EXPIRES_AT = var.PreviousOriginVerifyKeyExpiresAt
344345
EntraRoleArn = aws_iam_role.entra_role.arn
345346
LinkryKvArn = var.LinkryKvArn
346347
"NODE_OPTIONS" = "--enable-source-maps"
@@ -401,6 +402,7 @@ resource "aws_lambda_function" "slow_lambda" {
401402
"AWS_CRT_NODEJS_BINARY_RELATIVE_PATH" = "node_modules/aws-crt/dist/bin/linux-arm64-glibc/aws-crt-nodejs.node"
402403
ORIGIN_VERIFY_KEY = var.CurrentOriginVerifyKey
403404
PREVIOUS_ORIGIN_VERIFY_KEY = var.PreviousOriginVerifyKey
405+
PREVIOUS_ORIGIN_VERIFY_KEY_EXPIRES_AT = var.PreviousOriginVerifyKeyExpiresAt
404406
EntraRoleArn = aws_iam_role.entra_role.arn
405407
LinkryKvArn = var.LinkryKvArn
406408
"NODE_OPTIONS" = "--enable-source-maps"
@@ -413,6 +415,19 @@ resource "aws_lambda_function_url" "slow_api_lambda_function_url" {
413415
authorization_type = "NONE"
414416
}
415417

418+
module "lambda_warmer_main" {
419+
source = "github.com/acm-uiuc/terraform-modules/lambda-warmer?ref=v1.0.1"
420+
function_to_warm = local.core_api_lambda_name
421+
}
422+
423+
module "lambda_warmer_slow" {
424+
source = "github.com/acm-uiuc/terraform-modules/lambda-warmer?ref=v1.0.1"
425+
function_to_warm = local.core_api_slow_lambda_name
426+
}
427+
428+
429+
// Outputs
430+
416431
output "core_function_url" {
417432
value = replace(replace(aws_lambda_function_url.api_lambda_function_url.function_url, "https://", ""), "/", "")
418433
}

terraform/modules/lambdas/variables.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ variable "PreviousOriginVerifyKey" {
2626
sensitive = true
2727
}
2828

29+
variable "PreviousOriginVerifyKeyExpiresAt" {
30+
type = string
31+
}
32+
2933
variable "LogRetentionDays" {
3034
type = number
3135
}

0 commit comments

Comments
 (0)