Skip to content

Commit

Permalink
feat: (IAC-276) Support Azure Application Gateway with Azure WAF
Browse files Browse the repository at this point in the history
  • Loading branch information
riragh committed Nov 22, 2023
1 parent ab9ada5 commit d2bc8d6
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 23 deletions.
1 change: 1 addition & 0 deletions iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ resource "azurerm_user_assigned_identity" "uai" {
name = "${var.prefix}-aks-identity"
resource_group_name = local.aks_rg.name
location = var.location
tags = var.tags
}
7 changes: 6 additions & 1 deletion locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ locals {
cluster_endpoint_public_access_cidrs = var.cluster_api_mode == "private" ? [] : (var.cluster_endpoint_public_access_cidrs == null ? local.default_public_access_cidrs : var.cluster_endpoint_public_access_cidrs)
postgres_public_access_cidrs = var.postgres_public_access_cidrs == null ? local.default_public_access_cidrs : var.postgres_public_access_cidrs

subnets = { for k, v in var.subnets : k => v if !(k == "netapp" && var.storage_type == "standard") }
subnets = { for k, v in var.subnets : k => v if !(k == "netapp" && var.storage_type == "standard") && !(k == "gateway" && !var.create_app_gateway) }

# Kubernetes
kubeconfig_filename = "${var.prefix}-aks-kubeconfig.conf"
Expand All @@ -39,6 +39,11 @@ locals {
}
} : {}

# App Gateway
app_gateway_config = merge(var.app_gateway_defaults, var.app_gateway_config)
waf_policy_config = var.waf_policy != null ? jsondecode(file(var.waf_policy)) : null
waf_policy_enabled = local.waf_policy_config != null ? length(local.waf_policy_config) != 0 ? true : false : false

# Container Registry
container_registry_sku = title(var.container_registry_sku)

Expand Down
24 changes: 24 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ module "message_broker" {
tags = var.tags
}

module "app_gateway" {
source = "./modules/azurerm_app_gateway"
count = var.create_app_gateway ? 1 : 0

prefix = var.prefix
resource_group_name = local.aks_rg.name
location = var.location
sku = local.app_gateway_config.sku
port = local.app_gateway_config.port
protocol = local.app_gateway_config.protocol
waf_policy_enabled = local.waf_policy_enabled
waf_policy_config = local.waf_policy_config
backend_host_name = local.app_gateway_config.backend_host_name
backend_trusted_root_certificate = local.app_gateway_config.backend_trusted_root_certificate
ssl_certificate = local.app_gateway_config.ssl_certificate
identity_ids = local.app_gateway_config.identity_ids
backend_address_pool_fqdn = local.app_gateway_config.backend_address_pool_fqdn
probe = local.app_gateway_config.probe
subnet_id = module.vnet.subnets["gateway"].id
tags = var.tags

depends_on = [module.vnet]
}

data "external" "git_hash" {
program = ["files/tools/iac_git_info.sh"]
}
Expand Down
36 changes: 18 additions & 18 deletions modules/azure_aks/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@

# Reference: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster
resource "azurerm_kubernetes_cluster" "aks" {
name = var.aks_cluster_name
location = var.aks_cluster_location
resource_group_name = var.aks_cluster_rg
dns_prefix = var.aks_private_cluster == false || var.aks_cluster_private_dns_zone_id == "" ? var.aks_cluster_dns_prefix : null
dns_prefix_private_cluster = var.aks_private_cluster && var.aks_cluster_private_dns_zone_id != "" ? var.aks_cluster_dns_prefix : null

sku_tier = var.aks_cluster_sku_tier
role_based_access_control_enabled = true
http_application_routing_enabled = false
name = var.aks_cluster_name
location = var.aks_cluster_location
resource_group_name = var.aks_cluster_rg
dns_prefix = var.aks_private_cluster == false || var.aks_cluster_private_dns_zone_id == "" ? var.aks_cluster_dns_prefix : null
dns_prefix_private_cluster = var.aks_private_cluster && var.aks_cluster_private_dns_zone_id != "" ? var.aks_cluster_dns_prefix : null

sku_tier = var.aks_cluster_sku_tier
role_based_access_control_enabled = true
http_application_routing_enabled = false

# https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions
# az aks get-versions --location eastus -o table
kubernetes_version = var.kubernetes_version
api_server_authorized_ip_ranges = var.aks_cluster_endpoint_public_access_cidrs
private_cluster_enabled = var.aks_private_cluster
private_dns_zone_id = var.aks_private_cluster && var.aks_cluster_private_dns_zone_id != "" ? var.aks_cluster_private_dns_zone_id : (var.aks_private_cluster ? "System" : null)
kubernetes_version = var.kubernetes_version
api_server_authorized_ip_ranges = var.aks_cluster_endpoint_public_access_cidrs
private_cluster_enabled = var.aks_private_cluster
private_dns_zone_id = var.aks_private_cluster && var.aks_cluster_private_dns_zone_id != "" ? var.aks_cluster_private_dns_zone_id : (var.aks_private_cluster ? "System" : null)

network_profile {
network_plugin = var.aks_network_plugin
Expand All @@ -45,7 +45,7 @@ resource "azurerm_kubernetes_cluster" "aks" {
content {
admin_username = var.aks_cluster_node_admin
ssh_key {
key_data = var.aks_cluster_ssh_public_key
key_data = var.aks_cluster_ssh_public_key
}
}
}
Expand Down Expand Up @@ -80,7 +80,7 @@ resource "azurerm_kubernetes_cluster" "aks" {
dynamic "identity" {
for_each = var.aks_uai_id == null ? [] : [1]
content {
type = "UserAssigned"
type = "UserAssigned"
identity_ids = [var.aks_uai_id]
}
}
Expand Down Expand Up @@ -108,8 +108,8 @@ resource "azurerm_kubernetes_cluster" "aks" {

}

data "azurerm_public_ip" "cluster_public_ip" {
count = var.cluster_egress_type == "loadBalancer" ? 1 : 0
data "azurerm_public_ip" "cluster_public_ip" {
count = var.cluster_egress_type == "loadBalancer" ? 1 : 0

# effective_outbound_ips is a set of strings, that needs to be converted to a list type
name = split("/", tolist(azurerm_kubernetes_cluster.aks.network_profile[0].load_balancer_profile[0].effective_outbound_ips)[0])[8]
Expand Down
4 changes: 2 additions & 2 deletions modules/azure_aks/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ variable "aks_dns_service_ip" {
type = string
default = "10.0.0.10"
validation {
condition = var.aks_dns_service_ip != null ? can(regex("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",var.aks_dns_service_ip)) : false
condition = var.aks_dns_service_ip != null ? can(regex("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", var.aks_dns_service_ip)) : false
error_message = "ERROR: aks_dns_service_ip - value must not be null and must be a valid IP address."
}

Expand Down Expand Up @@ -225,6 +225,6 @@ variable "cluster_egress_type" {
}

variable "aks_cluster_private_dns_zone_id" {
type = string
type = string
default = ""
}
216 changes: 216 additions & 0 deletions modules/azurerm_app_gateway/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Copyright © 2020-2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

## https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway
## https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/web_application_firewall_policy

locals {
backend_address_pool_name = "${var.prefix}-backend-pool"
frontend_port_name = "${var.prefix}-frontend-port"
frontend_ip_configuration_name = "${var.prefix}-frontend-ip"
http_setting_name = "${var.prefix}-backend-setting"
listener_name = "${var.prefix}-listener"
request_routing_rule_name = "${var.prefix}-routing-rule"
}

resource "azurerm_public_ip" "gateway_ip" {
name = "${var.prefix}-gateway-public_ip"
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Static"
sku = "Standard"
domain_name_label = var.backend_host_name == null ? "${var.prefix}-appgateway" : null
tags = var.tags
}

resource "azurerm_web_application_firewall_policy" "waf_policy" {
count = var.waf_policy_enabled ? 1 : 0

name = "${var.prefix}-waf-policy"
resource_group_name = var.resource_group_name
location = var.location
tags = var.tags

dynamic "custom_rules" {
for_each = var.waf_policy_config.custom_rules
content {
name = custom_rules.value.name
priority = custom_rules.value.priority
rule_type = custom_rules.value.rule_type
action = custom_rules.value.action
dynamic "match_conditions" {
for_each = custom_rules.value.match_conditions
content {
operator = match_conditions.value.operator
negation_condition = match_conditions.value.negation_condition
match_values = match_conditions.value.match_values
dynamic "match_variables" {
for_each = match_conditions.value.match_variables
content {
variable_name = match_variables.value.variable_name
}
}
}
}
}
}

dynamic "policy_settings" {
for_each = var.waf_policy_config.policy_settings != null ? [var.waf_policy_config.policy_settings] : []
content {
enabled = policy_settings.value.enabled
mode = policy_settings.value.mode
request_body_check = policy_settings.value.request_body_check
file_upload_limit_in_mb = policy_settings.value.file_upload_limit_in_mb
max_request_body_size_in_kb = policy_settings.value.max_request_body_size_in_kb
}
}

managed_rules {
dynamic "exclusion" {
for_each = var.waf_policy_config.managed_rules.exclusion
content {
match_variable = exclusion.value.match_variable
selector = exclusion.value.selector
selector_match_operator = exclusion.value.selector_match_operator
dynamic "excluded_rule_set" {
for_each = exclusion.value.excluded_rule_set
content {
type = excluded_rule_set.value.type
version = excluded_rule_set.value.version
dynamic "rule_group" {
for_each = excluded_rule_set.value.rule_group
content {
rule_group_name = rule_group.value.rule_group_name
excluded_rules = rule_group.value.excluded_rules
}
}
}
}
}
}

dynamic "managed_rule_set" {
for_each = var.waf_policy_config.managed_rules.managed_rule_set
content {
type = managed_rule_set.value.type
version = managed_rule_set.value.version
dynamic "rule_group_override" {
for_each = managed_rule_set.value.rule_group_override
content {
rule_group_name = rule_group_override.value.rule_group_name
dynamic "rule" {
for_each = rule_group_override.value.rule
content {
id = rule.value.id
enabled = rule.value.enabled
action = rule.value.action
}
}
}
}
}
}
}
}


resource "azurerm_application_gateway" "appgateway" {
name = "${var.prefix}-appgateway"
resource_group_name = var.resource_group_name
location = var.location
firewall_policy_id = var.waf_policy_enabled ? azurerm_web_application_firewall_policy.waf_policy[0].id : null
force_firewall_policy_association = var.waf_policy_enabled ? true : false

sku {
name = var.waf_policy_enabled ? "WAF_v2" : var.sku
tier = var.waf_policy_enabled ? "WAF_v2" : "Standard_v2"
capacity = 2
}

gateway_ip_configuration {
name = "${var.prefix}-gateway-ip-configuration"
subnet_id = var.subnet_id
}

frontend_port {
name = local.frontend_port_name
port = var.port
}

frontend_ip_configuration {
name = local.frontend_ip_configuration_name
public_ip_address_id = azurerm_public_ip.gateway_ip.id
}

backend_address_pool {
name = local.backend_address_pool_name
fqdns = var.backend_address_pool_fqdn != null ? length(var.backend_address_pool_fqdn) != 0 ? var.backend_address_pool_fqdn : null : null
}

dynamic "trusted_root_certificate" {
for_each = var.backend_trusted_root_certificate == null ? [] : [1]

content {
name = "root-cert"
data = filebase64(var.backend_trusted_root_certificate)
}
}

dynamic "ssl_certificate" {
for_each = var.ssl_certificate == null ? [] : var.ssl_certificate

content {
name = "ListenerCert"
data = ssl_certificate.value.data != null ? filebase64(ssl_certificate.value.data) : null
password = ssl_certificate.value.password
key_vault_secret_id = ssl_certificate.value.data != null ? null : ssl_certificate.value.key_vault_secret_id
}
}

backend_http_settings {
name = local.http_setting_name
cookie_based_affinity = "Disabled"
port = var.port
protocol = var.protocol
request_timeout = 60
probe_name = var.probe != null ? "default-probe" : null
host_name = var.backend_host_name == null ? azurerm_public_ip.gateway_ip.fqdn : var.backend_host_name
trusted_root_certificate_names = var.backend_trusted_root_certificate == null ? null : ["root-cert"]
}

http_listener {
name = local.listener_name
frontend_ip_configuration_name = local.frontend_ip_configuration_name
frontend_port_name = local.frontend_port_name
protocol = var.protocol
ssl_certificate_name = var.ssl_certificate == null ? null : "ListenerCert"
}

request_routing_rule {
name = local.request_routing_rule_name
rule_type = "Basic"
http_listener_name = local.listener_name
backend_address_pool_name = local.backend_address_pool_name
backend_http_settings_name = local.http_setting_name
priority = 1
}

dynamic "probe" {
for_each = var.probe != null ? var.probe : []

content {
name = probe.value.name
interval = 60
protocol = var.protocol
path = probe.value.path
timeout = 30
unhealthy_threshold = 3
pick_host_name_from_backend_http_settings = true
}
}

tags = var.tags

depends_on = [azurerm_web_application_firewall_policy.waf_policy]
}
6 changes: 6 additions & 0 deletions modules/azurerm_app_gateway/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright © 2020-2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

output "gateway_frontend_ip" {
value = azurerm_public_ip.gateway_ip.ip_address
}
Loading

0 comments on commit d2bc8d6

Please sign in to comment.