Skip to content

Commit

Permalink
feat: init (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
burib authored Dec 4, 2024
1 parent fd013f0 commit 33cb73c
Show file tree
Hide file tree
Showing 12 changed files with 2,035 additions and 504 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.vscode
.terraform
.terraform.lock.hcl
*.zip
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
# terraform-aws-website-module ( beta )

#### This module is used to create a static website hosted on s3 bucket with cloudfront distribution and custom domain.
#### The module is able to protect certain routes using lambda@edge with cognito user pool authentication.

# Usage example:

```terraform
module "website" {
source = "github.com/burib/terraform-aws-ui-module?ref=v0"
source = "github.com/burib/terraform-aws-ui-module?ref=v0"
domain_name = "example.com"
wildcard_certificate_arn = "REPLACE_WITH_WILDCARD_CERTIFICATE_ARN"
route53_zone_id = "REPLACE_WITH_ROUTE53_ZONE_ID_OF_TOP_LEVEL_DOMAIN_LIKE_EXAMPLE_COM"
environment = "dev" # dev, staging, prod
domain_name = "example.com"
wildcard_certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/xxx"
route53_zone_id = "ZXXXXXXXXXXXXX"
}
# Auth integration
cognito_client_id = "REPLACE_WITH_AWS_COGNITO_USER_POOL_CLIENT_ID"
cognito_domain = "auth"
auth_urls = "REPLACE_WITH_AUTH_URLS"
cognito_token_issuer_endpoint = "REPLACE_WITH_COGNITO_TOKEN_ISSUER_ENDPOINT"
}
```
139 changes: 139 additions & 0 deletions cloudfront.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
resource "aws_cloudfront_origin_access_control" "website" {
name = var.domain_name
description = "OAC for ${var.domain_name}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}

resource "aws_cloudfront_distribution" "website" {
enabled = true
is_ipv6_enabled = true
http_version = "http2and3"
default_root_object = "index.html"
aliases = var.redirect_www_to_https ? [var.domain_name, local.www_domain] : [var.domain_name]
price_class = var.price_class
tags = var.tags

origin {
domain_name = "${aws_s3_bucket.website.bucket}.s3.${data.aws_region.current.name}.amazonaws.com"
origin_id = local.s3_origin_id
origin_access_control_id = aws_cloudfront_origin_access_control.website.id
}

default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
viewer_protocol_policy = "redirect-to-https"
compress = true

forwarded_values {
query_string = false
cookies {
forward = "none"
}
}

min_ttl = local.cache_settings.dynamic.min_ttl
default_ttl = local.cache_settings.dynamic.default_ttl
max_ttl = local.cache_settings.dynamic.max_ttl
}

dynamic "ordered_cache_behavior" {
for_each = toset(local.static_paths)
content {
path_pattern = ordered_cache_behavior.value
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
compress = true

forwarded_values {
query_string = false
cookies {
forward = "none"
}
}

viewer_protocol_policy = "redirect-to-https"
min_ttl = local.cache_settings.static.min_ttl
default_ttl = local.cache_settings.static.default_ttl
max_ttl = local.cache_settings.static.max_ttl
}
}

ordered_cache_behavior {
path_pattern = "/dashboard/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id

lambda_function_association {
event_type = "viewer-request"
lambda_arn = aws_lambda_function.auth_check.qualified_arn
include_body = false
}

forwarded_values {
query_string = true
headers = ["Authorization"]
cookies {
forward = "all"
}
}

viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 0
max_ttl = 0
}

ordered_cache_behavior {
path_pattern = "/auth/*"
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id

forwarded_values {
query_string = true
headers = ["Authorization", "Host"] # Host header needed for domain detection
cookies {
forward = "all"
}
}

viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 0
max_ttl = 0

lambda_function_association {
event_type = "viewer-request"
lambda_arn = aws_lambda_function.auth_check.qualified_arn
include_body = false
}
}

dynamic "custom_error_response" {
for_each = local.error_pages
content {
error_code = custom_error_response.value["error_code"]
response_page_path = "/${custom_error_response.key}"
error_caching_min_ttl = try(custom_error_response.value["error_caching_min_ttl"], 3600)
response_code = coalesce(try(custom_error_response.value["response_code"], null), custom_error_response.value["error_code"], 200)
}
}

viewer_certificate {
acm_certificate_arn = var.wildcard_certificate_arn
minimum_protocol_version = var.cloudfront_minimum_protocol_version
ssl_support_method = "sni-only"
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}
}
105 changes: 105 additions & 0 deletions cloudfront_lambda_at_edge.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

# IAM Role for Lambda@Edge
resource "aws_iam_role" "lambda_edge_auth_check" {
name = "lambda-edge-auth-check-${var.environment}"
permissions_boundary = "arn:aws:iam::${local.account_id}:policy/PermissionBoundary"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
]
}
}
]
})

}

resource "aws_iam_role_policy_attachment" "lambda_basic_execution_role" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.lambda_edge_auth_check.name
}


# Combined IAM Policy for SSM and CloudWatch access
resource "aws_iam_role_policy" "lambda_edge_permissions" {
name = "lambda-edge-permissions"
role = aws_iam_role.lambda_edge_auth_check.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ssm:GetParameter"
]
Resource = [
"arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/auth/cognito_user_pool_client/main/*"
]
}
]
})
}

# resource "null_resource" "wait_for_lambda_replication" {
# triggers = {
# function_version = aws_lambda_function.auth_check.version
# }
#
# provisioner "local-exec" {
# when = destroy
# command = <<EOF
# # wait 15 minutes for the replicas to be deleted
# for i in {1..90}; do
# echo "Waiting for Lambda@Edge replicas to be deleted... $((i*10))s of 900s elapsed"
# sleep 10
# done
# EOF
# }
# }

resource "aws_lambda_function" "auth_check" {
filename = data.archive_file.auth_check.output_path
function_name = "auth-check-${var.environment}"
source_code_hash = data.archive_file.auth_check.output_base64sha256
role = aws_iam_role.lambda_edge_auth_check.arn
handler = "index.handler"
runtime = "python3.13"
publish = true # Required for Lambda@Edge
timeout = 5 # Lambda@Edge has a 5-second timeout limit

# Add depends_on for deletion order
depends_on = [aws_iam_role_policy_attachment.lambda_basic_execution_role]

lifecycle {
create_before_destroy = true
}

timeouts {
delete = "60m"
}
}

# Create the Lambda code
data "archive_file" "auth_check" {
type = "zip"
output_path = "${path.module}/auth_check.zip"

source {
content = templatefile("${path.module}/functions/lambda_at_edge/auth_check.tpl.py", {
cognito_client_id = var.cognito_client_id
auth_domain = var.cognito_domain
protected_paths = jsonencode(var.protected_paths)
cognito_token_issuer_endpoint = var.cognito_token_issuer_endpoint
})
filename = "index.py"
}
}
Loading

0 comments on commit 33cb73c

Please sign in to comment.