diff --git a/terraform/.terraform-docs.yml b/terraform/.terraform-docs.yml new file mode 100644 index 0000000..a691780 --- /dev/null +++ b/terraform/.terraform-docs.yml @@ -0,0 +1,26 @@ +--- +formatter: "markdown table" +version: "~> 0.16" +settings: + anchor: true + default: true + description: false + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: true + read-comments: true + required: true + sensitive: true + type: true +sort: + enabled: true + by: name +output: + file: README.md + mode: inject + template: |- + + {{ .Content }} + diff --git a/terraform/.terraform-version b/terraform/.terraform-version new file mode 100644 index 0000000..bfa363e --- /dev/null +++ b/terraform/.terraform-version @@ -0,0 +1 @@ +1.8.4 diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..8aeedc9 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,81 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/azure/azapi" { + version = "1.13.1" + constraints = ">= 1.13.1" + hashes = [ + "h1:2cnqo8u7YMuBexFZv8/lXGxIn1dXuEnC44LAL90GAa0=", + "zh:1f2aceddd67ceeb82a75c2f15dc01e54781e9aed5968507dbc29590c165b2e2b", + "zh:397f0bfbac899d48e23cecf38d362c27562150aa20b19157b5bd370b8e6801ee", + "zh:652263b7d00623684e29ef7b8ff285a17c5bd7cc8ba7d22967c66d0b3a3c568a", + "zh:652c53320a41434942877515780296a1509be03f32d54e60178f39200f960a67", + "zh:666426faf686401e54ec09fe06e9d7c06a6455ec398764f70558440c73aeb7f9", + "zh:6aa91ae8ba78f2494f99b4c99e66d15ed0b14d735cd1f77adc12ff9dfa075807", + "zh:a529e5a13c37d1805c469227f08cdbe7527d04dd64d18709d26627c6a0b588b1", + "zh:a589c049205e8e5bf94a13d56b28f400d908ad27e13e16df64408ee82eb8a0ff", + "zh:a9a50defdee230f315f74be6c77ff104fe2610a1b3ad6b87326f555e80d13b18", + "zh:ba49ef70d96e13795e2dbffd6cb2ff976dfe84e0373a5971ebe3b4c9c9b7af60", + "zh:d3ed50efe5f8c80d3d7d464ab9a13ccf82440d871c9ce3032ce476845364c6b9", + "zh:e3eb48ee8c36ee4f81850d8a21fc59b81886c729d7c3b7adece4a25f355bed2f", + ] +} + +provider "registry.terraform.io/hashicorp/azuread" { + version = "2.49.1" + constraints = ">= 2.37.1" + hashes = [ + "h1:3SsxIXBu068WDJaaQeshKVrkXppJJoczHop7h9nolT0=", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:402c943f0508f7dae29cabe3352e4430cf7ef9c569433392624ea46d834892ae", + "zh:4cb66ad4e6d40b5a58160d90c1922e2e67e4c89b3c7543b227f5ecafe97a4b41", + "zh:549b966a79433939e154e3bd926069cfd21180546a94e98ee6d5f17d6efca3b1", + "zh:6cba71af694b06563903767a940d701375737ccc7898d8156ed5df10ba4d4118", + "zh:7867c7065bc9eebf79b0dad1b64056fd991490eba9973378e8c8df61fd57f6d7", + "zh:ab280f6ed9b59adff1b25e4d5c86417359adf72aabe49d0a4ab19c93adbfbddf", + "zh:b68fbefe5043bd224265d81629650572095b6c375a2ad0c7046980ba06fa472f", + "zh:c35bf5d22d8051c7da2fdced75d8fe86142c117a746c4fd0ed917b1c3e780838", + "zh:c8826f24bd0a48ad46a56844ef85064c70b64d83214907089f06c3b84a1dca04", + "zh:ccd3bb336ad73b17861c720af41401d9d04f9d0e097c1fc36af56895697ae7a0", + "zh:d2e6f67d31cd334b9af32243f40ed564d4acf67b1dff39c47a752a9e22361e44", + ] +} + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.103.1" + hashes = [ + "h1:6bkftpJOwSmA74r2ewWu5JrIoSbwzWRAcsv7S/A+6fs=", + "zh:0e78a947c041893a47d2af804f2896d1337cc06230e730e3926db78d416ba883", + "zh:21666ab923f659a2fb7a28eee464249acc8617a21eeb4a805fd1acce5f6c0768", + "zh:357f7daa2f8cc88394d357192f736b21c2626aa99e31bf0dc0dc2fcf6956e555", + "zh:3bfaaa2b1b20841093c44c863bd3cf31068fc6e51b72f85006aa6e656e6555c6", + "zh:624d8eea3587b606209cbae89c51070aa85bf4877ea7d4ffeb4cb5d90d0cd3bb", + "zh:b66a65f0f60e62b9dc911f5376e7801d481810b8c52ae5e36a58730be0779b8a", + "zh:c0362821d82e9a989de4217527f7b9858cd71923508147ae65f47b32ffd85a0e", + "zh:ca8d1fc6e67af8970d3655c8f47bccd4e799b2efb5c7ce402ace7462915f30b3", + "zh:cd9aa496be3900b447a3c3e041e9d25aa6d10a6b0b4d1ebb1385cd6668d35b50", + "zh:d2350210ad53f1dd18ec29b84255aa7b14877e0f1cb5ae77355f9b8ebe2ea209", + "zh:f51cbed8c9b225fb346cc42d884c41bf43bb79c90d753e8cf2770362e4689d79", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.2" + constraints = ">= 3.2.1" + hashes = [ + "h1:IMVAUHKoydFrlPrl9OzasDnw/8ntZFerCC9iXw1rXQY=", + "zh:3248aae6a2198f3ec8394218d05bd5e42be59f43a3a7c0b71c66ec0df08b69e7", + "zh:32b1aaa1c3013d33c245493f4a65465eab9436b454d250102729321a44c8ab9a", + "zh:38eff7e470acb48f66380a73a5c7cdd76cc9b9c9ba9a7249c7991488abe22fe3", + "zh:4c2f1faee67af104f5f9e711c4574ff4d298afaa8a420680b0cb55d7bbc65606", + "zh:544b33b757c0b954dbb87db83a5ad921edd61f02f1dc86c6186a5ea86465b546", + "zh:696cf785090e1e8cf1587499516b0494f47413b43cb99877ad97f5d0de3dc539", + "zh:6e301f34757b5d265ae44467d95306d61bef5e41930be1365f5a8dcf80f59452", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:913a929070c819e59e94bb37a2a253c228f83921136ff4a7aa1a178c7cce5422", + "zh:aa9015926cd152425dbf86d1abdbc74bfe0e1ba3d26b3db35051d7b9ca9f72ae", + "zh:bb04798b016e1e1d49bcc76d62c53b56c88c63d6f2dfe38821afef17c416a0e1", + "zh:c23084e1b23577de22603cff752e59128d83cfecc2e6819edadd8cf7a10af11e", + ] +} diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..96a8c7e --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,54 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.6.1 | +| [azapi](#requirement\_azapi) | >=1.13.1 | +| [azurerm](#requirement\_azurerm) | >= 3.51.0 | + +## Providers + +| Name | Version | +|------|---------| +| [azapi](#provider\_azapi) | 1.13.1 | +| [azurerm](#provider\_azurerm) | 3.103.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [azurerm\_key\_vault](#module\_azurerm\_key\_vault) | github.com/DFE-Digital/terraform-azurerm-key-vault-tfvars | v0.4.1 | + +## Resources + +| Name | Type | +|------|------| +| [azapi_update_resource.patch_logs](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/update_resource) | resource | +| [azurerm_container_group.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_group) | resource | +| [azurerm_log_analytics_workspace.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace) | resource | +| [azurerm_resource_group.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | +| [azurerm_route_table.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route_table) | resource | +| [azurerm_subnet.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource | +| [azurerm_subnet_route_table_association.container_apps_infra_subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_route_table_association) | resource | +| [azurerm_user_assigned_identity.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) | resource | +| [azurerm_virtual_network.default](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network) | resource | +| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [environment](#input\_environment) | Environment name | `string` | n/a | yes | +| [key\_vault\_access\_ipv4](#input\_key\_vault\_access\_ipv4) | List of IPv4 Addresses that are permitted to access the Key Vault | `list(string)` | n/a | yes | +| [registry\_password](#input\_registry\_password) | Password for authenticating to the Container Registry | `string` | `""` | no | +| [registry\_server](#input\_registry\_server) | Hostname of the Container Registry | `string` | n/a | yes | +| [registry\_username](#input\_registry\_username) | Username for authenticating to the Container Registry | `string` | `""` | no | +| [slack\_webhook\_url](#input\_slack\_webhook\_url) | A Slack Webhook URL that the script can route messages to | `string` | `""` | no | +| [tags](#input\_tags) | Tags to assign to the resources | `map(string)` | `{}` | no | +| [tfvars\_filename](#input\_tfvars\_filename) | tfvars filename. This file is uploaded and stored encrypted within Key Vault, to ensure that the latest tfvars are stored in a shared place. | `string` | n/a | yes | + +## Outputs + +No outputs. + diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..6602f20 --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,3 @@ +terraform { + backend "azurerm" {} +} diff --git a/terraform/backend.vars.example b/terraform/backend.vars.example new file mode 100644 index 0000000..718c7ca --- /dev/null +++ b/terraform/backend.vars.example @@ -0,0 +1,5 @@ +subscription_id = "" +resource_group_name = "" +storage_account_name = "" +container_name = "" +key = "terraform.tstate" diff --git a/terraform/data.tf b/terraform/data.tf new file mode 100644 index 0000000..634e52c --- /dev/null +++ b/terraform/data.tf @@ -0,0 +1 @@ +data "azurerm_subscription" "current" {} diff --git a/terraform/key-vault-tfvars-secrets.tf b/terraform/key-vault-tfvars-secrets.tf new file mode 100644 index 0000000..a6fe5ae --- /dev/null +++ b/terraform/key-vault-tfvars-secrets.tf @@ -0,0 +1,15 @@ +module "azurerm_key_vault" { + source = "github.com/DFE-Digital/terraform-azurerm-key-vault-tfvars?ref=v0.4.1" + + environment = local.environment + project_name = "afdcdv" + existing_resource_group = azurerm_resource_group.default.name + azure_location = local.region + key_vault_access_use_rbac_authorization = true + key_vault_access_users = [] + key_vault_access_ipv4 = local.key_vault_access_ipv4 + tfvars_filename = local.tfvars_filename + enable_diagnostic_setting = false + enable_diagnostic_storage_account = false + tags = local.tags +} diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..0314116 --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,20 @@ +locals { + region = "westeurope" + environment = var.environment + project_name = "rsd-kvscanner" + resource_prefix = "${local.environment}${local.project_name}" + registry_server = var.registry_server + registry_username = var.registry_username + registry_password = var.registry_password + registry_image_name = "rsd-kv-secret-scanner" + registry_image_tag = "latest" + job_cpu = 0.5 + job_memory = 1 + virtual_network_address_space = "172.16.0.0/12" + virtual_network_address_space_mask = element(split("/", local.virtual_network_address_space), 1) + container_apps_infra_subnet_cidr = cidrsubnet(local.virtual_network_address_space, 21 - local.virtual_network_address_space_mask, 0) + key_vault_access_ipv4 = var.key_vault_access_ipv4 + tfvars_filename = var.tfvars_filename + slack_webhook_url = var.slack_webhook_url + tags = var.tags +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..ba38009 --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,7 @@ +provider "azurerm" { + features {} + skip_provider_registration = true +} + +provider "azapi" { +} diff --git a/terraform/terraform.tf b/terraform/terraform.tf new file mode 100644 index 0000000..951938c --- /dev/null +++ b/terraform/terraform.tf @@ -0,0 +1,129 @@ +resource "azurerm_resource_group" "default" { + name = local.resource_prefix + location = local.region + + tags = local.tags +} + +resource "azurerm_log_analytics_workspace" "default" { + name = "${local.resource_prefix}-logs" + location = azurerm_resource_group.default.location + resource_group_name = azurerm_resource_group.default.name + sku = "PerGB2018" + retention_in_days = 30 + + tags = local.tags +} + +resource "azurerm_container_group" "default" { + name = "${local.resource_prefix}-job" + location = azurerm_resource_group.default.location + resource_group_name = azurerm_resource_group.default.name + ip_address_type = "Private" + os_type = "Linux" + + container { + name = "${local.resource_prefix}-containerjob" + image = "${local.registry_server}/${local.registry_image_name}:${local.registry_image_tag}" + cpu = local.job_cpu + memory = local.job_memory + commands = ["/bin/bash", "-c", "./docker-entrypoint.sh"] + + ports { # bogus + port = 65530 + protocol = "TCP" + } + + environment_variables = { + "AZ_SUBSCRIPTION_SCOPE" = data.azurerm_subscription.current.display_name + "SLACK_WEBHOOK_URL" = local.slack_webhook_url + } + } + + image_registry_credential { + server = local.registry_server + user_assigned_identity_id = azurerm_user_assigned_identity.default.id + } + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.default.id] + } + + exposed_port = [] + restart_policy = "Never" + subnet_ids = [azurerm_subnet.default.id] + + tags = local.tags +} + +# necessary because of: https://github.com/Azure/azure-rest-api-specs/issues/9768 +resource "azapi_update_resource" "patch_logs" { + type = "Microsoft.ContainerInstance/containerGroups@2023-05-01" + resource_id = azurerm_container_group.default.id + + body = jsonencode({ + properties = { + diagnostics : { + logAnalytics : { + "logType" : "ContainerInstanceLogs", + "workspaceId" : azurerm_log_analytics_workspace.default.workspace_id, + "workspaceKey" : azurerm_log_analytics_workspace.default.primary_shared_key + } + }, + imageRegistryCredentials : [ + { + "server" : local.registry_server, + "user_assigned_identity_id" : azurerm_user_assigned_identity.default.id + } + ] + } + }) +} + +resource "azurerm_virtual_network" "default" { + name = "${local.resource_prefix}default" + address_space = [local.virtual_network_address_space] + location = azurerm_resource_group.default.location + resource_group_name = azurerm_resource_group.default.name + + tags = local.tags +} + +resource "azurerm_route_table" "default" { + name = "${local.resource_prefix}default" + location = azurerm_resource_group.default.location + resource_group_name = azurerm_resource_group.default.name + disable_bgp_route_propagation = false + + tags = local.tags +} + +resource "azurerm_subnet" "default" { + name = "${local.resource_prefix}containerappsinfra" + virtual_network_name = azurerm_virtual_network.default.name + resource_group_name = azurerm_resource_group.default.name + address_prefixes = [local.container_apps_infra_subnet_cidr] + + delegation { + name = "delegation" + + service_delegation { + name = "Microsoft.ContainerInstance/containerGroups" + actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + } + } +} + +resource "azurerm_subnet_route_table_association" "container_apps_infra_subnet" { + subnet_id = azurerm_subnet.default.id + route_table_id = azurerm_route_table.default.id +} + +resource "azurerm_user_assigned_identity" "default" { + location = azurerm_resource_group.default.location + name = "${local.resource_prefix}-uami-containerjob" + resource_group_name = azurerm_resource_group.default.name + + tags = local.tags +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..d5ede61 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,37 @@ +variable "registry_server" { + description = "Hostname of the Container Registry" + type = string +} +variable "registry_username" { + description = "Username for authenticating to the Container Registry" + type = string + default = "" +} +variable "registry_password" { + description = "Password for authenticating to the Container Registry" + type = string + default = "" +} +variable "tags" { + description = "Tags to assign to the resources" + type = map(string) + default = {} +} +variable "environment" { + description = "Environment name" + type = string +} +variable "key_vault_access_ipv4" { + description = "List of IPv4 Addresses that are permitted to access the Key Vault" + type = list(string) +} +variable "tfvars_filename" { + description = "tfvars filename. This file is uploaded and stored encrypted within Key Vault, to ensure that the latest tfvars are stored in a shared place." + type = string +} +variable "slack_webhook_url" { + description = "A Slack Webhook URL that the script can route messages to" + sensitive = true + type = string + default = "" +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000..adf552e --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.6.1" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.51.0" + } + azapi = { + source = "Azure/azapi" + version = ">=1.13.1" + } + } +}