Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(Terraform): front door #222

Merged
merged 13 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ All resources in these Resource Groups should be reflected in Terraform in this

For browsing the [Azure portal](https://portal.azure.com), you can [switch your `Default subscription filter`](https://docs.microsoft.com/en-us/azure/azure-portal/set-preferences).

## Access restrictions

We restrict which IP addresses that can access the app service by using a Web Application Firewall (WAF) configured on a Front Door. There is an exception for the `/healthcheck` path, which can be accessed by any IP address.

The app service itself gives access only to our Front Door and to Azure availability tests.

## Monitoring

We have [ping tests](https://docs.microsoft.com/en-us/azure/azure-monitor/app/monitor-web-app-availability) set up to notify about availability of each environment. Alerts go to [#benefits-notify](https://cal-itp.slack.com/archives/C022HHSEE3F).
Expand Down
22 changes: 17 additions & 5 deletions terraform/app_service.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,25 @@ resource "azurerm_linux_web_app" "main" {
ftps_state = "Disabled"
http2_enabled = true

dynamic "ip_restriction" {
for_each = var.IP_ADDRESS_WHITELIST
content {
ip_address = ip_restriction.value
vnet_route_all_enabled = true

ip_restriction {
name = "Front Door"
priority = 100
action = "Allow"
service_tag = "AzureFrontDoor.Backend"
headers {
x_azure_fdid = [azurerm_cdn_frontdoor_profile.main.resource_guid]
}
}
vnet_route_all_enabled = true

ip_restriction {
name = "Availability Test"
priority = 200
action = "Allow"
service_tag = "ApplicationInsightsAvailability"
}
afeld marked this conversation as resolved.
Show resolved Hide resolved

application_stack {
docker_image = "ghcr.io/cal-itp/eligibility-server"
docker_image_tag = local.env_name
Expand Down
1 change: 1 addition & 0 deletions terraform/environment.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
locals {
is_prod = terraform.workspace == "default"
is_test = terraform.workspace == "test"
env_name = local.is_prod ? "prod" : terraform.workspace
}

Expand Down
104 changes: 104 additions & 0 deletions terraform/front_door.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
locals {
front_door_name = "eligibility-server-${local.env_name}"
}

resource "azurerm_cdn_frontdoor_profile" "main" {
name = local.front_door_name
resource_group_name = data.azurerm_resource_group.main.name
sku_name = "Standard_AzureFrontDoor"
}

resource "azurerm_cdn_frontdoor_endpoint" "main" {
# used in the front door URL
name = "mst-courtesy-cards-eligibility-server-${local.env_name}"
cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id
}

resource "azurerm_cdn_frontdoor_origin_group" "main" {
name = local.front_door_name
cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id

# this block is required, and it's empty because we are fine with using the default values
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/cdn_frontdoor_origin_group#load_balancing
load_balancing {}
thekaveman marked this conversation as resolved.
Show resolved Hide resolved
}

resource "azurerm_cdn_frontdoor_origin" "main" {
name = local.front_door_name
cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.main.id

enabled = true
host_name = azurerm_linux_web_app.main.default_hostname
origin_host_header = azurerm_linux_web_app.main.default_hostname
certificate_name_check_enabled = true
weight = 1000
}

resource "azurerm_cdn_frontdoor_route" "main" {
name = local.front_door_name
cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.main.id
cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.main.id
cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.main.id]

https_redirect_enabled = true
supported_protocols = ["Http", "Https"]
patterns_to_match = ["/*"]
forwarding_protocol = "HttpsOnly"
link_to_default_domain = true
}

resource "azurerm_cdn_frontdoor_security_policy" "main" {
name = local.front_door_name
cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id

security_policies {
firewall {
cdn_frontdoor_firewall_policy_id = azurerm_cdn_frontdoor_firewall_policy.main.id
association {
patterns_to_match = ["/*"]
domain {
cdn_frontdoor_domain_id = azurerm_cdn_frontdoor_endpoint.main.host_name
}
}
}
}
}

resource "azurerm_cdn_frontdoor_firewall_policy" "main" {
name = "${local.env_name}waf"
resource_group_name = data.azurerm_resource_group.main.name
sku_name = azurerm_cdn_frontdoor_profile.main.sku_name
enabled = true
mode = "Prevention"
custom_block_response_status_code = 403
custom_block_response_body = base64encode("Forbidden")

custom_rule {
name = "healthcheck"
enabled = true
type = "MatchRule"
priority = 1
action = "Allow"

match_condition {
match_variable = "RequestUri"
operator = "Equals"
match_values = ["https://${azurerm_cdn_frontdoor_endpoint.main.host_name}:443/healthcheck"]
}
}

custom_rule {
name = "iprestriction${local.env_name}"
enabled = true
type = "MatchRule"
priority = 2
action = "Block"

match_condition {
match_variable = "SocketAddr"
operator = "Contains"
thekaveman marked this conversation as resolved.
Show resolved Hide resolved
negation_condition = true
match_values = local.is_prod ? var.IP_ADDRESS_WHITELIST_PROD : local.is_test ? var.IP_ADDRESS_WHITELIST_TEST : var.IP_ADDRESS_WHITELIST_DEV
}
}
}
afeld marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions terraform/uptime.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# when setting up access restrictions, make sure to allow the ApplicationInsightsAvailability service tag
module "healthcheck" {
source = "./uptime"

Expand Down
14 changes: 13 additions & 1 deletion terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@ variable "VELOCITY_ETL_APP_OBJECT_ID" {
type = string
}

variable "IP_ADDRESS_WHITELIST" {
variable "IP_ADDRESS_WHITELIST_DEV" {
description = "List of IP addresses allowed to connect to the app service, in CIDR notation: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#ip_address. By default, all IP addresses are allowed."
type = list(string)
default = []
}

variable "IP_ADDRESS_WHITELIST_TEST" {
description = "List of IP addresses allowed to connect to the app service, in CIDR notation: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#ip_address. By default, all IP addresses are allowed."
type = list(string)
default = []
}

variable "IP_ADDRESS_WHITELIST_PROD" {
angela-tran marked this conversation as resolved.
Show resolved Hide resolved
description = "List of IP addresses allowed to connect to the app service, in CIDR notation: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#ip_address. By default, all IP addresses are allowed."
type = list(string)
default = []
Expand Down