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..6a126f4 --- /dev/null +++ b/terraform/.terraform-version @@ -0,0 +1 @@ +1.7.5 diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..ae80823 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,118 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/azure/azapi" { + version = "1.12.1" + constraints = ">= 1.6.0" + hashes = [ + "h1:EaQL7pQCRm5iL2zy/dG7rOe2OZ0ZypuyVnpQAiAwJmM=", + "zh:1cf52e685ceb04e73e13fbf3f3036bff23a3274a4ceda8693c0612076a588166", + "zh:321b59c2a67c6cb4e5cf0dbe2cc978f5389d781e8b391f9b75bf4d830abd2ffe", + "zh:49046bd8020c3b44c6b5dc67041f181e4fff45e3bc1a9ff0646dd20c21c8ce47", + "zh:5784d0c326ec4825571577bc39b253019bd3b1030c19d67ca3436df2d7ba01c8", + "zh:5ad7e18d26f170c01888d8e65dab7aa475089aac7bf0106526fd57cdd56533bc", + "zh:6695854f4f655673bea85e37444bf0c070b440dba4bc269aa144d0f6b7c1cc5f", + "zh:7f372c897da6b9ad90869a8eb85b37dad4dff2d5d311b3eca1a2e6373e2271ed", + "zh:8afa1a2be1dada4e8be4ab72d9d56f36af1e486c9353d04aabf6e79db7310125", + "zh:90809364619238c45185bff25c7d9c4fde34253561d8183ebbe797456c44bc9c", + "zh:9338d44650c9e68e10a6bc2d69f7beacd5059e6ac681d2e388e80a1652d9c183", + "zh:c94ee6fb1df2c1d35f338107b5e73cdba86c4ecf9dcde95e2ca0132cbbd4bd7c", + "zh:de231d363b1a664c6b5d3af8d3b9cf542d04d4506fb9458ba6c8ebf94e0e32ae", + ] +} + +provider "registry.terraform.io/hashicorp/azuread" { + version = "2.47.0" + constraints = ">= 2.37.1" + hashes = [ + "h1:g8+gBFM4QVOEQFqAEs5pR6iXpbGvgPvcEi1evHwziyw=", + "zh:1372d81eb24ef3b4b00ea350fe87219f22da51691b8e42ce91d662f6c2a8af5e", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:1e654a74d171d6ff8f9f6f67e3ff1421d4c5e56a18607703626bf12cd23ba001", + "zh:35227fad617a0509c64ab5759a8b703b10d244877f1aa5416bfbcc100c96996f", + "zh:357f553f0d78d46a96c7b2ed06d25ee0fc60fc5be19812ccb5d969fa47d62e17", + "zh:58faa2940065137e3e87d02eba59ab5cd7137d7a18caf225e660d1788f274569", + "zh:7308eda0339620fa24f47cedd22221fc2c02cab9d5be1710c09a783aea84eb3a", + "zh:863eabf7f908a8263e28d8aa2ad1381affd6bb5c67755216781f674ef214100e", + "zh:8b95b595a7c14ed7b56194d03cdec253527e7a146c1c58961be09e6b5c50baee", + "zh:afbca6b4fac9a0a488bc22ff9e51a8f14e986137d25275068fd932f379a51d57", + "zh:c6aadec4c81a44c3ffc22c2d90ffc6706bf5a9a903a395d896477516f4be6cbb", + "zh:e54a59de7d4ef0f3a18f91fed0b54a2bce18257ae2ee1df8a88226e1023c5811", + ] +} + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.96.0" + constraints = ">= 3.52.0, >= 3.67.0, >= 3.76.0" + hashes = [ + "h1:o1BGLLHL33WaMjlUYSCr6zo7nuw4mKrpcLee14fSLc0=", + "zh:2fb3f3c309bc8b040cd63f3a5711d4a6fc107e653a760063ec3ee6417912d14d", + "zh:45b83f492bd371c837df6d68e96ee3ab89faa00f740bca915187b344fd795ae3", + "zh:4a8b9f31da14ae824b2358fe772bb03ee79283d3294985f2acb48a0d4cd950bb", + "zh:4ab3c38b6141a0bd52d9216383d256771c0bfdc1869dccf52f414ed04290ed35", + "zh:6772d182dde23ff3fe10497f104a866cfc1cb848988f830100247363f9dd9ef7", + "zh:85875de128bc2d119c63f16116773594345ad5d0e8a3b464f7612479900df640", + "zh:9cd696005f4cfab4662d7db81039a64fc4c66d6eeedddf0808f2e97bc8af25f4", + "zh:bdc8921161253d3bff8f951cbf63f73f856bbda0ee2e9f51af60d74464059d21", + "zh:d7320767f7cde3796906f453a99ba80284fe8479ce127a4703ecf45dd9ef1321", + "zh:e0c28b79c0bf5004a9d094a68ec0c887c7df307f2cedeed2cbbef567c61443c6", + "zh:f069aa8e951508ea812cb8fef73f79594212864014eb85db39cdea2c648f69ee", + "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", + ] +} + +provider "registry.terraform.io/statuscakedev/statuscake" { + version = "2.2.2" + constraints = ">= 2.1.0" + hashes = [ + "h1:+BzbpPyJvjkn34pRYswz7K/a1v9LqcaH/tXeVlU0oH0=", + "h1:2y6sc3WpkhqovLq2zZh994HPQYfnX7cmHOH3NKb8EqA=", + "h1:GoqF8hHJHM2EceT1oCZRvGX0xkmwYrgEmcGvYWrKyaQ=", + "h1:OoqL/K/eNLahbfMwJvYZHo9kacafjtrJKhd6cLrubZ4=", + "h1:PFVKpIeHsABv8YGW6tRcs/sDTIpCI5+cfc/fx+daymU=", + "h1:QBfk+RVQhzJM0eQDzCDF82baabL23bVN23YuXJ+x/hI=", + "h1:SJiQusSTE2fC3lPNbtW0aExYwSQAZTe920yzux2iwkY=", + "h1:WU3zNJtKJmskgNhHnJzjLS0UnrlO//MJQ6G3EbKcWVA=", + "h1:eZCJ+mvkgqzB6JobTWLyZmN6hCjLcHIddbyUpZnBCwA=", + "h1:latCWLUpxz+tlByrUhC5SpekEJSJo7SpEEfFq+cj1Ug=", + "h1:nVaJkDBk4sv0yWFzg3p+yeJGzE8mB4KJv3Q6/UgU164=", + "h1:phIY76XC5ij8dZUSHJqU4Aj/vq17wq3rQFZ9guLtVF4=", + "h1:v9W0xSeWF2bz3b3txV0s43el5DCxCZGZO13hiVBkz9c=", + "h1:wFoZJfmNvG6XTf65NLai67geSHqYV1Tilx7OITrHilE=", + "zh:0916313344c579d6e05d70f88129a10fe48f7dabe0e61cad17874d6c496f288d", + "zh:0d491ff72c2eda6482855033ca2146c5ace1663d07cb3da7253b59ed2e2ec6f4", + "zh:11fffbce18eb3d3c283e877242f477e0c561342c19090240b60af7d948bd84ac", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:1c6116092c59bc0010e147dc7832ae981d528f235cef563e5ae05a93ef8bac5c", + "zh:1f13a543b1d32cc1f1e3d2ed5ca83445f088787c335690fe20dee1203488a8bc", + "zh:23f55fd0714696c3863f892646a79a780cea1923c0c4d2b1064df735ba4156f5", + "zh:288fbb4431ac12014aa5aab10ee7166dfe71fd1158464e06e8a527aa4919e64e", + "zh:2a0746d3c8cdc7d0875df0f4605d81130e6da0ce6196d0f5f8661fe9191bc2ab", + "zh:31d660756abc53d252906fe1239fad58ecb6c1d0dbb087408a8af266be1f9ae4", + "zh:6765281d1b7efb41085c5375660b6c6b271babc5d09b2c030bec7176f7a7bfb6", + "zh:6d7204eadb667c1f2cab762a7a97234cf47452f0ecad680f5106c8fd02524c87", + "zh:b70a97b0eba471d683e23ce8744a5c67ef9952086e2d6f5825b72f32b6caff89", + "zh:df89ee4aaba88faea33d33384a36ea52588b5514644d335f724378a682d8d9da", + "zh:ffe2e1e7224ea5f4dd65b9eef45464e3124cfc824473b04e7af429f9177f2375", + ] +} diff --git a/terraform/Brewfile b/terraform/Brewfile new file mode 100644 index 0000000..000ace3 --- /dev/null +++ b/terraform/Brewfile @@ -0,0 +1,6 @@ +brew "tfenv" +brew "terraform-docs" +brew "tfsec" +brew "az" +brew "coreutils" +brew "jq" diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..3a2e8f7 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,196 @@ +This documentation covers the deployment of the infrastructure to host the app. + +## Azure infrastructure + +The infrastructure is managed using [Terraform](https://www.terraform.io/).
+The state is stored remotely in encrypted Azure storage.
+[Terraform workspaces](https://www.terraform.io/docs/state/workspaces.html) are used to separate environments. + +#### Configuring the storage backend + +The Terraform state is stored remotely in Azure, this allows multiple team members to +make changes and means the state file is backed up. The state file contains +sensitive information so access to it should be restricted, and it should be stored +encrypted at rest. + +##### Create a new storage backend + +This step only needs to be done once per project (eg. not per environment). +If it has already been created, obtain the storage backend attributes and skip to the next step. + +The [Azure tutorial](https://docs.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage) outlines the steps to create a storage account and container for the state file. You will need: + +- resource_group_name: The name of the resource group used for the Azure Storage account. +- storage_account_name: The name of the Azure Storage account. +- container_name: The name of the blob container. +- key: The name of the state store file to be created. + +##### Create a backend configuration file + +Create a new file named `backend.vars` with the following content: + +``` +resource_group_name = [the name of the Azure resource group] +storage_account_name = [the name of the Azure Storage account] +container_name = [the name of the blob container] +key = "terraform.tstate" +``` + +##### Install dependencies + +We can use [Homebrew](https://brew.sh) to install the dependecies we need to deploy the infrastructure (eg. tfenv, Azure cli). +These are listed in the `Brewfile` + +to install, run: + +``` +$ brew bundle +``` + +##### Log into azure with the Azure CLI + +Log in to your account: + +``` +$ az login +``` + +Confirm which account you are currently using: + +``` +$ az account show +``` + +To list the available subscriptions, run: + +``` +$ az account list +``` + +Then if needed, switch to it using the 'id': + +``` +$ az account set --subscription +``` + +##### Initialise Terraform + +Install the required terraform version with the Terraform version manager `tfenv`: + +``` +$ tfenv install +``` + +Initialize Terraform to download the required Terraform modules and configure the remote state backend +to use the settings you specified in the previous step. + +`$ terraform init -backend-config=backend.vars` + +##### Create a Terraform variables file + +Each environment will need it's own `tfvars` file. + +Copy the `terraform.tfvars.example` to `environment-name.tfvars` and modify the contents as required + +##### Create the infrastructure + +Now Terraform has been initialised you can create a workspace if needed: + +`$ terraform workspace new staging` + +Or to check what workspaces already exist: + +`$ terraform workspace list` + +Switch to the new or existing workspace: + +`$ terraform workspace select staging` + +Plan the changes: + +`$ terraform plan -var-file=staging.tfvars` + +Terraform will ask you to provide any variables not specified in an `*.auto.tfvars` file. +Now you can run: + +`$ terraform apply -var-file=staging.tfvars` + +If everything looks good, answer `yes` and wait for the new infrastructure to be created. + +##### Azure resources + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [azurerm](#requirement\_azurerm) | >= 3.67.0 | +| [statuscake](#requirement\_statuscake) | >= 2.1.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [azure\_container\_apps\_hosting](#module\_azure\_container\_apps\_hosting) | github.com/DFE-Digital/terraform-azurerm-container-apps-hosting | v1.5.0 | +| [statuscake-tls-monitor](#module\_statuscake-tls-monitor) | github.com/dfe-digital/terraform-statuscake-tls-monitor | v0.1.3 | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [azure\_location](#input\_azure\_location) | Azure location in which to launch resources. | `string` | n/a | yes | +| [cdn\_frontdoor\_custom\_domains](#input\_cdn\_frontdoor\_custom\_domains) | Azure CDN Front Door custom domains. If they are within the DNS zone (optionally created), the Validation TXT records and ALIAS/CNAME records will be created | `list(string)` | n/a | yes | +| [cdn\_frontdoor\_enable\_rate\_limiting](#input\_cdn\_frontdoor\_enable\_rate\_limiting) | Enable CDN Front Door Rate Limiting. This will create a WAF policy, and CDN security policy. For pricing reasons, there will only be one WAF policy created. | `bool` | n/a | yes | +| [cdn\_frontdoor\_forwarding\_protocol](#input\_cdn\_frontdoor\_forwarding\_protocol) | Azure CDN Front Door forwarding protocol | `string` | `"HttpOnly"` | no | +| [cdn\_frontdoor\_health\_probe\_path](#input\_cdn\_frontdoor\_health\_probe\_path) | Specifies the path relative to the origin that is used to determine the health of the origin. | `string` | `"/"` | no | +| [cdn\_frontdoor\_health\_probe\_protocol](#input\_cdn\_frontdoor\_health\_probe\_protocol) | Use Http or Https | `string` | `"Http"` | no | +| [cdn\_frontdoor\_host\_add\_response\_headers](#input\_cdn\_frontdoor\_host\_add\_response\_headers) | List of response headers to add at the CDN Front Door `[{ "name" = "Strict-Transport-Security", "value" = "max-age=31536000" }]` | `list(map(string))` | n/a | yes | +| [cdn\_frontdoor\_origin\_fqdn\_override](#input\_cdn\_frontdoor\_origin\_fqdn\_override) | Manually specify the hostname that the CDN Front Door should target. Defaults to the Container App FQDN | `string` | `""` | no | +| [cdn\_frontdoor\_origin\_host\_header\_override](#input\_cdn\_frontdoor\_origin\_host\_header\_override) | Manually specify the host header that the CDN sends to the target. Defaults to the recieved host header. Set to null to set it to the host\_name (`cdn_frontdoor_origin_fqdn_override`) | `string` | `""` | no | +| [container\_apps\_allow\_ips\_inbound](#input\_container\_apps\_allow\_ips\_inbound) | Restricts access to the Container Apps by creating a network security group rule that only allow inbound traffic from the provided list of IPs | `list(string)` | `[]` | no | +| [container\_command](#input\_container\_command) | Container command | `list(any)` | n/a | yes | +| [container\_health\_probe\_path](#input\_container\_health\_probe\_path) | Specifies the path that is used to determine the liveness of the Container | `string` | `"/"` | no | +| [container\_health\_probe\_protocol](#input\_container\_health\_probe\_protocol) | Use HTTPS or a TCP connection for the Container liveness probe | `string` | `"tcp"` | no | +| [container\_scale\_http\_concurrency](#input\_container\_scale\_http\_concurrency) | When the number of concurrent HTTP requests exceeds this value, then another replica is added. Replicas continue to add to the pool up to the max-replicas amount. | `number` | `10` | no | +| [container\_secret\_environment\_variables](#input\_container\_secret\_environment\_variables) | Container secret environment variables | `map(string)` | n/a | yes | +| [dns\_ns\_records](#input\_dns\_ns\_records) | DNS NS records to add to the DNS Zone |
map(
object({
ttl : optional(number, 300),
records : list(string)
})
)
| n/a | yes | +| [dns\_txt\_records](#input\_dns\_txt\_records) | DNS TXT records to add to the DNS Zone |
map(
object({
ttl : optional(number, 300),
records : list(string)
})
)
| n/a | yes | +| [dns\_zone\_domain\_name](#input\_dns\_zone\_domain\_name) | DNS zone domain name. If created, records will automatically be created to point to the CDN. | `string` | n/a | yes | +| [enable\_cdn\_frontdoor](#input\_enable\_cdn\_frontdoor) | Enable Azure CDN FrontDoor. This will use the Container Apps endpoint as the origin. | `bool` | n/a | yes | +| [enable\_container\_health\_probe](#input\_enable\_container\_health\_probe) | Enable liveness probes for the Container | `bool` | `true` | no | +| [enable\_container\_registry](#input\_enable\_container\_registry) | Set to true to create a container registry | `bool` | n/a | yes | +| [enable\_dns\_zone](#input\_enable\_dns\_zone) | Conditionally create a DNS zone | `bool` | n/a | yes | +| [enable\_monitoring](#input\_enable\_monitoring) | Create an App Insights instance and notification group for the Container App | `bool` | n/a | yes | +| [environment](#input\_environment) | Environment name. Will be used along with `project_name` as a prefix for all resources. | `string` | n/a | yes | +| [existing\_logic\_app\_workflow](#input\_existing\_logic\_app\_workflow) | Name, and Resource Group of an existing Logic App Workflow. Leave empty to create a new Resource |
object({
name : string
resource_group_name : string
})
|
{
"name": "",
"resource_group_name": ""
}
| no | +| [existing\_network\_watcher\_name](#input\_existing\_network\_watcher\_name) | Use an existing network watcher to add flow logs. | `string` | n/a | yes | +| [existing\_network\_watcher\_resource\_group\_name](#input\_existing\_network\_watcher\_resource\_group\_name) | Existing network watcher resource group. | `string` | n/a | yes | +| [image\_name](#input\_image\_name) | Image name | `string` | n/a | yes | +| [image\_tag](#input\_image\_tag) | Default image tag for the primary container | `string` | `"web-latest"` | no | +| [monitor\_email\_receivers](#input\_monitor\_email\_receivers) | A list of email addresses that should be notified by monitoring alerts | `list(string)` | n/a | yes | +| [monitor\_endpoint\_healthcheck](#input\_monitor\_endpoint\_healthcheck) | Specify a route that should be monitored for a 200 OK status | `string` | n/a | yes | +| [project\_name](#input\_project\_name) | Project name. Will be used along with `environment` as a prefix for all resources. | `string` | n/a | yes | +| [registry\_admin\_enabled](#input\_registry\_admin\_enabled) | Do you want to enable access key based authentication for your Container Registry? | `bool` | `true` | no | +| [registry\_managed\_identity\_assign\_role](#input\_registry\_managed\_identity\_assign\_role) | Assign the 'AcrPull' Role to the Container App User-Assigned Managed Identity. Note: If you do not have 'Microsoft.Authorization/roleAssignments/write' permission, you will need to manually assign the 'AcrPull' Role to the identity | `bool` | `false` | no | +| [registry\_use\_managed\_identity](#input\_registry\_use\_managed\_identity) | Create a User-Assigned Managed Identity for the Container App. Note: If you do not have 'Microsoft.Authorization/roleAssignments/write' permission, you will need to manually assign the 'AcrPull' Role to the identity | `bool` | `true` | no | +| [statuscake\_api\_token](#input\_statuscake\_api\_token) | API token for StatusCake | `string` | `"00000000000000000000000000000"` | no | +| [statuscake\_contact\_group\_email\_addresses](#input\_statuscake\_contact\_group\_email\_addresses) | List of email address that should receive notifications from StatusCake | `list(string)` | `[]` | no | +| [statuscake\_contact\_group\_integrations](#input\_statuscake\_contact\_group\_integrations) | List of Integration IDs to connect to your Contact Group | `list(string)` | `[]` | no | +| [statuscake\_contact\_group\_name](#input\_statuscake\_contact\_group\_name) | Name of the contact group in StatusCake | `string` | `""` | no | +| [statuscake\_monitored\_resource\_addresses](#input\_statuscake\_monitored\_resource\_addresses) | The URLs to perform TLS checks on | `list(string)` | `[]` | no | +| [tags](#input\_tags) | Tags to be applied to all resources | `map(string)` | n/a | yes | +| [virtual\_network\_address\_space](#input\_virtual\_network\_address\_space) | Virtual network address space CIDR | `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..bc23821 --- /dev/null +++ b/terraform/backend.vars.example @@ -0,0 +1,5 @@ +resource_group_name = "" +storage_account_name = "" +container_name = "" +key = "terraform.tstate" +subscription_id = "" diff --git a/terraform/container-apps-hosting.tf b/terraform/container-apps-hosting.tf new file mode 100644 index 0000000..dd00caf --- /dev/null +++ b/terraform/container-apps-hosting.tf @@ -0,0 +1,48 @@ +module "azure_container_apps_hosting" { + source = "github.com/DFE-Digital/terraform-azurerm-container-apps-hosting?ref=v1.5.0" + + environment = local.environment + project_name = local.project_name + azure_location = local.azure_location + tags = local.tags + + virtual_network_address_space = local.virtual_network_address_space + + enable_container_registry = local.enable_container_registry + registry_admin_enabled = local.registry_admin_enabled + registry_use_managed_identity = local.registry_use_managed_identity + registry_managed_identity_assign_role = local.registry_managed_identity_assign_role + + image_name = local.image_name + image_tag = local.image_tag + container_command = local.container_command + container_secret_environment_variables = local.container_secret_environment_variables + container_scale_http_concurrency = local.container_scale_http_concurrency + container_health_probe_protocol = local.container_health_probe_protocol + + enable_dns_zone = local.enable_dns_zone + dns_zone_domain_name = local.dns_zone_domain_name + dns_ns_records = local.dns_ns_records + dns_txt_records = local.dns_txt_records + + enable_cdn_frontdoor = local.enable_cdn_frontdoor + cdn_frontdoor_forwarding_protocol = local.cdn_frontdoor_forwarding_protocol + cdn_frontdoor_enable_rate_limiting = local.cdn_frontdoor_enable_rate_limiting + cdn_frontdoor_host_add_response_headers = local.cdn_frontdoor_host_add_response_headers + cdn_frontdoor_custom_domains = local.cdn_frontdoor_custom_domains + cdn_frontdoor_origin_fqdn_override = local.cdn_frontdoor_origin_fqdn_override + cdn_frontdoor_origin_host_header_override = local.cdn_frontdoor_origin_host_header_override + container_apps_allow_ips_inbound = local.container_apps_allow_ips_inbound + + enable_monitoring = local.enable_monitoring + monitor_email_receivers = local.monitor_email_receivers + container_health_probe_path = local.container_health_probe_path + cdn_frontdoor_health_probe_path = local.cdn_frontdoor_health_probe_path + monitor_endpoint_healthcheck = local.monitor_endpoint_healthcheck + enable_container_health_probe = local.enable_container_health_probe + cdn_frontdoor_health_probe_protocol = local.cdn_frontdoor_health_probe_protocol + + existing_logic_app_workflow = local.existing_logic_app_workflow + existing_network_watcher_name = local.existing_network_watcher_name + existing_network_watcher_resource_group_name = local.existing_network_watcher_resource_group_name +} diff --git a/terraform/key-vault-tfvars-secrets.tf b/terraform/key-vault-tfvars-secrets.tf new file mode 100644 index 0000000..019415f --- /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 = local.project_name +# existing_resource_group = module.azure_container_apps_hosting.azurerm_resource_group_default.name +# azure_location = local.azure_location +# 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 +# diagnostic_log_analytics_workspace_id = module.azure_container_apps_hosting.azurerm_log_analytics_workspace_container_app.id +# diagnostic_eventhub_name = "" +# tags = local.tags +# } diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..98033fd --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,45 @@ +locals { + environment = var.environment + project_name = var.project_name + azure_location = var.azure_location + tags = var.tags + virtual_network_address_space = var.virtual_network_address_space + enable_container_registry = var.enable_container_registry + registry_admin_enabled = var.registry_admin_enabled + registry_use_managed_identity = var.registry_use_managed_identity + registry_managed_identity_assign_role = var.registry_managed_identity_assign_role + image_name = var.image_name + image_tag = var.image_tag + container_command = var.container_command + container_secret_environment_variables = var.container_secret_environment_variables + container_scale_http_concurrency = var.container_scale_http_concurrency + container_health_probe_protocol = var.container_health_probe_protocol + enable_dns_zone = var.enable_dns_zone + dns_zone_domain_name = var.dns_zone_domain_name + dns_ns_records = var.dns_ns_records + dns_txt_records = var.dns_txt_records + enable_cdn_frontdoor = var.enable_cdn_frontdoor + container_apps_allow_ips_inbound = var.container_apps_allow_ips_inbound + cdn_frontdoor_enable_rate_limiting = var.cdn_frontdoor_enable_rate_limiting + cdn_frontdoor_host_add_response_headers = var.cdn_frontdoor_host_add_response_headers + cdn_frontdoor_custom_domains = var.cdn_frontdoor_custom_domains + cdn_frontdoor_origin_fqdn_override = var.cdn_frontdoor_origin_fqdn_override + cdn_frontdoor_origin_host_header_override = var.cdn_frontdoor_origin_host_header_override + cdn_frontdoor_forwarding_protocol = var.cdn_frontdoor_forwarding_protocol + # key_vault_access_ipv4 = var.key_vault_access_ipv4 + # tfvars_filename = var.tfvars_filename + enable_monitoring = var.enable_monitoring + monitor_email_receivers = var.monitor_email_receivers + enable_container_health_probe = var.enable_container_health_probe + container_health_probe_path = var.container_health_probe_path + cdn_frontdoor_health_probe_path = var.cdn_frontdoor_health_probe_path + cdn_frontdoor_health_probe_protocol = var.cdn_frontdoor_health_probe_protocol + monitor_endpoint_healthcheck = var.monitor_endpoint_healthcheck + existing_logic_app_workflow = var.existing_logic_app_workflow + existing_network_watcher_name = var.existing_network_watcher_name + existing_network_watcher_resource_group_name = var.existing_network_watcher_resource_group_name + statuscake_monitored_resource_addresses = var.statuscake_monitored_resource_addresses + statuscake_contact_group_name = var.statuscake_contact_group_name + statuscake_contact_group_integrations = var.statuscake_contact_group_integrations + statuscake_contact_group_email_addresses = var.statuscake_contact_group_email_addresses +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..c06a45c --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,8 @@ +provider "azurerm" { + features {} + skip_provider_registration = true +} + +provider "statuscake" { + api_token = var.statuscake_api_token +} diff --git a/terraform/statuscake-tls-monitor.tf b/terraform/statuscake-tls-monitor.tf new file mode 100644 index 0000000..5799330 --- /dev/null +++ b/terraform/statuscake-tls-monitor.tf @@ -0,0 +1,11 @@ +module "statuscake-tls-monitor" { + source = "github.com/dfe-digital/terraform-statuscake-tls-monitor?ref=v0.1.3" + + statuscake_monitored_resource_addresses = local.statuscake_monitored_resource_addresses + statuscake_alert_at = [ # days to alert on + 40, 20, 5 + ] + statuscake_contact_group_name = local.statuscake_contact_group_name + statuscake_contact_group_integrations = local.statuscake_contact_group_integrations + statuscake_contact_group_email_addresses = local.statuscake_contact_group_email_addresses +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..4714926 --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,15 @@ +environment = "development" +project_name = "myproject" +azure_location = "uksouth" +enable_container_registry = true +image_name = "myimage" +enable_mssql_database = true +mssql_server_admin_password = "S3crEt" +mssql_database_name = "mydatabase" +container_command = ["/bin/bash", "-c", "echo hello && sleep 86400"] +container_environment_variables = { + "ASPNETCORE_ENVIRONMENT" = "production" +} +key_vault_access_users = [ + "someone_example.com#EXT#@tenantname.onmicrosoft.com", +] diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..0920312 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,258 @@ +variable "environment" { + description = "Environment name. Will be used along with `project_name` as a prefix for all resources." + 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 ensures that tfvars are kept up to date in Key Vault." +# type = string +# } + +variable "project_name" { + description = "Project name. Will be used along with `environment` as a prefix for all resources." + type = string +} + +variable "azure_location" { + description = "Azure location in which to launch resources." + type = string +} + +variable "tags" { + description = "Tags to be applied to all resources" + type = map(string) +} + +variable "virtual_network_address_space" { + description = "Virtual network address space CIDR" + type = string +} + +variable "enable_container_registry" { + description = "Set to true to create a container registry" + type = bool +} + +variable "image_name" { + description = "Image name" + type = string +} + +variable "registry_admin_enabled" { + description = "Do you want to enable access key based authentication for your Container Registry?" + type = bool + default = true +} + +variable "registry_use_managed_identity" { + description = "Create a User-Assigned Managed Identity for the Container App. Note: If you do not have 'Microsoft.Authorization/roleAssignments/write' permission, you will need to manually assign the 'AcrPull' Role to the identity" + type = bool + default = true +} + +variable "registry_managed_identity_assign_role" { + description = "Assign the 'AcrPull' Role to the Container App User-Assigned Managed Identity. Note: If you do not have 'Microsoft.Authorization/roleAssignments/write' permission, you will need to manually assign the 'AcrPull' Role to the identity" + type = bool + default = false +} + +variable "container_command" { + description = "Container command" + type = list(any) +} + +variable "container_health_probe_protocol" { + description = "Use HTTPS or a TCP connection for the Container liveness probe" + type = string + default = "tcp" +} + +variable "container_secret_environment_variables" { + description = "Container secret environment variables" + type = map(string) + sensitive = true +} + +variable "container_scale_http_concurrency" { + description = "When the number of concurrent HTTP requests exceeds this value, then another replica is added. Replicas continue to add to the pool up to the max-replicas amount." + type = number + default = 10 +} + +variable "enable_cdn_frontdoor" { + description = "Enable Azure CDN FrontDoor. This will use the Container Apps endpoint as the origin." + type = bool +} + +variable "cdn_frontdoor_origin_fqdn_override" { + description = "Manually specify the hostname that the CDN Front Door should target. Defaults to the Container App FQDN" + type = string + default = "" +} + +variable "cdn_frontdoor_origin_host_header_override" { + description = "Manually specify the host header that the CDN sends to the target. Defaults to the recieved host header. Set to null to set it to the host_name (`cdn_frontdoor_origin_fqdn_override`)" + type = string + default = "" + nullable = true +} + +variable "cdn_frontdoor_enable_rate_limiting" { + description = "Enable CDN Front Door Rate Limiting. This will create a WAF policy, and CDN security policy. For pricing reasons, there will only be one WAF policy created." + type = bool +} + +variable "cdn_frontdoor_host_add_response_headers" { + description = "List of response headers to add at the CDN Front Door `[{ \"name\" = \"Strict-Transport-Security\", \"value\" = \"max-age=31536000\" }]`" + type = list(map(string)) +} + +variable "enable_monitoring" { + description = "Create an App Insights instance and notification group for the Container App" + type = bool +} + +variable "monitor_email_receivers" { + description = "A list of email addresses that should be notified by monitoring alerts" + type = list(string) +} + +variable "existing_logic_app_workflow" { + description = "Name, and Resource Group of an existing Logic App Workflow. Leave empty to create a new Resource" + type = object({ + name : string + resource_group_name : string + }) + default = { + name = "" + resource_group_name = "" + } +} + +variable "container_health_probe_path" { + description = "Specifies the path that is used to determine the liveness of the Container" + type = string + default = "/" +} + +variable "cdn_frontdoor_health_probe_path" { + description = "Specifies the path relative to the origin that is used to determine the health of the origin." + type = string + default = "/" +} + +variable "cdn_frontdoor_custom_domains" { + description = "Azure CDN Front Door custom domains. If they are within the DNS zone (optionally created), the Validation TXT records and ALIAS/CNAME records will be created" + type = list(string) +} + +variable "monitor_endpoint_healthcheck" { + description = "Specify a route that should be monitored for a 200 OK status" + type = string +} + +variable "existing_network_watcher_name" { + description = "Use an existing network watcher to add flow logs." + type = string +} + +variable "existing_network_watcher_resource_group_name" { + description = "Existing network watcher resource group." + type = string +} + +variable "container_apps_allow_ips_inbound" { + description = "Restricts access to the Container Apps by creating a network security group rule that only allow inbound traffic from the provided list of IPs" + type = list(string) + default = [] +} + +variable "enable_dns_zone" { + description = "Conditionally create a DNS zone" + type = bool +} + +variable "cdn_frontdoor_forwarding_protocol" { + description = "Azure CDN Front Door forwarding protocol" + type = string + default = "HttpOnly" +} + +variable "dns_zone_domain_name" { + description = "DNS zone domain name. If created, records will automatically be created to point to the CDN." + type = string +} + +variable "dns_ns_records" { + description = "DNS NS records to add to the DNS Zone" + type = map( + object({ + ttl : optional(number, 300), + records : list(string) + }) + ) +} + +variable "dns_txt_records" { + description = "DNS TXT records to add to the DNS Zone" + type = map( + object({ + ttl : optional(number, 300), + records : list(string) + }) + ) +} + +variable "enable_container_health_probe" { + description = "Enable liveness probes for the Container" + type = bool + default = true +} + +variable "cdn_frontdoor_health_probe_protocol" { + description = "Use Http or Https" + type = string + default = "Http" +} + +variable "image_tag" { + description = "Default image tag for the primary container" + type = string + default = "web-latest" +} + +variable "statuscake_api_token" { + description = "API token for StatusCake" + type = string + sensitive = true + default = "00000000000000000000000000000" +} + +variable "statuscake_contact_group_name" { + description = "Name of the contact group in StatusCake" + type = string + default = "" +} + +variable "statuscake_contact_group_integrations" { + description = "List of Integration IDs to connect to your Contact Group" + type = list(string) + default = [] +} + +variable "statuscake_monitored_resource_addresses" { + description = "The URLs to perform TLS checks on" + type = list(string) + default = [] +} + +variable "statuscake_contact_group_email_addresses" { + description = "List of email address that should receive notifications from StatusCake" + type = list(string) + default = [] +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000..d098126 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.5.7" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.67.0" + } + statuscake = { + source = "StatusCakeDev/statuscake" + version = ">= 2.1.0" + } + } +}