diff --git a/src/_variables.resources.tf b/src/_variables.resources.tf index 71cdc78b..3aa2b5c6 100644 --- a/src/_variables.resources.tf +++ b/src/_variables.resources.tf @@ -73,3 +73,16 @@ variable "recovery_vaults" { default = {} } variable "disk_encryption_sets" { default = {} } variable "logic_apps_standard" { default = {} } + +variable "application_gateways" { default = {} } + +variable "log_categories" { default = {} } + +variable "excluded_log_categories" { default = {} } + +variable "metric_categories" { default = {} } + +variable "logs_destination_ids" { default = {} } + +variable "log_analytics_destination_type" { default = {} } +variable "diagnostic_settings" { default = {} } diff --git a/src/_variables.tf b/src/_variables.tf index 34468cca..dfcdb123 100644 --- a/src/_variables.tf +++ b/src/_variables.tf @@ -39,6 +39,7 @@ variable "global_settings" { } } + variable "landingzone" { description = "Landing zone metadata and tfstate dependencies" type = object({ @@ -50,3 +51,4 @@ variable "landingzone" { }))) }) } + diff --git a/src/diagnostic.tf b/src/diagnostic.tf new file mode 100644 index 00000000..890b094f --- /dev/null +++ b/src/diagnostic.tf @@ -0,0 +1,26 @@ +module "diagnostic_setting" { + source = "./modules/monitoring/diagnostic_setting" + for_each = var.diagnostic_settings + + + settings = each.value + resources = merge( + { + (var.landingzone.key) = { + resource_groups = module.resource_groups + storage_accounts = module.storage_accounts + keyvaults = module.keyvaults + log_analytics_workspaces = module.log_analytics_workspaces + + } + }, + { + for k, v in module.remote_states : k => v.outputs + } + ) + global_settings = local.global_settings + + client_config = { + landingzone_key = var.landingzone.key + } +} diff --git a/src/modules/_networking/application_gateway/_locals.tf b/src/modules/_networking/application_gateway/_locals.tf new file mode 100644 index 00000000..51f4ae40 --- /dev/null +++ b/src/modules/_networking/application_gateway/_locals.tf @@ -0,0 +1,22 @@ +locals { + resource_group = var.resources[ + try(var.settings.resource_group_lz_key, var.client_config.landingzone_key) + ].resource_groups[var.settings.resource_group_ref] + + resource_group_name = local.resource_group.name + location = local.resource_group.location + + subnet = var.resources[ + try(var.settings.subnet_lz_key, var.client_config.landingzone_key) + ].virtual_networks[ + var.settings.virtual_network + ].subnets[ + split("/", var.settings.subnet_ref)[1] + ] + + tags = merge( + var.global_settings.tags, + var.global_settings.inherit_resource_group_tags ? local.resource_group.tags : {}, + try(var.settings.tags, {}) + ) +} diff --git a/src/modules/_networking/application_gateway/main.tf b/src/modules/_networking/application_gateway/main.tf new file mode 100644 index 00000000..7bb56157 --- /dev/null +++ b/src/modules/_networking/application_gateway/main.tf @@ -0,0 +1,95 @@ +resource "azurerm_application_gateway" "main" { + name = var.settings.name + location = local.location + resource_group_name = local.resource_group_name + tags = local.tags + + sku { + name = var.settings.sku.name + tier = var.settings.sku.tier + capacity = var.settings.sku.capacity + } + + dynamic "gateway_ip_configuration" { + for_each = try(var.settings.gateway_ip_configuration, {}) + content { + name = gateway_ip_configuration.value.name + subnet_id = var.resources[ + try(gateway_ip_configuration.value.lz_key, var.client_config.landingzone_key) + ].virtual_networks[ + split("/", gateway_ip_configuration.value.subnet_ref)[0] + ].subnets[ + split("/", gateway_ip_configuration.value.subnet_ref)[1] + ].id + } + } + + dynamic "frontend_ip_configuration" { + for_each = try(var.settings.frontend_ip_configuration, {}) + content { + name = frontend_ip_configuration.value.name + public_ip_address_id = var.resources[ + try(frontend_ip_configuration.value.lz_key, var.client_config.landingzone_key) + ].public_ips[frontend_ip_configuration.value.public_ip].id + } + } + + dynamic "frontend_port" { + for_each = try(var.settings.frontend_ports, {}) + content { + name = frontend_port.value.name + port = frontend_port.value.port + } + } + + dynamic "backend_address_pool" { + for_each = try(var.settings.backend_address_pools, {}) + content { + name = backend_address_pool.value.name + ip_addresses = try(backend_address_pool.value.ip_addresses, []) + } + } + + dynamic "backend_http_settings" { + for_each = try(var.settings.backend_http_settings_list, {}) + content { + name = backend_http_settings.value.name + port = backend_http_settings.value.port + protocol = backend_http_settings.value.protocol + cookie_based_affinity = try(backend_http_settings.value.cookie_based_affinity, "Disabled") + request_timeout = try(backend_http_settings.value.request_timeout, 20) + } + } + + dynamic "http_listener" { + for_each = try(var.settings.http_listeners, {}) + content { + name = http_listener.value.name + frontend_ip_configuration_name = http_listener.value.frontend_ip_configuration_name + frontend_port_name = http_listener.value.frontend_port_name + protocol = http_listener.value.protocol + host_name = try(http_listener.value.host_name, null) + } + } + + dynamic "request_routing_rule" { + for_each = try(var.settings.request_routing_rules, {}) + content { + name = request_routing_rule.value.name + rule_type = request_routing_rule.value.rule_type + http_listener_name = request_routing_rule.value.http_listener_name + backend_address_pool_name = request_routing_rule.value.backend_address_pool_name + backend_http_settings_name = request_routing_rule.value.backend_http_settings_name + priority = try(request_routing_rule.value.priority, 100) + } + } + + dynamic "timeouts" { + for_each = can(var.settings.timeouts) ? [1] : [] + content { + create = try(var.settings.timeouts.create, null) + update = try(var.settings.timeouts.update, null) + delete = try(var.settings.timeouts.delete, null) + } + } +} diff --git a/src/modules/_networking/application_gateway/outputs.tf b/src/modules/_networking/application_gateway/outputs.tf new file mode 100644 index 00000000..be140896 --- /dev/null +++ b/src/modules/_networking/application_gateway/outputs.tf @@ -0,0 +1,7 @@ +output "name" { + value = azurerm_application_gateway.main.name +} + +output "id" { + value = azurerm_application_gateway.main.id +} diff --git a/src/modules/_networking/application_gateway/variables.tf b/src/modules/_networking/application_gateway/variables.tf new file mode 100644 index 00000000..cc2a100b --- /dev/null +++ b/src/modules/_networking/application_gateway/variables.tf @@ -0,0 +1,19 @@ +variable "settings" { + type = any +} + +variable "resources" { + type = any + description = "Resources provided by other modules" + default = {} +} + +variable "global_settings" { + type = any + description = "Global settings shared across modules" +} + +variable "client_config" { + type = any + description = "Client config object (landingzone key, etc.)" +} diff --git a/src/modules/azuread_service_principal/_locals.tf b/src/modules/azuread_service_principal/_locals.tf index 697ce08a..cc0c1123 100644 --- a/src/modules/azuread_service_principal/_locals.tf +++ b/src/modules/azuread_service_principal/_locals.tf @@ -10,4 +10,15 @@ locals { var.global_settings.tags, try(var.settings.tags, {}) ) + + + key_vault_id = var.resources[ + try(var.settings.keyvault_lz_key, var.client_config.landingzone_key) + ].key_vaults[var.settings.keyvault_ref].id + + keyvault_secret_name = try(var.settings.keyvault_secret_name, "client-secret") + + + + } diff --git a/src/modules/azuread_service_principal/azuread_service_principal_password.tf b/src/modules/azuread_service_principal/azuread_service_principal_password.tf index 959c1cff..dd965053 100644 --- a/src/modules/azuread_service_principal/azuread_service_principal_password.tf +++ b/src/modules/azuread_service_principal/azuread_service_principal_password.tf @@ -1,10 +1,7 @@ -# resource "time_rotating" "main" { -# rotation_days = 7 -# } - resource "azuread_service_principal_password" "main" { service_principal_id = azuread_service_principal.main.id - # rotate_when_changed = { - # rotation = time_rotating.main.id - # } + + display_name = var.settings.password_display_name + start_date = try(var.settings.password_start_date, null) + end_date = try(var.settings.password_end_date, null) } diff --git a/src/modules/azuread_service_principal/keyvault_client_secret.tf b/src/modules/azuread_service_principal/keyvault_client_secret.tf new file mode 100644 index 00000000..20da4272 --- /dev/null +++ b/src/modules/azuread_service_principal/keyvault_client_secret.tf @@ -0,0 +1,9 @@ +resource "azurerm_key_vault_secret" "client_secret" { + count = try(var.settings.use_keyvault, false) ? 1 : 0 + + name = local.keyvault_secret_name + value = azuread_service_principal_password.main.value + key_vault_id = local.key_vault_id + + tags = try(local.tags, null) +} diff --git a/src/modules/container_registry/container_registry.tf b/src/modules/container_registry/container_registry.tf index 5fdc4d13..44fe908f 100644 --- a/src/modules/container_registry/container_registry.tf +++ b/src/modules/container_registry/container_registry.tf @@ -9,24 +9,26 @@ resource "azurerm_container_registry" "main" { admin_enabled = try(var.settings.admin_enabled, false) dynamic "georeplications" { - for_each = can(var.settings.georeplications) ? [1] : [] + for_each = try(length(var.settings.georeplications) > 0 ? [1] : [], []) content { - location = try(georeplications.value.location, null) + location = try(georeplications.value.location, null) zone_redundancy_enabled = try(georeplications.value.zone_redundancy_enabled, false) tags = try(georeplications.value.tags, null) } } + dynamic "identity" { - for_each = can(var.settings.identity) ? [1] : [] + for_each = try(length(var.settings.identity) > 0 ? [1] : [], []) content { type = var.settings.identity.type identity_ids = try(local.identity_ids, null) } } + dynamic "encryption" { - for_each = can(var.settings.encryption) ? [1] : [] + for_each = try(length(var.settings.encryption) > 0 ? [1] : [], []) content { key_vault_key_id = try( @@ -43,6 +45,22 @@ resource "azurerm_container_registry" "main" { null ) } + } + + dynamic "network_rule_set" { + for_each = try(length(var.settings.network_rule_set) > 0 ? [1] : [], []) + content { + default_action = try(var.settings.network_rule_set[0].default_action, "Allow") + + dynamic "ip_rule" { + for_each = try(var.settings.network_rule_set[0].ip_rule, []) + + content { + action = try(ip_rule.value.action, "Allow") + ip_range = ip_rule.value.ip_range + } + } + } } } diff --git a/src/modules/monitoring/diagnostic_setting/_locals.tf b/src/modules/monitoring/diagnostic_setting/_locals.tf new file mode 100644 index 00000000..eac36619 --- /dev/null +++ b/src/modules/monitoring/diagnostic_setting/_locals.tf @@ -0,0 +1,4 @@ +locals { + use_storage = try(var.settings, null) != null + use_law = try(var.settings, null) != null +} diff --git a/src/modules/monitoring/diagnostic_setting/main.tf b/src/modules/monitoring/diagnostic_setting/main.tf new file mode 100644 index 00000000..1277d0a9 --- /dev/null +++ b/src/modules/monitoring/diagnostic_setting/main.tf @@ -0,0 +1,40 @@ +resource "azurerm_monitor_diagnostic_setting" "main" { + + + name = var.settings.name + target_resource_id = var.resources[ + try(var.settings.resource_lz_key, var.client_config.landingzone_key) + ][var.settings.resource_type][var.settings.resource_ref].id + + dynamic "enabled_log" { + for_each = try(var.settings.enabled_log, {}) + content { + category = each.value.category + + } + } + + dynamic "enabled_metric" { + for_each = try(var.settings.enabled_metric, {}) + content { + category = each.value.category + + } + } + + log_analytics_workspace_id = ( + try(var.settings.log_analytics_workspace_ref, null) != null ? + var.resources[ + try(var.settings.log_analytics_lz_key, var.client_config.landingzone_key) + ].log_analytics_workspaces[var.settings.log_analytics_workspace_ref].id : + null + ) + + storage_account_id = ( + try(var.settings.storage_account_ref, null) != null ? + var.resources[ + try(var.settings.storage_account_lz_key, var.client_config.landingzone_key) + ].storage_accounts[var.settings.storage_account_ref].id : + null + ) +} diff --git a/src/modules/monitoring/diagnostic_setting/outputs.tf b/src/modules/monitoring/diagnostic_setting/outputs.tf new file mode 100644 index 00000000..73e8fff0 --- /dev/null +++ b/src/modules/monitoring/diagnostic_setting/outputs.tf @@ -0,0 +1,3 @@ +output "id" { + value = azurerm_monitor_diagnostic_setting.main.id +} \ No newline at end of file diff --git a/src/modules/monitoring/diagnostic_setting/variables.tf b/src/modules/monitoring/diagnostic_setting/variables.tf new file mode 100644 index 00000000..080eca09 --- /dev/null +++ b/src/modules/monitoring/diagnostic_setting/variables.tf @@ -0,0 +1,23 @@ +variable "resources" { + description = "CAF resources map from root module" + type = any +} + +variable "client_config" { + description = "Client config including landingzone key" + type = object({ + landingzone_key = string + }) +} + +variable "global_settings" { + description = "Global settings passed from root" + type = object({ + tags = map(string) + inherit_resource_group_tags = bool + }) +} +variable "settings" { + default = {} +} + diff --git a/src/modules/role_assignments/built_in_role/data.tf b/src/modules/role_assignments/built_in_role/data.tf new file mode 100644 index 00000000..76a4f0fe --- /dev/null +++ b/src/modules/role_assignments/built_in_role/data.tf @@ -0,0 +1,21 @@ +data "azuread_group" "by_name" { + for_each = local.group_names + display_name = each.key +} + +data "azuread_user" "by_name" { + for_each = local.user_names + user_principal_name = each.key +} + +locals { + group_names = toset([ + for ra in local.computed_role_assignments : ra.principal + if ra.principal_type == "group_names" + ]) + + user_names = toset([ + for ra in local.computed_role_assignments : ra.principal + if ra.principal_type == "user_names" + ]) +} diff --git a/src/modules/role_assignments/built_in_role/locals.tf b/src/modules/role_assignments/built_in_role/locals.tf new file mode 100644 index 00000000..23b452f4 --- /dev/null +++ b/src/modules/role_assignments/built_in_role/locals.tf @@ -0,0 +1,22 @@ +locals { + computed_role_assignments = tomap({ + for item in flatten([ + for role_definition_name, resources in var.settings : [ + for resource_key, resource_details in resources : [ + for principal_type, principals in try(resource_details, {}) : [ + for principal in ( + can(principals) && length(principals) > 0 ? principals : [] + ) : { + role_definition_name = role_definition_name + resource_key = resource_key + resource_type = var.resource_type + principal_type = principal_type + principal = principal + } + ] + ] + ] + ]) : + "${item.role_definition_name}-${item.resource_key}-${item.principal_type}-${item.principal}" => item + }) +} diff --git a/src/modules/role_assignments/built_in_role/main.tf b/src/modules/role_assignments/built_in_role/main.tf index e30c9685..3bb3493f 100644 --- a/src/modules/role_assignments/built_in_role/main.tf +++ b/src/modules/role_assignments/built_in_role/main.tf @@ -1,26 +1,5 @@ resource "azurerm_role_assignment" "main" { - for_each = tomap({ - for item in flatten([ - for role_definition_name, resources in var.settings : [ - for resource_key, resource_details in resources : [ - for principal_type, principals in try(resource_details, {}) : [ - for principal in( - # Handle cases where the principal is a list (like object_ids) or a single value - can(principals) && length(principals) > 0 ? principals : [] - ) : { - role_definition_name = role_definition_name - resource_key = resource_key - resource_type = var.resource_type - principal_type = principal_type - principal = principal - } - ] - ] - ] - ]) : - # Ensure unique keys for each role assignment - "${item.role_definition_name}-${item.resource_key}-${item.principal_type}-${item.principal}" => item - }) + for_each = local.computed_role_assignments scope = try( var.resources[each.value.resource_type][each.value.resource_key].id, @@ -28,12 +7,13 @@ resource "azurerm_role_assignment" "main" { ) principal_id = try( - # If principal is directly an ID (like object_ids), use it. Otherwise, resolve via var.resources. - each.value.principal_type == "object_ids" - ? each.value.principal - : var.resources[each.value.principal_type][each.value.principal].principal_id, - null - ) + each.value.principal_type == "object_ids" ? each.value.principal : + each.value.principal_type == "group_names" ? data.azuread_group.by_name[each.value.principal].object_id : + each.value.principal_type == "user_names" ? data.azuread_user.by_name[each.value.principal].object_id : + var.resources[each.value.principal_type][each.value.principal].principal_id, + null +) + role_definition_name = each.value.role_definition_name } diff --git a/src/modules/role_assignments/custom_role/main.tf b/src/modules/role_assignments/custom_role/main.tf index 5bd99073..e59710f2 100644 --- a/src/modules/role_assignments/custom_role/main.tf +++ b/src/modules/role_assignments/custom_role/main.tf @@ -27,6 +27,7 @@ resource "azurerm_role_assignment" "main" { null ) + principal_id = try( # If principal is directly an ID (like object_ids), use it. Otherwise, resolve via var.resources. each.value.principal_type == "object_ids" @@ -36,4 +37,4 @@ resource "azurerm_role_assignment" "main" { ) role_definition_id = try(var.resources.role_definitions[each.value.role_definition_name].role_definition_resource_id, null) -} +} \ No newline at end of file diff --git a/src/modules/role_assignments/subscription/main.tf b/src/modules/role_assignments/subscription/main.tf index e7714b3a..0d9d543f 100644 --- a/src/modules/role_assignments/subscription/main.tf +++ b/src/modules/role_assignments/subscription/main.tf @@ -35,4 +35,4 @@ resource "azurerm_role_assignment" "assignments" { principal_id = data.azuread_user.users[each.value.user].object_id role_definition_name = each.value.role scope = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" -} +} \ No newline at end of file diff --git a/src/networking.tf b/src/networking.tf index 816b698c..439d0274 100644 --- a/src/networking.tf +++ b/src/networking.tf @@ -73,6 +73,31 @@ module "virtual_network_gateways" { landingzone_key = var.landingzone.key } } +module "application_gateways" { + source = "./modules/_networking/application_gateway" + for_each = var.application_gateways + + settings = each.value + global_settings = local.global_settings + + resources = merge( + { + (var.landingzone.key) = { + resource_groups = module.resource_groups + virtual_networks = module.virtual_networks + public_ips = module.public_ips + } + }, + { + for k, v in module.remote_states : k => v.outputs + } + ) + + client_config = { + landingzone_key = var.landingzone.key + } +} + module "public_ips" { source = "./modules/_networking/public_ip"