This terraform module creates a Lambda@Edge Lambda to be used within a viewer policy of a CloudFront Distribution to enforce Cognito Authentication through a configured Cognito User Pool.
- Terraform version >= 1.0.X
- NodeJS + NPM (compatible with NodeJS 20.X.X)
- Used for
npm ci
dependency installation for Lambda@Edge Bundle.
- Used for
- Terraform AWS Provider in
us-east-1
- Requirement for CloudFront + Lambda@Edge runtime.
- Existing Cognito User Pool and User Pool Client.
To use this module, source it into your Terraform project like so:
data "aws_cognito_user_pools" "user_pool" {
name = "my_cognito_pool_name"
}
resource "aws_cognito_user_pool_client" "cognito_client" {
...
}
module "cloudfront_cognito_auth_lambda" {
source = "git::https://github.com/disney/terraform-aws-lambda-at-edge-cognito-authentication.git?ref=<version-ref>"
name = "my-application"
cognito_user_pool_name = data.aws_cognito_user_pools.user_pool.name
cognito_user_pool_region = "us-east-1"
cognito_user_pool_id = aws_cognito_user_pool_client.cognito_client.user_pool_id
cognito_user_pool_app_client_id = aws_cognito_user_pool_client.cognito_client.id
tags = { foo = "bar" }
}
You can use the output of this module to pass as a rule on your CloudFront distribution cache on viewer-requests (typically on an S3 Bucket Origin with an OAI).
resource "aws_cloudfront_distribution" "my_cloudfront_distribution" {
...
ordered_cache_behavior {
...
lambda_function_association {
event_type = "viewer-request"
lambda_arn = module.cloudfront_cognito_auth_lambda.qualified_arn
}
}
}
The module supports three different strategies to make the required cognito-at-edge config available to the lambda function, controlled by the lambda_config_mode
variable.
Note
While the default config mode is dynamic
(for backwards compatibility), either of the other modes is a better choice for most users.
In this mode, the lambda introspects the IAM role/policy to determine the SSM Parameter that contains the config. This is the current/default behaviour if not supplied by the calling module.
This requires at least three round trips to AWS, which can cause 503 responses from Cloudfront during lambda cold starts when the round trips and cognito-at-edge initialisation collectively takes longer than the maximum lambda-at-edge run time (5 seconds). The lambda code does not need to be redeployed when config changes are made (so terraform applies are fast).
In this mode, the name of the parameter store entry that contains the config is written into a config file shipped with the lambda. Versus 'dynamic' mode this removes the need for IAM introspection, which saves two round trips to AWS, which reduces the likelihood that cold starts exceed the max run time. The net result is that this mode will produce signicantly less 503's, particularly if your cloudfront distribution is used in regions a long distance from us-east-1, or only used sporadically. Changes to the config of the lambda will still be fast, but changing the actual SSM entry that holds the config will force a lambda redeploy (which takes minutes). This mode will be cheaper to run than 'dynamic' (as on average cold starts consume 1-2 seconds less lambda runtime).
In this mode, the entire cognito-at-edge config is written into a config file shipped with the lambda. Versus 'hybrid' mode, this saves another round trip to AWS (the fetching of the config from SSM), which yields even faster cold start times and will therefore be cheaper still to run. The downsides:
- any config change requires a reprovisioning of the lambda (slow)
- secrets passed into the module config will be written to the config file in the local file system, which may lead to secret leakage.
To prevent this happening unintentionally, if the calling module setscognito_user_pool_app_client_secret
andlambda_config_mode = static
, then plan/apply will fail unlesslambda_config_allow_insecure_secret_storage = true
is also set.
If a Lambda@Edge function has IAM permission to logs:CreateLogGroup
then it will create a Cloudwatch log group called /aws/lambda/us-east-1.<lambda-name>
in every CloudFront region that serves a request, which can result in logging proliferating across many regions, as described in https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-logs.html#lambda-at-edge-logs . This auto-created log group has no retention policy set, so in addition to Cloudwatch ingest costs, Cloudwatch storage costs increase over time for the life of the function. Setting cloudwatch_enable_log_group_create = false
will mean that the IAM policies in the module will not grant logs:CreateLogGroup
to the edge-auth Lambda@Edge function, and if the log group does not exit, no logging happens (avoiding Cloudwatch ingest and storage costs). Note:
- If the variable is
false
and logging is required in a specific region, the caller is responsible for ensuring the log group exists in that region. - Retroactively setting the variable to
false
is not sufficient to disable logging if the log group already exists in a region - manual deletion of the unwanted log groups will be required to prevent further logging. - If a policy outside the module grants the edge-auth function rights to
logs:CreateLogGroup
, then Lambda@Edge will still create the log group and log.
Note that if a destroy action is performed on this terraform module, terraform is unable to delete the Lambda@Edge that was published as a part of this infrastructure (This is noted by this issue on the Terraform provider). It will only be removed from the terraform state as the skip_destroy
flag is set to true.
In order to properly delete this resource, it should be manually cleaned up, instructions here.
Name | Version |
---|---|
terraform | ~> 1.0 |
aws | >= 4.57 |
Name | Version |
---|---|
archive | 2.4.0 |
aws | 5.26.0 |
local | 2.5.2 |
null | 3.2.2 |
No modules.
Name | Type |
---|---|
aws_iam_role.lambda_at_edge | resource |
aws_iam_role_policy.allow_cloudwatch_logs | resource |
aws_iam_role_policy.lambda_edge_self_role_read | resource |
aws_iam_role_policy.ssm_parameter_decrypt | resource |
aws_iam_role_policy.ssm_parameter_permission_for_lambda_auth | resource |
aws_kms_key.ssm_kms_key | resource |
aws_lambda_function.cloudfront_auth_edge | resource |
aws_ssm_parameter.lambda_configuration_parameters | resource |
local_file.lambda_configuration | resource |
null_resource.install_lambda_dependencies | resource |
archive_file.lambda_edge_bundle | data source |
aws_caller_identity.current | data source |
aws_iam_policy_document.allow_cloudwatch_logs | data source |
aws_iam_policy_document.allow_lambda_edge_self_role_read | data source |
aws_iam_policy_document.allow_lambda_service_assume | data source |
aws_iam_policy_document.allow_ssm_parameter_decrypt | data source |
aws_iam_policy_document.allow_ssm_parameter_permission_for_lambda_auth | data source |
Name | Description | Type | Default | Required |
---|---|---|---|---|
cloudwatch_enable_log_group_create | Allow Lambda@Edge to create log groups in cloudwatch, defaults to true. | bool |
true |
no |
cognito_additional_settings | Map of any to configure any additional cognito@edge parameters not handled by this module. | any |
{} |
no |
cognito_cookie_expiration_days | Number of days to keep the cognito cookie valid. | number |
7 |
no |
cognito_disable_cookie_domain | Sets domain attribute in cookies, defaults to false. | bool |
false |
no |
cognito_log_level | Logging level. Default: 'silent' | string |
"silent" |
no |
cognito_redirect_path | Optional path to redirect to after a successful cognito login. | string |
"" |
no |
cognito_user_pool_app_client_id | Cognito User Pool App Client ID for the targeted user pool. | string |
n/a | yes |
cognito_user_pool_app_client_secret | Cognito User Pool App Client Secret for the targeted user pool. NOTE: This is currently not compatible with AppSync applications. | string |
null |
no |
cognito_user_pool_domain | Optional: Full Domain of the Cognito User Pool to utilize. Mutually exclusive with 'cognito_user_pool_name'. | string |
"" |
no |
cognito_user_pool_id | Cognito User Pool ID for the targeted user pool. | string |
n/a | yes |
cognito_user_pool_name | Name of the Cognito User Pool to utilize. Required if 'cognito_user_pool_domain' is not set. | string |
"" |
no |
cognito_user_pool_region | AWS region where the cognito user pool was created. | string |
"us-west-2" |
no |
lambda_config_allow_insecure_secret_storage | Allow secrets to be stored in the lambda config file, defaults to false. | bool |
false |
no |
lambda_config_mode | Which strategy to use to supply config to the lambda function, defaults to 'dynamic'. | string |
"dynamic" |
no |
lambda_runtime | Lambda runtime to utilize for Lambda@Edge. | string |
"nodejs20.x" |
no |
lambda_timeout | Amount of timeout in seconds to set on for Lambda@Edge. | number |
5 |
no |
name | Name to prefix on all infrastructure created by this module. | string |
n/a | yes |
tags | Map of tags to attach to all AWS resources created by this module. | map(string) |
{} |
no |
Name | Description |
---|---|
arn | ARN for the Lambda@Edge created by this module. |
function_name | Name of the Lambda@Edge created by this module. |
qualified_arn | Qualified ARN for the Lambda@Edge created by this module. |