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"
+ }
+ }
+}