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: (IAC-276) Support Azure Application Gateway with Azure WAF #346

Closed
wants to merge 1 commit into from
Closed
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
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
Loading