Skip to content

Commit b0092c2

Browse files
authored
Create a seperate slow lambda for slow API requests (#248)
This will prevent clogging up our alarms
1 parent 2357147 commit b0092c2

File tree

15 files changed

+380
-240
lines changed

15 files changed

+380
-240
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: 47 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: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,14 @@ 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
5551
}
5652

57-
resource "random_password" "origin_verify_key" {
58-
length = 16
59-
special = false
60-
keepers = {
61-
force_recreation = formatdate("DD-MMM-YYYY", plantimestamp())
62-
}
53+
module "origin_verify" {
54+
source = "../../modules/origin_verify"
55+
ProjectId = var.ProjectId
6356
}
6457

6558
resource "aws_cloudfront_key_value_store" "linkry_kv" {
@@ -72,27 +65,31 @@ module "alarms" {
7265
resource_prefix = var.ProjectId
7366
main_cloudfront_distribution_id = module.frontend.main_cloudfront_distribution_id
7467
standard_sns_arn = var.GeneralSNSAlertArn
75-
main_lambda_function_name = module.lambdas.core_api_lambda_name
68+
all_lambdas = toset([module.lambdas.core_api_lambda_name, module.lambdas.core_api_slow_lambda_name, module.lambdas.core_sqs_consumer_lambda_name])
69+
performance_noreq_lambdas = toset([module.lambdas.core_api_lambda_name])
7670
}
7771

7872
module "lambdas" {
79-
source = "../../modules/lambdas"
80-
ProjectId = var.ProjectId
81-
RunEnvironment = "prod"
82-
LinkryKvArn = aws_cloudfront_key_value_store.linkry_kv.arn
83-
OriginVerifyKey = random_password.origin_verify_key.result
84-
LogRetentionDays = 30
85-
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
8682
}
8783

8884
module "frontend" {
8985
source = "../../modules/frontend"
9086
BucketPrefix = local.bucket_prefix
9187
CoreLambdaHost = module.lambdas.core_function_url
92-
OriginVerifyKey = random_password.origin_verify_key.result
88+
OriginVerifyKey = module.origin_verify.current_origin_verify_key
9389
ProjectId = var.ProjectId
9490
CoreCertificateArn = var.CoreCertificateArn
9591
CorePublicDomain = var.CorePublicDomain
92+
CoreSlowLambdaHost = module.lambdas.core_slow_function_url
9693
IcalPublicDomain = var.IcalPublicDomain
9794
LinkryPublicDomain = var.LinkryPublicDomain
9895
LinkryKvArn = aws_cloudfront_key_value_store.linkry_kv.arn

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

Lines changed: 47 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: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,43 +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
85-
OriginVerifyKey = random_password.origin_verify_key.result
80+
CoreSlowLambdaHost = module.lambdas.core_slow_function_url
81+
OriginVerifyKey = module.origin_verify.current_origin_verify_key
8682
ProjectId = var.ProjectId
8783
CoreCertificateArn = var.CoreCertificateArn
8884
CorePublicDomain = var.CorePublicDomain

terraform/modules/alarms/main.tf

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ resource "aws_cloudwatch_metric_alarm" "app_dlq_messages_alarm" {
2525
}
2626

2727
resource "aws_cloudwatch_metric_alarm" "app_latency_alarm" {
28-
alarm_name = "${var.resource_prefix}-latency-high"
29-
alarm_description = "Trailing Mean - 95% API gateway latency is > 1.25s for 2 times in 4 minutes."
28+
for_each = var.performance_noreq_lambdas
29+
alarm_name = "${each.value}-latency-high"
30+
alarm_description = "${replace(each.value, var.resource_prefix, "")} Trailing Mean - 95% API gateway latency is > 1.25s for 2 times in 4 minutes."
3031
namespace = "AWS/Lambda"
3132
metric_name = "UrlRequestLatency"
3233
extended_statistic = "tm95"
@@ -38,13 +39,14 @@ resource "aws_cloudwatch_metric_alarm" "app_latency_alarm" {
3839
var.standard_sns_arn
3940
]
4041
dimensions = {
41-
FunctionName = var.main_lambda_function_name
42+
FunctionName = each.value
4243
}
4344
}
4445

4546
resource "aws_cloudwatch_metric_alarm" "app_no_requests_alarm" {
46-
alarm_name = "${var.resource_prefix}-no-requests"
47-
alarm_description = "No requests have been received in the past 5 minutes."
47+
for_each = var.performance_noreq_lambdas
48+
alarm_name = "${each.value}-no-requests"
49+
alarm_description = "${replace(each.value, var.resource_prefix, "")}: no requests have been received in the past 5 minutes."
4850
namespace = "AWS/Lambda"
4951
metric_name = "UrlRequestCount"
5052
statistic = "Sum"
@@ -56,13 +58,14 @@ resource "aws_cloudwatch_metric_alarm" "app_no_requests_alarm" {
5658
var.priority_sns_arn
5759
]
5860
dimensions = {
59-
FunctionName = var.main_lambda_function_name
61+
FunctionName = each.value
6062
}
6163
}
6264

6365
resource "aws_cloudwatch_metric_alarm" "app_invocation_error_alarm" {
64-
alarm_name = "${var.resource_prefix}-error-invocation"
65-
alarm_description = "Lambda threw an error, meaning the init of the application itself has encountered an error"
66+
for_each = var.all_lambdas
67+
alarm_name = "${each.value}-error-invocation"
68+
alarm_description = "${replace(each.value, var.resource_prefix, "")} lambda threw an error, meaning the init of the application itself has encountered an error"
6669
namespace = "AWS/Lambda"
6770
metric_name = "Errors"
6871
statistic = "Sum"
@@ -74,7 +77,7 @@ resource "aws_cloudwatch_metric_alarm" "app_invocation_error_alarm" {
7477
var.priority_sns_arn
7578
]
7679
dimensions = {
77-
FunctionName = var.main_lambda_function_name
80+
FunctionName = each.value
7881
}
7982
}
8083

0 commit comments

Comments
 (0)