From 19818632efc7ad9bda39729c9a9738af75e11290 Mon Sep 17 00:00:00 2001 From: Simon Hughesdon Date: Wed, 16 Oct 2024 09:39:15 +0100 Subject: [PATCH] Add custom domain and WAF for search-api The WAF needs to be applied in a similar way to CloudFront WAFs which requires the domain configuration. This applies to the API gateway we're setting up, not to internal traffic. It permits 100 requests per client per 5 minutes, which is 1 every 3 seconds on average. This is probably sufficient for now, though if lots of users are behind a single IP, then this will need amending. This WAF also applies across the whole deployment, so should we add another API that is likely to be more frequently used, we'll definitely want to relax it and do more management at the API layer. This also registers the API Gateway with AWS Shield Advanced. --- .../search_api_gateway.tf | 62 ++++++++++++++++++- .../variables.tf | 10 +++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/terraform/deployments/govuk-publishing-infrastructure/search_api_gateway.tf b/terraform/deployments/govuk-publishing-infrastructure/search_api_gateway.tf index 9420d1164..c6da4a14b 100644 --- a/terraform/deployments/govuk-publishing-infrastructure/search_api_gateway.tf +++ b/terraform/deployments/govuk-publishing-infrastructure/search_api_gateway.tf @@ -1,3 +1,8 @@ +resource "aws_api_gateway_domain_name" "search_api_domain" { + domain_name = var.search_api_domain + certificate_arn = var.publishing_certificate_arn +} + # VPC Link to allow API Gateway to connect to the search load balancer resource "aws_api_gateway_vpc_link" "search_vpc_link" { name = "search_api_vpc_link" @@ -11,7 +16,7 @@ resource "aws_api_gateway_rest_api" "search_rest_api" { description = "API Gateway for Search API" endpoint_configuration { - types = ["EDGE"] # "Edge-optimized" routes traffic through CloudFront + types = ["EDGE"] # "Edge-optimized" routes traffic through CloudFront } } @@ -46,6 +51,61 @@ resource "aws_api_gateway_deployment" "search_deployment" { stage_name = "v0.1" # Version embedded in the published URL } +# Map API Gateway stages to custom domain +resource "aws_api_gateway_base_path_mapping" "search_api_mapping" { + domain_name = aws_api_gateway_domain_name.search_api_domain.domain_name + api_id = aws_api_gateway_rest_api.search_api.id +} + +# WAF settings +resource "aws_wafv2_web_acl" "search_api_waf" { + name = "search-api-waf" + description = "WAF for Search API with rate limiting" + scope = "CLOUDFRONT" + + default_action { + allow {} + } + + rule { + name = "rate-limit-rule" + priority = 1 + action { + block {} + } + + statement { + rate_based_statement { + limit = 100 # Limit 100 requests per IP in 5 minutes + aggregate_key_type = "IP" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "search-api-rate-limit-rule" + sampled_requests_enabled = true + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "search-api-waf" + sampled_requests_enabled = true + } +} + +resource "aws_wafv2_web_acl_association" "waf_association" { + resource_arn = aws_api_gateway_domain_name.search_api_domain.cloudfront_domain_name + web_acl_arn = aws_wafv2_web_acl.search_api_waf.arn +} + +resource "aws_shield_protection" "search_api_shield" { + name = "search-api-shield" + resource_arn = aws_api_gateway_rest_api.search_api.execution_arn +} + + output "api_gateway_cname" { value = aws_api_gateway_domain_name.search_api_domain.cloudfront_domain_name description = "CNAME to use in your DNS settings" diff --git a/terraform/deployments/govuk-publishing-infrastructure/variables.tf b/terraform/deployments/govuk-publishing-infrastructure/variables.tf index 9bb5ac1d1..692d32781 100644 --- a/terraform/deployments/govuk-publishing-infrastructure/variables.tf +++ b/terraform/deployments/govuk-publishing-infrastructure/variables.tf @@ -62,3 +62,13 @@ variable "search_api_lb_dns_name" { type = string description = "The DNS name of the search-api-v2 load balancer" } + +variable "search_api_domain" { + type = string + description = "The domain name of the API gateway" +} + +variable "publishing_certificate_arn" { + type = string + description = "The ARN of the publishing certificate" +}