From 06636a80ea30ed3140bc4730ca8b9d64c9b1a70f Mon Sep 17 00:00:00 2001 From: Michele Zanotti Date: Wed, 7 Aug 2024 12:27:41 +0200 Subject: [PATCH] fix postgres networking -> never expose to the internet --- README.md | 108 ++++++++++++------------ main.tf | 75 +++++++++++++--- tests/test_values_validation.tftest.hcl | 4 - variables.tf | 48 ++++++----- 4 files changed, 145 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 4600a8c..2d1ad6f 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,18 @@ Available on [Terraform Registry](https://registry.terraform.io/modules/nebuly-a | [postgres\_server\_lock](#input\_postgres\_server\_lock) | Optionally lock the PostgreSQL server to prevent deletion. |
object({
enabled = optional(bool, false)
notes = optional(string, "Cannot be deleted.")
name = optional(string, "terraform-lock")
})
|
{
"enabled": true
}
| no | | [postgres\_server\_maintenance\_window](#input\_postgres\_server\_maintenance\_window) | The window for performing automatic maintenance of the PostgreSQL Server. Default is Sunday at 00:00 of the timezone of the server location. |
object({
day_of_week : number
start_hour : number
start_minute : number
})
|
{
"day_of_week": 0,
"start_hour": 0,
"start_minute": 0
}
| no | | [postgres\_server\_max\_storage\_mb](#input\_postgres\_server\_max\_storage\_mb) | The max storage allowed for the PostgreSQL Flexible Server. Possible values are 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4193280, 4194304, 8388608, 16777216 and 33553408. | `number` | `262144` | no | -| [postgres\_server\_networking](#input\_postgres\_server\_networking) | Server networking configuration.

If allowed\_ip\_ranges is not empty, then the server is accessible from
the Internet through the configured firewall rules.

If delegated\_subnet\_id or private\_dns\_zone\_id are provided, then the Server
is accessible only from the specified virutal network. |
object({
allowed_ip_ranges : optional(list(object({
name : string
start_ip_address : string
end_ip_address : string
})), [])
delegated_subnet_id : optional(string, null)
private_dns_zone_id : optional(string, null)
public_network_access_enabled : optional(bool, false)
})
| `{}` | no | | [postgres\_server\_optional\_configurations](#input\_postgres\_server\_optional\_configurations) | Optional Flexible PostgreSQL configurations. Defaults to recommended configurations. | `map(string)` |
{
"intelligent_tuning": "on",
"intelligent_tuning.metric_targets": "ALL",
"metrics.autovacuum_diagnostics": "on",
"metrics.collector_database_activity": "on",
"pg_qs.query_capture_mode": "ALL",
"pg_qs.retention_period_in_days": "7",
"pg_qs.store_query_plans": "on",
"pgaudit.log": "WRITE",
"pgms_wait_sampling.query_capture_mode": "ALL",
"track_io_timing": "on"
}
| no | | [postgres\_server\_point\_in\_time\_backup](#input\_postgres\_server\_point\_in\_time\_backup) | The backup settings of the PostgreSQL Server. |
object({
geo_redundant : optional(bool, true)
retention_days : optional(number, 30)
})
|
{
"geo_redundant": true,
"retention_days": 30
}
| no | | [postgres\_server\_sku](#input\_postgres\_server\_sku) | The SKU of the PostgreSQL Server, including the Tier and the Name. Examples: B\_Standard\_B1ms, GP\_Standard\_D2s\_v3, MO\_Standard\_E4s\_v3 |
object({
tier : string
name : string
})
|
{
"name": "Standard_D4ds_v5",
"tier": "GP"
}
| no | | [postgres\_version](#input\_postgres\_version) | The PostgreSQL version to use. | `string` | `"16"` | no | -| [private\_dns\_zones](#input\_private\_dns\_zones) | Private DNS zones to use for Private Endpoint connections. If not provided, a new DNS Zone
is created and linked to the respective subnet. |
object({
file = optional(object({
name : string
id : string
}), null)
blob = optional(object({
name : string
id : string
}), null)
dfs = optional(object({
name : string
id : string
}), null)
})
| `{}` | no | +| [private\_dns\_zones](#input\_private\_dns\_zones) | Private DNS zones to use for Private Endpoint connections. If not provided, a new DNS Zone
is created and linked to the respective subnet. |
object({
file = optional(object({
name : string
id : string
}), null)
blob = optional(object({
name : string
id : string
}), null)
dfs = optional(object({
name : string
id : string
}), null)
flexible_postgres = optional(object({
name : string
id : string
}), null)
})
| `{}` | no | | [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group where to provision the resources. | `string` | n/a | yes | | [resource\_prefix](#input\_resource\_prefix) | The prefix that is used for generating resource names. | `string` | n/a | yes | | [subnet\_address\_space\_aks\_nodes](#input\_subnet\_address\_space\_aks\_nodes) | Address space of the new subnet in which to create the nodes of the AKS cluster.
If `subnet_name_aks_nodes` is provided, the existing subnet is used and this variable is ignored. | `list(string)` |
[
"10.0.0.0/22"
]
| no | +| [subnet\_address\_space\_flexible\_postgres](#input\_subnet\_address\_space\_flexible\_postgres) | Address space of the new subnet delgated to Flexible PostgreSQL Server service.
If `subnet_name_flexible_postgres` is provided, the existing subnet is used and this variable is ignored. | `list(string)` |
[
"10.0.12.0/26"
]
| no | | [subnet\_address\_space\_private\_endpoints](#input\_subnet\_address\_space\_private\_endpoints) | Address space of the new subnet in which to create private endpoints.
If `subnet_name_private_endpoints` is provided, the existing subnet is used and this variable is ignored. | `list(string)` |
[
"10.0.8.0/26"
]
| no | | [subnet\_name\_aks\_nodes](#input\_subnet\_name\_aks\_nodes) | Optional name of the subnet to be used for provisioning AKS nodes.
If not provided, a new subnet is created. | `string` | `null` | no | +| [subnet\_name\_flexible\_postgres](#input\_subnet\_name\_flexible\_postgres) | Optional name of the subnet delegated to Flexible PostgreSQL Server service.
If not provided, a new subnet is created. | `string` | `null` | no | | [subnet\_name\_private\_endpoints](#input\_subnet\_name\_private\_endpoints) | Optional name of the subnet to which attach the Private Endpoints.
If not provided, a new subnet is created. | `string` | `null` | no | | [tags](#input\_tags) | Common tags that are applied to all resources. | `map(string)` | `{}` | no | | [virtual\_network\_address\_space](#input\_virtual\_network\_address\_space) | Address space of the new virtual network in which to create resources.
If `virtual_network_name` is provided, the existing virtual network is used and this variable is ignored. | `list(string)` |
[
"10.0.0.0/16"
]
| no | @@ -83,53 +84,56 @@ Available on [Terraform Registry](https://registry.terraform.io/modules/nebuly-a ## Resources -- resource.azuread_application.main (/terraform-docs/main.tf#254) -- resource.azuread_service_principal.main (/terraform-docs/main.tf#260) -- resource.azuread_service_principal_password.main (/terraform-docs/main.tf#265) -- resource.azurerm_cognitive_account.main (/terraform-docs/main.tf#457) -- resource.azurerm_cognitive_deployment.gpt_4_turbo (/terraform-docs/main.tf#476) -- resource.azurerm_cognitive_deployment.gpt_4o_mini (/terraform-docs/main.tf#491) -- resource.azurerm_key_vault.main (/terraform-docs/main.tf#188) -- resource.azurerm_key_vault_secret.azure_openai_api_key (/terraform-docs/main.tf#506) -- resource.azurerm_key_vault_secret.azuread_application_client_id (/terraform-docs/main.tf#269) -- resource.azurerm_key_vault_secret.azuread_application_client_secret (/terraform-docs/main.tf#278) -- resource.azurerm_key_vault_secret.jwt_signing_key (/terraform-docs/main.tf#741) -- resource.azurerm_key_vault_secret.postgres_password (/terraform-docs/main.tf#440) -- resource.azurerm_key_vault_secret.postgres_user (/terraform-docs/main.tf#431) -- resource.azurerm_kubernetes_cluster_node_pool.linux_pools (/terraform-docs/main.tf#698) -- resource.azurerm_management_lock.postgres_server (/terraform-docs/main.tf#374) -- resource.azurerm_monitor_metric_alert.postgres_server_alerts (/terraform-docs/main.tf#382) -- resource.azurerm_postgresql_flexible_server.main (/terraform-docs/main.tf#296) -- resource.azurerm_postgresql_flexible_server_configuration.mandatory_configurations (/terraform-docs/main.tf#347) -- resource.azurerm_postgresql_flexible_server_configuration.optional_configurations (/terraform-docs/main.tf#340) -- resource.azurerm_postgresql_flexible_server_database.analytics (/terraform-docs/main.tf#368) -- resource.azurerm_postgresql_flexible_server_database.auth (/terraform-docs/main.tf#362) -- resource.azurerm_postgresql_flexible_server_firewall_rule.main (/terraform-docs/main.tf#354) -- resource.azurerm_private_dns_zone.blob (/terraform-docs/main.tf#149) -- resource.azurerm_private_dns_zone.dfs (/terraform-docs/main.tf#167) -- resource.azurerm_private_dns_zone.file (/terraform-docs/main.tf#131) -- resource.azurerm_private_dns_zone_virtual_network_link.blob (/terraform-docs/main.tf#155) -- resource.azurerm_private_dns_zone_virtual_network_link.dfs (/terraform-docs/main.tf#173) -- resource.azurerm_private_dns_zone_virtual_network_link.file (/terraform-docs/main.tf#137) -- resource.azurerm_private_endpoint.blob (/terraform-docs/main.tf#545) -- resource.azurerm_private_endpoint.dfs (/terraform-docs/main.tf#585) -- resource.azurerm_private_endpoint.file (/terraform-docs/main.tf#565) -- resource.azurerm_private_endpoint.key_vault (/terraform-docs/main.tf#214) -- resource.azurerm_role_assignment.aks_network_contributor (/terraform-docs/main.tf#693) -- resource.azurerm_role_assignment.key_vault_secret_officer__current (/terraform-docs/main.tf#244) -- resource.azurerm_role_assignment.key_vault_secret_user__aks (/terraform-docs/main.tf#239) -- resource.azurerm_role_assignment.storage_container_models__data_contributor (/terraform-docs/main.tf#540) -- resource.azurerm_storage_account.main (/terraform-docs/main.tf#522) -- resource.azurerm_storage_container.models (/terraform-docs/main.tf#536) -- resource.azurerm_subnet.aks_nodes (/terraform-docs/main.tf#111) -- resource.azurerm_subnet.private_endpints (/terraform-docs/main.tf#119) -- resource.azurerm_virtual_network.main (/terraform-docs/main.tf#103) -- resource.random_password.postgres_server_admin_password (/terraform-docs/main.tf#291) -- resource.time_sleep.wait_aks_creation (/terraform-docs/main.tf#680) -- resource.tls_private_key.aks (/terraform-docs/main.tf#609) -- resource.tls_private_key.jwt_signing_key (/terraform-docs/main.tf#737) -- data source.azurerm_client_config.current (/terraform-docs/main.tf#71) -- data source.azurerm_resource_group.main (/terraform-docs/main.tf#68) -- data source.azurerm_subnet.aks_nodes (/terraform-docs/main.tf#79) -- data source.azurerm_subnet.private_endpoints (/terraform-docs/main.tf#93) -- data source.azurerm_virtual_network.main (/terraform-docs/main.tf#73) +- resource.azuread_application.main (/terraform-docs/main.tf#315) +- resource.azuread_service_principal.main (/terraform-docs/main.tf#321) +- resource.azuread_service_principal_password.main (/terraform-docs/main.tf#326) +- resource.azurerm_cognitive_account.main (/terraform-docs/main.tf#510) +- resource.azurerm_cognitive_deployment.gpt_4_turbo (/terraform-docs/main.tf#529) +- resource.azurerm_cognitive_deployment.gpt_4o_mini (/terraform-docs/main.tf#544) +- resource.azurerm_key_vault.main (/terraform-docs/main.tf#248) +- resource.azurerm_key_vault_secret.azure_openai_api_key (/terraform-docs/main.tf#559) +- resource.azurerm_key_vault_secret.azuread_application_client_id (/terraform-docs/main.tf#330) +- resource.azurerm_key_vault_secret.azuread_application_client_secret (/terraform-docs/main.tf#339) +- resource.azurerm_key_vault_secret.jwt_signing_key (/terraform-docs/main.tf#794) +- resource.azurerm_key_vault_secret.postgres_password (/terraform-docs/main.tf#493) +- resource.azurerm_key_vault_secret.postgres_user (/terraform-docs/main.tf#484) +- resource.azurerm_kubernetes_cluster_node_pool.linux_pools (/terraform-docs/main.tf#751) +- resource.azurerm_management_lock.postgres_server (/terraform-docs/main.tf#427) +- resource.azurerm_monitor_metric_alert.postgres_server_alerts (/terraform-docs/main.tf#435) +- resource.azurerm_postgresql_flexible_server.main (/terraform-docs/main.tf#357) +- resource.azurerm_postgresql_flexible_server_configuration.mandatory_configurations (/terraform-docs/main.tf#408) +- resource.azurerm_postgresql_flexible_server_configuration.optional_configurations (/terraform-docs/main.tf#401) +- resource.azurerm_postgresql_flexible_server_database.analytics (/terraform-docs/main.tf#421) +- resource.azurerm_postgresql_flexible_server_database.auth (/terraform-docs/main.tf#415) +- resource.azurerm_private_dns_zone.blob (/terraform-docs/main.tf#191) +- resource.azurerm_private_dns_zone.dfs (/terraform-docs/main.tf#209) +- resource.azurerm_private_dns_zone.file (/terraform-docs/main.tf#173) +- resource.azurerm_private_dns_zone.flexible_postgres (/terraform-docs/main.tf#227) +- resource.azurerm_private_dns_zone_virtual_network_link.blob (/terraform-docs/main.tf#197) +- resource.azurerm_private_dns_zone_virtual_network_link.dfs (/terraform-docs/main.tf#215) +- resource.azurerm_private_dns_zone_virtual_network_link.file (/terraform-docs/main.tf#179) +- resource.azurerm_private_dns_zone_virtual_network_link.flexible_postgres (/terraform-docs/main.tf#233) +- resource.azurerm_private_endpoint.blob (/terraform-docs/main.tf#598) +- resource.azurerm_private_endpoint.dfs (/terraform-docs/main.tf#638) +- resource.azurerm_private_endpoint.file (/terraform-docs/main.tf#618) +- resource.azurerm_private_endpoint.key_vault (/terraform-docs/main.tf#274) +- resource.azurerm_role_assignment.aks_network_contributor (/terraform-docs/main.tf#746) +- resource.azurerm_role_assignment.key_vault_secret_officer__current (/terraform-docs/main.tf#305) +- resource.azurerm_role_assignment.key_vault_secret_user__aks (/terraform-docs/main.tf#300) +- resource.azurerm_role_assignment.storage_container_models__data_contributor (/terraform-docs/main.tf#593) +- resource.azurerm_storage_account.main (/terraform-docs/main.tf#575) +- resource.azurerm_storage_container.models (/terraform-docs/main.tf#589) +- resource.azurerm_subnet.aks_nodes (/terraform-docs/main.tf#131) +- resource.azurerm_subnet.flexible_postgres (/terraform-docs/main.tf#147) +- resource.azurerm_subnet.private_endpints (/terraform-docs/main.tf#139) +- resource.azurerm_virtual_network.main (/terraform-docs/main.tf#123) +- resource.random_password.postgres_server_admin_password (/terraform-docs/main.tf#352) +- resource.time_sleep.wait_aks_creation (/terraform-docs/main.tf#733) +- resource.tls_private_key.aks (/terraform-docs/main.tf#662) +- resource.tls_private_key.jwt_signing_key (/terraform-docs/main.tf#790) +- data source.azurerm_client_config.current (/terraform-docs/main.tf#77) +- data source.azurerm_resource_group.main (/terraform-docs/main.tf#74) +- data source.azurerm_subnet.aks_nodes (/terraform-docs/main.tf#85) +- data source.azurerm_subnet.flexible_postgres (/terraform-docs/main.tf#106) +- data source.azurerm_subnet.private_endpoints (/terraform-docs/main.tf#99) +- data source.azurerm_virtual_network.main (/terraform-docs/main.tf#79) diff --git a/main.tf b/main.tf index bb21cea..1af1d13 100644 --- a/main.tf +++ b/main.tf @@ -43,6 +43,7 @@ locals { use_existing_virtual_network = var.virtual_network_name != null use_existing_aks_nodes_subnet = var.subnet_name_aks_nodes != null use_existing_private_endpoints_subnet = var.subnet_name_private_endpoints != null + use_existing_flexible_postgres_subnet = var.subnet_name_flexible_postgres != null virtual_network = ( local.use_existing_virtual_network ? @@ -59,6 +60,11 @@ locals { data.azurerm_subnet.private_endpoints[0] : azurerm_subnet.private_endpints[0] ) + flexible_postgres_subnet = ( + local.use_existing_flexible_postgres_subnet ? + data.azurerm_subnet.flexible_postgres[0] : + azurerm_subnet.flexible_postgres[0] + ) } @@ -97,6 +103,20 @@ data "azurerm_subnet" "private_endpoints" { virtual_network_name = var.virtual_network_name name = var.subnet_name_private_endpoints } +data "azurerm_subnet" "flexible_postgres" { + count = local.use_existing_flexible_postgres_subnet ? 1 : 0 + + resource_group_name = data.azurerm_resource_group.main.name + virtual_network_name = data.azurerm_virtual_network.main[0].name + name = var.subnet_name_flexible_postgres + + lifecycle { + precondition { + condition = length(data.azurerm_virtual_network.main) > 0 + error_message = "`virtual_network_name` must be provided and must point to a valid virtual network." + } + } +} # ------ Networking: Networks and Subnets ------ # @@ -124,6 +144,28 @@ resource "azurerm_subnet" "private_endpints" { resource_group_name = data.azurerm_resource_group.main.name address_prefixes = var.subnet_address_space_private_endpoints } +resource "azurerm_subnet" "flexible_postgres" { + count = local.use_existing_flexible_postgres_subnet ? 0 : 1 + + name = "flexible-postgres" + virtual_network_name = local.virtual_network.name + resource_group_name = data.azurerm_resource_group.main.name + address_prefixes = var.subnet_address_space_flexible_postgres + + service_endpoints = [ + "Microsoft.Storage", + ] + + delegation { + name = "delegation" + service_delegation { + actions = [ + "Microsoft.Network/virtualNetworks/subnets/join/action", + ] + name = "Microsoft.DBforPostgreSQL/flexibleServers" + } + } +} @@ -182,6 +224,24 @@ resource "azurerm_private_dns_zone_virtual_network_link" "dfs" { virtual_network_id = local.virtual_network.id private_dns_zone_name = azurerm_private_dns_zone.dfs[0].name } +resource "azurerm_private_dns_zone" "flexible_postgres" { + count = var.private_dns_zones.flexible_postgres == null ? 1 : 0 + + name = "${var.resource_prefix}.nebuly.postgres.database.azure.com" + resource_group_name = data.azurerm_resource_group.main.name +} +resource "azurerm_private_dns_zone_virtual_network_link" "flexible_postgres" { + count = var.private_dns_zones.flexible_postgres == null ? 1 : 0 + + name = format( + "%s-flexible-postgres-%s", + var.resource_prefix, + local.virtual_network.name, + ) + resource_group_name = data.azurerm_resource_group.main.name + virtual_network_id = local.virtual_network.id + private_dns_zone_name = azurerm_private_dns_zone.flexible_postgres[0].name +} # ------ Key Vault ------ # @@ -229,6 +289,7 @@ resource "azurerm_private_endpoint" "key_vault" { private_dns_zone_group { name = "privatelink-vaultcore-azure-net" + private_dns_zone_ids = [ var.key_vault_private_dns_zone.id ] @@ -309,10 +370,10 @@ resource "azurerm_postgresql_flexible_server" "main" { backup_retention_days = var.postgres_server_point_in_time_backup.retention_days geo_redundant_backup_enabled = var.postgres_server_point_in_time_backup.geo_redundant - public_network_access_enabled = var.postgres_server_networking.public_network_access_enabled + public_network_access_enabled = false - delegated_subnet_id = var.postgres_server_networking.delegated_subnet_id - private_dns_zone_id = var.postgres_server_networking.private_dns_zone_id + delegated_subnet_id = local.flexible_postgres_subnet.id + private_dns_zone_id = length(azurerm_private_dns_zone.flexible_postgres) > 0 ? azurerm_private_dns_zone.flexible_postgres[0].id : var.private_dns_zones.flexible_postgres.id dynamic "high_availability" { for_each = var.postgres_server_high_availability.enabled ? { "" : var.postgres_server_high_availability } : {} @@ -351,14 +412,6 @@ resource "azurerm_postgresql_flexible_server_configuration" "mandatory_configura server_id = azurerm_postgresql_flexible_server.main.id value = each.value } -resource "azurerm_postgresql_flexible_server_firewall_rule" "main" { - for_each = { for o in var.postgres_server_networking.allowed_ip_ranges : o.name => o } - - name = each.key - server_id = azurerm_postgresql_flexible_server.main.id - start_ip_address = each.value.start_ip_address - end_ip_address = each.value.end_ip_address -} resource "azurerm_postgresql_flexible_server_database" "auth" { name = "auth" server_id = azurerm_postgresql_flexible_server.main.id diff --git a/tests/test_values_validation.tftest.hcl b/tests/test_values_validation.tftest.hcl index 8681065..4fe1f21 100644 --- a/tests/test_values_validation.tftest.hcl +++ b/tests/test_values_validation.tftest.hcl @@ -7,10 +7,6 @@ variables { location = "EastUS" platform_domain = "intest.nebuly.ai" - - # ------ PostgreSQL Database ------ # - postgres_server_networking = {} - # ------ Key Vault ------ # key_vault_public_network_access_enabled = false diff --git a/variables.tf b/variables.tf index 75db104..e49055e 100644 --- a/variables.tf +++ b/variables.tf @@ -74,29 +74,6 @@ variable "postgres_server_maintenance_window" { } description = "The window for performing automatic maintenance of the PostgreSQL Server. Default is Sunday at 00:00 of the timezone of the server location." } -variable "postgres_server_networking" { - description = <