diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..a424c32c6f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# git +.git +.gitignore + +# node +node_modules +.env +dist +.pnpm-store +vite.config.*.timestamp* +vitest.config.*.timestamp* + +# Go +terraform-provider-azure +providers/azure/terraform-provider-azure + +# Terraform +**/.terraform/* +*.tfstate +*.tfstate.* +override.tf +override.tf.json +*_override.tf +*_override.tf.json +**/modules/**/.terraform.lock.hcl +**/_modules/**/.terraform.lock.hcl + +# MacOS +*.DS_Store + +# NX +.nx/cache +.nx/workspace-data +.github/instructions/nx.instructions.md diff --git a/.github/workflows/_release-docker-e2e-appconfiguration.yaml b/.github/workflows/_release-docker-e2e-appconfiguration.yaml new file mode 100644 index 0000000000..62edb5dae6 --- /dev/null +++ b/.github/workflows/_release-docker-e2e-appconfiguration.yaml @@ -0,0 +1,45 @@ +# Build and publish the application Docker image for the E2E App Configuration - including all scenarios. + +name: Publish E2E App Configuration - All Scenarios +on: + workflow_dispatch: + push: + branches: + - main + - CES-1469-aggiungere-esempi-del-modulo-da-usare-per-i-test-e-2-e + # paths: + # - "infra/modules/azure_app_configuration/tests/apps/all_scenarios/**" + +env: + MODULE_NAME: "azure_app_configuration" + +jobs: + publish: + name: Build and Push Docker Image + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write + packages: write + env: + IMAGE_NAME: "pagopa/e2e-appconfiguration-all-scenarios" + IMAGE_TAG: "latest" + + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Docker Build and Push + id: docker_build + uses: pagopa/dx/actions/docker-build-push@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + dockerfile_path: infra/modules/${{ env.MODULE_NAME }}/tests/apps/all_scenarios/Dockerfile + dockerfile_context: infra/modules/${{ env.MODULE_NAME }}/tests/apps/all_scenarios/src + docker_image_name: ${{ env.IMAGE_NAME }} + docker_image_description: "Web app which exposes endpoints to access App Configuration. Used for E2E tests of the Azure App Configuration Terraform module." + docker_image_authors: "PagoPA" + build_platforms: "linux/amd64,linux/arm64" + push_to_registry: true diff --git a/go.work b/go.work index b088f0fb64..2e1edcb470 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,8 @@ -go 1.25.3 +go 1.25.4 use ( + ./infra/modules/azure_app_configuration/tests + ./infra/modules/azure_app_configuration/tests/apps/all_scenarios/src ./infra/modules/azure_cosmos_account/tests ./providers/aws ./providers/aws/tools diff --git a/go.work.sum b/go.work.sum index 092fbab918..26d74264a5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -18,12 +18,11 @@ github.com/Azure/azure-sdk-for-go v64.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.0.0 h1:NYYoOOPGOqUXw/bGIVd6OY/K8J23a18IAlAx1tOHWNo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.0.0/go.mod h1:LDN3sr8FJ36sY6ZmMes6Q2vHJ+5r1aFsE3wEo7VbXJg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= @@ -172,6 +171,7 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc= @@ -233,7 +233,6 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= @@ -282,7 +281,6 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/README.md b/infra/modules/azure_app_configuration/examples/keyvault_integration/README.md new file mode 100644 index 0000000000..63be0f3146 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/README.md @@ -0,0 +1,43 @@ +# keyvault_integration + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.13.0 | +| [azurerm](#requirement\_azurerm) | ~> 4.0 | +| [pagopa-dx](#requirement\_pagopa-dx) | ~> 0.8 | +| [random](#requirement\_random) | ~> 3.7 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [appcs\_with\_kv](#module\_appcs\_with\_kv) | pagopa-dx/azure-app-configuration/azurerm | ~> 0.0 | + +## Resources + +| Name | Type | +|------|------| +| [azurerm_key_vault.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource | +| [azurerm_private_endpoint.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint) | resource | +| [azurerm_resource_group.e2e_appcs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | +| [random_integer.appcs_instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | +| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | +| [azurerm_private_dns_zone.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/private_dns_zone) | data source | +| [azurerm_resource_group.network](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | +| [azurerm_subnet.pep](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | +| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) | data source | +| [azurerm_virtual_network.e2e](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [name](#output\_name) | n/a | + diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/data.tf b/infra/modules/azure_app_configuration/examples/keyvault_integration/data.tf new file mode 100644 index 0000000000..a603649866 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/data.tf @@ -0,0 +1,29 @@ +data "azurerm_client_config" "current" {} + +data "azurerm_subscription" "current" {} + +data "azurerm_private_dns_zone" "kv" { + name = "privatelink.vaultcore.azure.net" + resource_group_name = local.e2e_virtual_network.resource_group_name +} + +data "azurerm_virtual_network" "e2e" { + name = local.e2e_virtual_network.name + resource_group_name = local.e2e_virtual_network.resource_group_name +} + +data "azurerm_subnet" "pep" { + name = provider::pagopa-dx::resource_name(merge(local.naming_config, { + name = "pep", + resource_type = "subnet" + })) + virtual_network_name = data.azurerm_virtual_network.e2e.name + resource_group_name = data.azurerm_virtual_network.e2e.resource_group_name +} + +data "azurerm_resource_group" "network" { + name = provider::pagopa-dx::resource_name(merge(local.naming_config, { + name = "network" + resource_type = "resource_group" + })) +} diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/fixtures.tf b/infra/modules/azure_app_configuration/examples/keyvault_integration/fixtures.tf new file mode 100644 index 0000000000..0fd8e2f3c6 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/fixtures.tf @@ -0,0 +1,111 @@ +resource "azurerm_resource_group" "e2e_appcs" { + name = provider::pagopa-dx::resource_name(merge(local.naming_config, { + domain = "e2e" + name = "appcs", + resource_type = "resource_group" + })) + location = local.environment.location + + tags = local.tags +} + +resource "azurerm_key_vault" "kv" { + name = provider::dx::resource_name(merge(local.naming_config, { resource_type = "key_vault", domain = "e2e", instance_number = random_integer.appcs_instance.result })) + location = azurerm_resource_group.e2e_appcs.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + rbac_authorization_enabled = true + purge_protection_enabled = true + soft_delete_retention_days = 7 + public_network_access_enabled = false + network_acls { + bypass = "AzureServices" + default_action = "Deny" + } + tags = local.tags +} + +resource "azurerm_private_endpoint" "kv" { + name = provider::dx::resource_name(merge(local.naming_config, { resource_type = "key_vault_private_endpoint", domain = "e2e", instance_number = random_integer.appcs_instance.result })) + location = azurerm_resource_group.e2e_appcs.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + subnet_id = data.azurerm_subnet.pep.id + + private_service_connection { + name = provider::dx::resource_name(merge(local.naming_config, { resource_type = "key_vault_private_endpoint", domain = "e2e", instance_number = random_integer.appcs_instance.result })) + private_connection_resource_id = azurerm_key_vault.kv.id + is_manual_connection = false + subresource_names = ["vault"] + } + + private_dns_zone_group { + name = "private-dns-zone-group" + private_dns_zone_ids = [data.azurerm_private_dns_zone.kv.id] + } + + tags = local.tags +} + +resource "dx_available_subnet_cidr" "private_app" { + virtual_network_id = data.azurerm_virtual_network.e2e.id + prefix_length = 26 +} + +resource "azurerm_subnet" "private_app" { + name = provider::dx::resource_name(merge(local.naming_config, { + name = "appcs-private", + resource_type = "container_instance_subnet" + })) + resource_group_name = local.e2e_virtual_network.resource_group_name + virtual_network_name = local.e2e_virtual_network.name + address_prefixes = [dx_available_subnet_cidr.private_app.cidr_block] + + delegation { + name = "Microsoft.ContainerInstance/containerGroups" + + service_delegation { + name = "Microsoft.ContainerInstance/containerGroups" + actions = [ + "Microsoft.Network/virtualNetworks/subnets/action", + ] + } + } +} + +resource "azurerm_container_group" "private_app" { + name = provider::dx::resource_name( + merge(local.naming_config, { name = "appcs-private", resource_type = "container_instance" }) + ) + location = local.environment.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + + identity { type = "SystemAssigned" } + + os_type = "Linux" + + container { + name = "network-access" + image = local.docker_image + cpu = "0.5" + memory = "1.5" + ports { + port = 8080 + } + } + + ip_address_type = "Private" + + subnet_ids = [ + azurerm_subnet.private_app.id + ] + + diagnostics { + log_analytics { + workspace_id = data.azurerm_log_analytics_workspace.e2e.workspace_id + workspace_key = data.azurerm_log_analytics_workspace.e2e.primary_shared_key + } + } + + tags = local.tags +} diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/locals.tf b/infra/modules/azure_app_configuration/examples/keyvault_integration/locals.tf new file mode 100644 index 0000000000..d7481adcaa --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/locals.tf @@ -0,0 +1,38 @@ +locals { + tags = { + CostCenter = "TS000 - Tecnologia e Servizi" + CreatedBy = "Terraform" + Environment = "Dev" + BusinessUnit = "DevEx" + Source = "https://github.com/pagopa/dx/tests/azure_app_configuration" + ManagementTeam = "Developer Experience" + TestSuite = "e2e" + } + + environment = { + prefix = "dx" + env_short = "d" + location = "italynorth" + app_name = "e2e" + instance_number = "01" + } + + naming_config = { + prefix = local.environment.prefix, + environment = local.environment.env_short, + location = local.environment.location, + name = local.environment.app_name, + instance_number = tonumber(local.environment.instance_number), + } + + e2e_virtual_network = { + name = provider::pagopa-dx::resource_name(merge(local.naming_config, { + name = "e2e", + resource_type = "virtual_network" + })) + resource_group_name = provider::pagopa-dx::resource_name(merge(local.naming_config, { + name = "e2e", + resource_type = "resource_group" + })) + } +} diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/mut.tf b/infra/modules/azure_app_configuration/examples/keyvault_integration/mut.tf new file mode 100644 index 0000000000..63efb9b6e8 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/mut.tf @@ -0,0 +1,31 @@ +resource "random_integer" "appcs_instance" { + min = 1 + max = 99 +} + +module "appcs_with_kv" { + source = "pagopa-dx/azure-app-configuration/azurerm" + version = "~> 0.0" + + environment = (merge(local.environment, { instance_number = random_integer.appcs_instance.result })) + resource_group_name = azurerm_resource_group.e2e_appcs.name + + subscription_id = data.azurerm_subscription.current.subscription_id + + subnet_pep_id = data.azurerm_subnet.pep.id + virtual_network = { + name = local.e2e_virtual_network.name + resource_group_name = local.e2e_virtual_network.resource_group_name + } + + private_dns_zone_resource_group_name = data.azurerm_resource_group.network.name + + key_vault = { + has_rbac_support = true + name = azurerm_key_vault.kv.name + resource_group_name = azurerm_key_vault.kv.resource_group_name + subscription_id = data.azurerm_subscription.current.subscription_id + } + + tags = local.tags +} diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/outputs.tf b/infra/modules/azure_app_configuration/examples/keyvault_integration/outputs.tf new file mode 100644 index 0000000000..7cbeb6f83e --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/outputs.tf @@ -0,0 +1,7 @@ +output "name" { + value = module.appcs_with_kv.name +} + +output "private_app_ip_address" { + value = azurerm_container_group.private_app.ip_address +} diff --git a/infra/modules/azure_app_configuration/examples/keyvault_integration/providers.tf b/infra/modules/azure_app_configuration/examples/keyvault_integration/providers.tf new file mode 100644 index 0000000000..2afef9ed75 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/keyvault_integration/providers.tf @@ -0,0 +1,37 @@ +terraform { + required_version = ">= 1.13.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + pagopa-dx = { + source = "pagopa-dx/azure" + version = "~> 0.8" + } + random = { + source = "hashicorp/random" + version = "~> 3.7" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = false + } + app_configuration { + purge_soft_delete_on_destroy = true + recover_soft_deleted = true + } + } +} + +provider "random" { +} diff --git a/infra/modules/azure_app_configuration/examples/network_access/README.md b/infra/modules/azure_app_configuration/examples/network_access/README.md new file mode 100644 index 0000000000..d314ca0032 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/README.md @@ -0,0 +1,57 @@ +# network_access + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.13.0 | +| [azurerm](#requirement\_azurerm) | ~> 4.0 | +| [dx](#requirement\_dx) | ~> 0.8 | +| [random](#requirement\_random) | ~> 3.7 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [integration\_github\_roles](#module\_integration\_github\_roles) | pagopa-dx/azure-role-assignments/azurerm | ~> 1.0 | +| [private\_appcs](#module\_private\_appcs) | ../../ | n/a | +| [role\_appcs\_private](#module\_role\_appcs\_private) | pagopa-dx/azure-role-assignments/azurerm | ~> 1.0 | +| [role\_appcs\_public](#module\_role\_appcs\_public) | pagopa-dx/azure-role-assignments/azurerm | ~> 1.0 | + +## Resources + +| Name | Type | +|------|------| +| [azurerm_app_configuration_key.test_secret](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_configuration_key) | resource | +| [azurerm_app_configuration_key.test_setting](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_configuration_key) | resource | +| [azurerm_container_group.private_app](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_group) | resource | +| [azurerm_container_group.public_app](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_group) | resource | +| [azurerm_key_vault.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource | +| [azurerm_key_vault_secret.test_secret](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault_secret) | resource | +| [azurerm_private_endpoint.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint) | resource | +| [azurerm_resource_group.e2e_appcs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | +| [azurerm_subnet.private_app](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource | +| [dx_available_subnet_cidr.private_app](https://registry.terraform.io/providers/pagopa-dx/azure/latest/docs/resources/available_subnet_cidr) | resource | +| [random_integer.appcs_instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | +| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | +| [azurerm_log_analytics_workspace.e2e](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/log_analytics_workspace) | data source | +| [azurerm_private_dns_zone.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/private_dns_zone) | data source | +| [azurerm_resource_group.network](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | +| [azurerm_subnet.pep](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | +| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) | data source | +| [azurerm_user_assigned_identity.integration_github](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/user_assigned_identity) | data source | +| [azurerm_virtual_network.e2e](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [name](#output\_name) | n/a | +| [private\_app\_ip\_address](#output\_private\_app\_ip\_address) | n/a | +| [public\_app\_ip\_address](#output\_public\_app\_ip\_address) | n/a | + diff --git a/infra/modules/azure_app_configuration/examples/network_access/data.tf b/infra/modules/azure_app_configuration/examples/network_access/data.tf new file mode 100644 index 0000000000..3976ac7fdb --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/data.tf @@ -0,0 +1,38 @@ +data "azurerm_client_config" "current" {} + +data "azurerm_subscription" "current" {} + +data "azurerm_log_analytics_workspace" "e2e" { + name = local.e2e_log_analytics_workspace.name + resource_group_name = local.e2e_virtual_network.resource_group_name +} + +data "azurerm_private_dns_zone" "kv" { + name = "privatelink.vaultcore.azure.net" + resource_group_name = data.azurerm_resource_group.network.name +} + +data "azurerm_user_assigned_identity" "integration_github" { + name = "dx-d-itn-devex-integration-id-01" + resource_group_name = "dx-d-itn-devex-rg-01" +} +data "azurerm_virtual_network" "e2e" { + name = local.e2e_virtual_network.name + resource_group_name = local.e2e_virtual_network.resource_group_name +} + +data "azurerm_subnet" "pep" { + name = provider::dx::resource_name(merge(local.naming_config, { + name = "pep", + resource_type = "subnet" + })) + virtual_network_name = data.azurerm_virtual_network.e2e.name + resource_group_name = data.azurerm_virtual_network.e2e.resource_group_name +} + +data "azurerm_resource_group" "network" { + name = provider::dx::resource_name(merge(local.naming_config, { + name = "network" + resource_type = "resource_group" + })) +} diff --git a/infra/modules/azure_app_configuration/examples/network_access/fixtures.tf b/infra/modules/azure_app_configuration/examples/network_access/fixtures.tf new file mode 100644 index 0000000000..a407478c09 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/fixtures.tf @@ -0,0 +1,224 @@ +resource "azurerm_resource_group" "e2e_appcs" { + name = provider::dx::resource_name(merge(local.naming_config, { + domain = "e2e" + name = "appcs", + resource_type = "resource_group" + })) + location = local.environment.location + + tags = local.tags +} + +resource "azurerm_container_group" "public_app" { + name = provider::dx::resource_name( + merge(local.naming_config, { name = "appcs-public", resource_type = "container_instance" }) + ) + location = local.environment.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + + identity { type = "SystemAssigned" } + + os_type = "Linux" + + container { + name = "network-access" + image = local.docker_image + cpu = "0.5" + memory = "1.5" + ports { + port = 8080 + } + } + + diagnostics { + log_analytics { + workspace_id = data.azurerm_log_analytics_workspace.e2e.workspace_id + workspace_key = data.azurerm_log_analytics_workspace.e2e.primary_shared_key + } + } + + tags = local.tags +} + +resource "dx_available_subnet_cidr" "private_app" { + virtual_network_id = data.azurerm_virtual_network.e2e.id + prefix_length = 26 +} + +resource "azurerm_subnet" "private_app" { + name = provider::dx::resource_name(merge(local.naming_config, { + name = "appcs-private", + resource_type = "container_instance_subnet" + })) + resource_group_name = local.e2e_virtual_network.resource_group_name + virtual_network_name = local.e2e_virtual_network.name + address_prefixes = [dx_available_subnet_cidr.private_app.cidr_block] + + delegation { + name = "Microsoft.ContainerInstance/containerGroups" + + service_delegation { + name = "Microsoft.ContainerInstance/containerGroups" + actions = [ + "Microsoft.Network/virtualNetworks/subnets/action", + ] + } + } +} + +resource "azurerm_container_group" "private_app" { + name = provider::dx::resource_name( + merge(local.naming_config, { name = "appcs-private", resource_type = "container_instance" }) + ) + location = local.environment.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + + identity { type = "SystemAssigned" } + + os_type = "Linux" + + container { + name = "network-access" + image = local.docker_image + cpu = "0.5" + memory = "1.5" + ports { + port = 8080 + } + } + + ip_address_type = "Private" + + subnet_ids = [ + azurerm_subnet.private_app.id + ] + + diagnostics { + log_analytics { + workspace_id = data.azurerm_log_analytics_workspace.e2e.workspace_id + workspace_key = data.azurerm_log_analytics_workspace.e2e.primary_shared_key + } + } + + tags = local.tags +} + +#trivy:ignore:AVD-AZU-0016 +resource "azurerm_key_vault" "kv" { + name = provider::dx::resource_name(merge(local.naming_config, { resource_type = "key_vault", instance_number = random_integer.appcs_instance.result })) + location = azurerm_resource_group.e2e_appcs.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + tenant_id = data.azurerm_client_config.current.tenant_id + rbac_authorization_enabled = true + sku_name = "standard" + purge_protection_enabled = false + public_network_access_enabled = false + + network_acls { + bypass = "AzureServices" + default_action = "Deny" + } + + tags = local.tags +} + +#trivy:ignore:AVD-AZU-0015 +#trivy:ignore:AVD-AZU-0017 +resource "azurerm_key_vault_secret" "test_secret" { + name = "secret-key" + key_vault_id = azurerm_key_vault.kv.id + value = "secret-value" + + depends_on = [ + module.integration_github_roles + ] +} + +resource "azurerm_private_endpoint" "kv" { + name = provider::dx::resource_name(merge(local.naming_config, { resource_type = "key_vault_private_endpoint", instance_number = random_integer.appcs_instance.result })) + location = azurerm_resource_group.e2e_appcs.location + resource_group_name = azurerm_resource_group.e2e_appcs.name + subnet_id = data.azurerm_subnet.pep.id + + private_service_connection { + name = provider::dx::resource_name(merge(local.naming_config, { resource_type = "key_vault_private_endpoint", instance_number = random_integer.appcs_instance.result })) + private_connection_resource_id = azurerm_key_vault.kv.id + is_manual_connection = false + subresource_names = ["vault"] + } + + private_dns_zone_group { + name = "private-dns-zone-group" + private_dns_zone_ids = [data.azurerm_private_dns_zone.kv.id] + } + + tags = local.tags +} + +module "role_appcs_private" { + source = "pagopa-dx/azure-role-assignments/azurerm" + version = "~> 1.0" + + principal_id = azurerm_container_group.private_app.identity[0].principal_id + subscription_id = data.azurerm_subscription.current.subscription_id + + app_config = [ + { + name = module.private_appcs.name + resource_group_name = module.private_appcs.resource_group_name + description = "Allow private Container Instance to read from App Configuration" + role = "reader" + } + ] + + # key_vault = { + # name = module.private_keyvault.key_vault_name + # resource_group_name = module.private_keyvault.resource_group_name + # has_rbac_enabled = true + # } +} + +module "role_appcs_public" { + source = "pagopa-dx/azure-role-assignments/azurerm" + version = "~> 1.0" + + principal_id = azurerm_container_group.public_app.identity[0].principal_id + subscription_id = data.azurerm_subscription.current.subscription_id + + app_config = [ + { + name = module.private_appcs.name + resource_group_name = module.private_appcs.resource_group_name + description = "Allow public Container Instance to read from App Configuration" + role = "reader" + } + ] +} + +module "integration_github_roles" { + source = "pagopa-dx/azure-role-assignments/azurerm" + version = "~> 1.0" + + principal_id = data.azurerm_user_assigned_identity.integration_github.principal_id + subscription_id = data.azurerm_subscription.current.subscription_id + + app_config = [ + { + name = module.private_appcs.name + resource_group_name = module.private_appcs.resource_group_name + description = "Allow GitHub to write settings on App Configuration" + role = "writer" + } + ] + + key_vault = [ + { + name = azurerm_key_vault.kv.name + resource_group_name = azurerm_key_vault.kv.resource_group_name + description = "Allow GitHub to write secrets on Key Vault" + roles = { + secrets = "writer" + } + } + ] +} diff --git a/infra/modules/azure_app_configuration/examples/network_access/locals.tf b/infra/modules/azure_app_configuration/examples/network_access/locals.tf new file mode 100644 index 0000000000..c12ab0b8d6 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/locals.tf @@ -0,0 +1,47 @@ +locals { + tags = { + CostCenter = "TS000 - Tecnologia e Servizi" + CreatedBy = "Terraform" + Environment = "Dev" + BusinessUnit = "DevEx" + Source = "https://github.com/pagopa/dx/tests/azure_app_configuration" + ManagementTeam = "Developer Experience" + TestSuite = "e2e" + } + + environment = { + prefix = "dx" + env_short = "d" + location = "italynorth" + app_name = "e2e" + instance_number = "01" + } + + naming_config = { + prefix = local.environment.prefix, + environment = local.environment.env_short, + location = local.environment.location, + name = local.environment.app_name, + instance_number = tonumber(local.environment.instance_number), + } + + e2e_virtual_network = { + name = provider::dx::resource_name(merge(local.naming_config, { + name = "e2e", + resource_type = "virtual_network" + })) + resource_group_name = provider::dx::resource_name(merge(local.naming_config, { + name = "e2e", + resource_type = "resource_group" + })) + } + + e2e_log_analytics_workspace = { + name = provider::dx::resource_name(merge(local.naming_config, { + name = "e2e", + resource_type = "log_analytics" + })) + } + + docker_image = "ghcr.io/pagopa/e2e-appconfiguration-all-scenarios:latest" +} diff --git a/infra/modules/azure_app_configuration/examples/network_access/mut.tf b/infra/modules/azure_app_configuration/examples/network_access/mut.tf new file mode 100644 index 0000000000..005e7440ce --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/mut.tf @@ -0,0 +1,46 @@ +resource "random_integer" "appcs_instance" { + min = 1 + max = 99 +} + +module "private_appcs" { + source = "pagopa-dx/azure-app-configuration/azurerm" + version = "~> 0.0" + + environment = (merge(local.environment, { instance_number = random_integer.appcs_instance.result })) + resource_group_name = azurerm_resource_group.e2e_appcs.name + + subscription_id = data.azurerm_subscription.current.subscription_id + + subnet_pep_id = data.azurerm_subnet.pep.id + virtual_network = { + name = local.e2e_virtual_network.name + resource_group_name = local.e2e_virtual_network.resource_group_name + } + + private_dns_zone_resource_group_name = data.azurerm_resource_group.network.name + tags = local.tags +} + +resource "azurerm_app_configuration_key" "test_setting" { + configuration_store_id = module.private_appcs.id + key = "Setting:test-key" + value = "test value" + content_type = "application/json" + + depends_on = [ + module.integration_github_roles + ] +} + +resource "azurerm_app_configuration_key" "test_secret" { + configuration_store_id = module.private_appcs.id + key = "Secret:secret-key" + type = "vault" + vault_key_reference = azurerm_key_vault_secret.test_secret.versionless_id + + depends_on = [ + module.integration_github_roles, + azurerm_key_vault_secret.test_secret + ] +} diff --git a/infra/modules/azure_app_configuration/examples/network_access/outputs.tf b/infra/modules/azure_app_configuration/examples/network_access/outputs.tf new file mode 100644 index 0000000000..44a2a88497 --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/outputs.tf @@ -0,0 +1,11 @@ +output "name" { + value = module.private_appcs.name +} + +output "public_app_ip_address" { + value = azurerm_container_group.public_app.ip_address +} + +output "private_app_ip_address" { + value = azurerm_container_group.private_app.ip_address +} diff --git a/infra/modules/azure_app_configuration/examples/network_access/providers.tf b/infra/modules/azure_app_configuration/examples/network_access/providers.tf new file mode 100644 index 0000000000..d8ebedddaf --- /dev/null +++ b/infra/modules/azure_app_configuration/examples/network_access/providers.tf @@ -0,0 +1,36 @@ +terraform { + required_version = ">= 1.13.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + dx = { + source = "pagopa-dx/azure" + version = "~> 0.8" + } + random = { + source = "hashicorp/random" + version = "~> 3.7" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + app_configuration { + purge_soft_delete_on_destroy = true + recover_soft_deleted = true + } + } +} + +provider "dx" { +} + +provider "random" { +} diff --git a/infra/modules/azure_app_configuration/tests/apps/all_scenarios/Dockerfile b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/Dockerfile new file mode 100644 index 0000000000..2985c423cd --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/Dockerfile @@ -0,0 +1,16 @@ +# Dockerfile +FROM public.ecr.aws/docker/library/golang:tip-alpine3.22 AS build + +WORKDIR /src +COPY go.mod . +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -o app main.go + +# trivy:ignore:AVD-DS-0002 +# trivy:ignore:AVD-DS-0026 +FROM gcr.io/distroless/base-debian12:nonroot + +EXPOSE 8080 +ENTRYPOINT ["/app"] +COPY --from=build /src/app /app diff --git a/infra/modules/azure_app_configuration/tests/apps/all_scenarios/README.md b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/README.md new file mode 100644 index 0000000000..16d4141ace --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/README.md @@ -0,0 +1 @@ +# All Scenarios diff --git a/infra/modules/azure_app_configuration/tests/apps/all_scenarios/project.json b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/project.json new file mode 100644 index 0000000000..a4a99d2208 --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/project.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../../../../../node_modules/nx/schemas/project-schema.json", + "name": "app_configuration_tests_all_scenarios", + "version": "0.0.0", + "description": "Simple application for Terraform E2E tests, which exposes endpoints to test connectivity and integration with App Configuration", + "private": true, + "targets": { + "docker:build": { + "options": { + "platform": "linux/amd64,linux/arm64" + } + }, + "docker:run": { + "options": { + "p": "8080:8080" + } + } + } +} diff --git a/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/go.mod b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/go.mod new file mode 100644 index 0000000000..a836ac875e --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/go.mod @@ -0,0 +1,27 @@ +module github.com/pagopa/dx/modules/azurerm_azure_app_configuration/tests/apps/all_scenarios + +go 1.25.4 + +require ( + github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect +) diff --git a/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/go.sum b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/go.sum new file mode 100644 index 0000000000..8dfa6298d0 --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/go.sum @@ -0,0 +1,59 @@ +github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration v1.3.0 h1:LpR4+JABMeDLrCJZUlS2QRJ1OiQRkSFnMiJ+HOPgE8U= +github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration v1.3.0/go.mod h1:JZjuVES/kbo6vp6wC0W5Mk72obx6BM54JCBa+oZY4mk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0 h1:uU4FujKFQAz31AbWOO3INV9qfIanHeIUSsGhRlcJJmg= +github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0/go.mod h1:qr3M3Oy6V98VR0c5tCHKUpaeJTRQh6KYzJewRtFWqfc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/main.go b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/main.go new file mode 100644 index 0000000000..b1168eb1a5 --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/apps/all_scenarios/src/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "time" + + "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" +) + +type Config struct { + TestKey string `json:"test-key"` +} + +type Secret struct { + SecretKey string `json:"secret-key"` +} + +const timeout = 5 + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("/setting", settingHandler) + mux.HandleFunc("/secret", secretHandler) + + addr := ":8080" + log.Printf("listening on %s", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Printf("listen error: %s", err) + } +} + +func settingHandler(w http.ResponseWriter, r *http.Request) { + hostname := getHostnameFromQueryString(r) + + ctx, cancel := context.WithTimeout(r.Context(), timeout*time.Second) + defer cancel() + + provider, err := loadAzureAppConfiguration(ctx, hostname, false) + if err != nil { + log.Printf("Failed to load Azure App Configuration: %v", err) + w.WriteHeader(http.StatusBadGateway) + return + } + + var config Config + if err := provider.Unmarshal(&config, nil); err != nil { + log.Printf("Failed to unmarshal configuration: %v", err) + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{"key":"%s"}`, config.TestKey) +} + +func secretHandler(w http.ResponseWriter, r *http.Request) { + hostname := getHostnameFromQueryString(r) + + ctx, cancel := context.WithTimeout(r.Context(), timeout*time.Second) + defer cancel() + + provider, err := loadAzureAppConfiguration(ctx, hostname, true) + if err != nil { + log.Printf("Failed to load Azure App Configuration: %v", err) + w.WriteHeader(http.StatusBadGateway) + return + } + + var secret Secret + if err := provider.Unmarshal(&secret, nil); err != nil { + log.Printf("Failed to unmarshal configuration: %v", err) + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{"secret-key":"%s"}`, secret.SecretKey) +} + +func getHostnameFromQueryString(r *http.Request) string { + q := r.URL.Query() + instanceName := q.Get("instanceName") + return fmt.Sprintf("%s.azconfig.io", instanceName) +} + +func loadAzureAppConfiguration(ctx context.Context, hostname string, enableSecret bool) (*azureappconfiguration.AzureAppConfiguration, error) { + credential, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + log.Printf("Failed to create credential: %v", err) + } + + authOptions := azureappconfiguration.AuthenticationOptions{ + Endpoint: fmt.Sprintf("https://%s", hostname), + Credential: credential, + } + + keyFilter := "Setting" + if enableSecret { + keyFilter = "Secret" + } + + options := &azureappconfiguration.Options{ + Selectors: []azureappconfiguration.Selector{ + { + KeyFilter: fmt.Sprintf("%s:*", keyFilter), + }, + }, + TrimKeyPrefixes: []string{fmt.Sprintf("%s:", keyFilter)}, + } + + if enableSecret { + options.KeyVaultOptions = azureappconfiguration.KeyVaultOptions{ + Credential: credential, + } + } + + return azureappconfiguration.Load(ctx, authOptions, options) +} diff --git a/infra/modules/azure_app_configuration/tests/e2e_test.go b/infra/modules/azure_app_configuration/tests/e2e_test.go new file mode 100644 index 0000000000..00e4ba32c7 --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/e2e_test.go @@ -0,0 +1,89 @@ +package test + +import ( + "crypto/tls" + "fmt" + "strings" + "testing" + "time" + + httpHelper "github.com/gruntwork-io/terratest/modules/http-helper" + "github.com/gruntwork-io/terratest/modules/terraform" + test_structure "github.com/gruntwork-io/terratest/modules/test-structure" +) + +func TestAppConfigurationNetworkSettings(t *testing.T) { + fixtureFolder := "../examples/network_access/" + + test_structure.RunTestStage(t, "setup", func() { + terraformOptions := &terraform.Options{ + TerraformDir: fixtureFolder, + } + + test_structure.SaveTerraformOptions(t, fixtureFolder, terraformOptions) + + terraform.InitAndApply(t, terraformOptions) + }) + + test_structure.RunTestStage(t, "validate_private_connectivity", func() { + terraformOptions := test_structure.LoadTerraformOptions(t, fixtureFolder) + + appConfigName := terraform.Output(t, terraformOptions, "name") + publicApp := terraform.Output(t, terraformOptions, "public_app_ip_address") + privateApp := terraform.Output(t, terraformOptions, "private_app_ip_address") + + probeSetting(t, privateApp, appConfigName, 200) + probeSetting(t, publicApp, appConfigName, 502) + }) + + // test_structure.RunTestStage(t, "teardown", func() { + // terraformOptions := test_structure.LoadTerraformOptions(t, fixtureFolder) + // terraform.Destroy(t, terraformOptions) + // }) +} + +func TestAppConfigurationKeyVaultIntegration(t *testing.T) { + fixtureFolder := "../examples/key_vault_integration/" + + test_structure.RunTestStage(t, "setup", func() { + terraformOptions := &terraform.Options{ + TerraformDir: fixtureFolder, + } + + test_structure.SaveTerraformOptions(t, fixtureFolder, terraformOptions) + + terraform.InitAndApply(t, terraformOptions) + }) + + test_structure.RunTestStage(t, "validate_keyvault_integration", func() { + terraformOptions := test_structure.LoadTerraformOptions(t, fixtureFolder) + + }) +} + +func probeSetting(t *testing.T, appIPAddress string, appConfigName string, expectedStatus int) { + url := fmt.Sprintf("http://%s:8080/setting?instanceName=%s", appIPAddress, appConfigName) + + maxRetries := 3 + delay := 5 * time.Second + + options := httpHelper.HttpGetOptions{ + Url: url, + TlsConfig: &tls.Config{}, + Timeout: 15, + } + + httpHelper.HttpGetWithRetryWithCustomValidationWithOptions(t, options, maxRetries, delay, func(statusCode int, body string) bool { + if statusCode != expectedStatus { + return false + } + if expectedStatus == 502 { + return true + } + trimmed := strings.TrimSpace(body) + if expectedStatus == 200 { + return trimmed == `{"key":"test value"}` + } + return false + }) +} diff --git a/infra/modules/azure_app_configuration/tests/go.mod b/infra/modules/azure_app_configuration/tests/go.mod new file mode 100644 index 0000000000..e88bba5fb8 --- /dev/null +++ b/infra/modules/azure_app_configuration/tests/go.mod @@ -0,0 +1,3 @@ +module github.com/pagopa/dx/modules/azure_app_configuration/e2e + +go 1.25.4 diff --git a/infra/modules/azure_app_configuration/tests/setup/README.md b/infra/modules/azure_app_configuration/tests/setup/README.md index a46df95900..11a1e7d420 100644 --- a/infra/modules/azure_app_configuration/tests/setup/README.md +++ b/infra/modules/azure_app_configuration/tests/setup/README.md @@ -1,13 +1,14 @@ # setup + ## Requirements -| Name | Version | -|------|---------| -| [azurerm](#requirement\_azurerm) | ~> 4.0 | -| [dx](#requirement\_dx) | ~> 0.8 | -| [random](#requirement\_random) | ~> 3.7 | +| Name | Version | +| ------------------------------------------------------------------ | ------- | +| [azurerm](#requirement_azurerm) | ~> 4.0 | +| [dx](#requirement_dx) | ~> 0.8 | +| [random](#requirement_random) | ~> 3.7 | ## Modules @@ -15,39 +16,40 @@ No modules. ## Resources -| Name | Type | -|------|------| -| [azurerm_key_vault.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource | -| [azurerm_private_endpoint.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint) | resource | -| [azurerm_resource_group.sut](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [random_integer.appcs_kv_instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | -| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | -| [azurerm_private_dns_zone.appcs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/private_dns_zone) | data source | -| [azurerm_private_dns_zone.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/private_dns_zone) | data source | -| [azurerm_resource_group.network](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [azurerm_resource_group.test](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [azurerm_subnet.pep](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | +| Name | Type | +| ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| [azurerm_key_vault.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource | +| [azurerm_private_endpoint.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint) | resource | +| [azurerm_resource_group.sut](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | +| [random_integer.appcs_kv_instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | +| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | +| [azurerm_private_dns_zone.appcs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/private_dns_zone) | data source | +| [azurerm_private_dns_zone.kv](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/private_dns_zone) | data source | +| [azurerm_resource_group.network](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | +| [azurerm_resource_group.test](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | +| [azurerm_subnet.pep](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | | [azurerm_user_assigned_identity.test](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/user_assigned_identity) | data source | -| [azurerm_virtual_network.vnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | +| [azurerm_virtual_network.vnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [environment](#input\_environment) | n/a |
object({
prefix = string
env_short = string
location = string
domain = optional(string)
app_name = string
instance_number = string
})
| n/a | yes | -| [tags](#input\_tags) | Tags to apply to setup resources | `map(string)` | n/a | yes | -| [test\_kind](#input\_test\_kind) | A value between integration and e2e | `string` | n/a | yes | +| Name | Description | Type | Default | Required | +| ------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | :------: | +| [environment](#input_environment) | n/a |
object({
prefix = string
env_short = string
location = string
domain = optional(string)
app_name = string
instance_number = string
})
| n/a | yes | +| [tags](#input_tags) | Tags to apply to setup resources | `map(string)` | n/a | yes | +| [test_kind](#input_test_kind) | A value between integration and e2e | `string` | n/a | yes | ## Outputs -| Name | Description | -|------|-------------| -| [key\_vaults](#output\_key\_vaults) | n/a | -| [managed\_identity\_principal\_id](#output\_managed\_identity\_principal\_id) | n/a | -| [private\_dns\_zone\_appcs](#output\_private\_dns\_zone\_appcs) | n/a | -| [private\_dns\_zone\_resource\_group\_name](#output\_private\_dns\_zone\_resource\_group\_name) | n/a | -| [resource\_group\_name](#output\_resource\_group\_name) | n/a | -| [subnet\_pep\_id](#output\_subnet\_pep\_id) | n/a | -| [subscription\_id](#output\_subscription\_id) | n/a | -| [virtual\_network](#output\_virtual\_network) | n/a | +| Name | Description | +| ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| [key_vaults](#output_key_vaults) | n/a | +| [managed_identity_principal_id](#output_managed_identity_principal_id) | n/a | +| [private_dns_zone_appcs](#output_private_dns_zone_appcs) | n/a | +| [private_dns_zone_resource_group_name](#output_private_dns_zone_resource_group_name) | n/a | +| [resource_group_name](#output_resource_group_name) | n/a | +| [subnet_pep_id](#output_subnet_pep_id) | n/a | +| [subscription_id](#output_subscription_id) | n/a | +| [virtual_network](#output_virtual_network) | n/a | +