diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl index d57d40b..efa3833 100644 --- a/.terraform.lock.hcl +++ b/.terraform.lock.hcl @@ -45,18 +45,36 @@ provider "registry.terraform.io/hashicorp/azurerm" { version = "3.90.0" constraints = ">= 3.51.0" hashes = [ - "h1:TpB4R7pYGxbXHC7wI7pZmhxZU/8O0JokORGlBfIpoRw=", - "zh:194a4342620958403beabf4d57d552133ca6ac18eef3027d6d1a98846b52f8ab", - "zh:1d8ee378aaa793e3288c9328e056763c98d0f2e8560357296bc3446fbd3b1b9d", - "zh:24aba7903e912570e36edb03f79c68028d3e254175947b588c96521f09f89df4", - "zh:27f91fbeef9d04c6382014b6c32883a96dbe91cf7a4fa07a97be5d6b03991f95", - "zh:59eeaa2f50f698bab6f36ada0e865d6b624625ff5d76309334b3c3aa366cb692", - "zh:732af42d18fa222ee88f7f97c0898d4955ae48fde5456e22af3b8f5d324c6b41", - "zh:766034eac5e6a66cf3631580956dd584b1c2e6134167302fc8b95d6b42ebf08b", - "zh:a5b2ec52abfc3fb154047af45ea692c98c646c2b5c336b12b6341a49be95025c", - "zh:bdd72f85d770fa4a2e6ebf542858341d3df7e858a4d70c0f94df758721bcd811", - "zh:e9f15f2399c667c24b3daf8a843f1cadd13bc619becf6362b46c3216b17009b1", + "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", "zh:f73b1ec8b372bc1480ca0d93e78914f1c9cebe81395f20273d7bc99579b84809", + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.2" + 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/README.md b/README.md index 139d860..6605704 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,14 @@ key = "terraform.tstate" |------|---------| | [terraform](#requirement\_terraform) | >= 1.6.1 | | [azurerm](#requirement\_azurerm) | >= 3.51.0 | +| [null](#requirement\_null) | 3.2.2 | ## Providers | Name | Version | |------|---------| | [azurerm](#provider\_azurerm) | 3.90.0 | +| [null](#provider\_null) | 3.2.2 | ## Modules @@ -63,6 +65,11 @@ key = "terraform.tstate" | Name | Type | |------|------| +| [azurerm_storage_account.tfvars](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource | +| [azurerm_storage_account_network_rules.tfvars](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account_network_rules) | resource | +| [azurerm_storage_blob.tfvars](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_blob) | resource | +| [azurerm_storage_container.tfvars](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_container) | resource | +| [null_resource.tfvars](https://registry.terraform.io/providers/hashicorp/null/3.2.2/docs/resources/resource) | resource | | [azurerm_container_app.container_apps](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/container_app) | data source | | [azurerm_linux_web_app.web_apps](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/linux_web_app) | data source | | [azurerm_resource_group.container_apps](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | @@ -88,6 +95,8 @@ key = "terraform.tstate" | [project\_name](#input\_project\_name) | Project name. Will be used along with `environment` as a prefix for all resources. | `string` | n/a | yes | | [response\_request\_timeout](#input\_response\_request\_timeout) | Azure CDN Front Door response or App Gateway V2 request timeout in seconds | `number` | n/a | yes | | [tags](#input\_tags) | Tags to be applied to all resources | `map(string)` | n/a | yes | +| [tfvars\_access\_ipv4](#input\_tfvars\_access\_ipv4) | List of IPv4 Addresses that are permitted to access the tfvars Storage Account | `list(string)` | `[]` | no | +| [tfvars\_filename](#input\_tfvars\_filename) | Name of the TF Vars file | `string` | `"terraform.tfvars"` | no | | [waf\_application](#input\_waf\_application) | Which product to apply the WAF to. Must be either CDN or AppGatewayV2 | `string` | n/a | yes | | [waf\_custom\_rules](#input\_waf\_custom\_rules) | Map of all Custom rules you want to apply to the WAF |
map(object({
priority : number,
action : string
match_conditions : map(object({
match_variable : string,
match_values : optional(list(string), []),
operator : optional(string, "Any"),
selector : optional(string, ""),
}))
}))
| `{}` | no | | [waf\_mode](#input\_waf\_mode) | WAF mode | `string` | n/a | yes | diff --git a/locals.tf b/locals.tf index 1b0bb06..d273c4c 100644 --- a/locals.tf +++ b/locals.tf @@ -1,12 +1,16 @@ locals { - environment = var.environment - project_name = var.project_name - azure_location = var.azure_location + environment = var.environment + project_name = var.project_name + azure_location = var.azure_location + resource_prefix = "${local.environment}${local.project_name}" key_vault_app_gateway_certificates_access_users = var.key_vault_app_gateway_certificates_access_users key_vault_app_gateway_certificates_access_ipv4 = var.key_vault_app_gateway_certificates_access_ipv4 key_vault_app_gateway_certificates_access_subnet_ids = var.key_vault_app_gateway_certificates_access_subnet_ids + tfvars_filename = var.tfvars_filename + tfvars_access_ipv4 = var.tfvars_access_ipv4 + existing_logic_app_workflow = var.existing_logic_app_workflow monitor_email_receivers = var.monitor_email_receivers diff --git a/scripts/check-tfvars-against-remote.sh b/scripts/check-tfvars-against-remote.sh new file mode 100755 index 0000000..76ff68f --- /dev/null +++ b/scripts/check-tfvars-against-remote.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# exit on failures +set -e +set -o pipefail + +usage() { + echo "Usage: $(basename "$0") [OPTIONS]" 1>&2 + echo " -h - help" + echo " -a - Azure Storage Account name" + echo " -c - Azure Storage Container name" + echo " -f - Name of the tfvars file with file extension" + exit 1 +} + +# if there are not arguments passed exit with usage +if [ $# -eq 0 ] +then + usage +fi + +while getopts "a:c:f:h" opt; do + case $opt in + a) + STORAGE_ACCOUNT_NAME=$OPTARG + ;; + c) + STORAGE_CONTAINER_NAME=$OPTARG + ;; + f) + TFVARS_FILE_NAME=$OPTARG + ;; + h) + usage + ;; + *) + usage + ;; + esac +done + +if [[ + -z "$STORAGE_ACCOUNT_NAME" || + -z "$STORAGE_CONTAINER_NAME" || + -z "$TFVARS_FILE_NAME" +]] +then + usage +fi + +set +e +STORAGE_CHECK=$(az storage blob list --account-name "$STORAGE_ACCOUNT_NAME" --container-name "$STORAGE_CONTAINER_NAME" 2>&1) +set -e + +if ! jq -r >/dev/null 2>&1 <<< "$STORAGE_CHECK" +then + exit 0 +fi + +LAST_UPDATED=$(jq -r \ + --arg name "$TFVARS_FILE_NAME" \ + '.[] | select(.name==$name) | .properties.lastModified' \ + <<< "$STORAGE_CHECK") + +if [ -z "$LAST_UPDATED" ] +then + exit 0 +fi + +LAST_UPDATED=$(echo "$LAST_UPDATED" | cut -d'+' -f1) +LAST_UPDATED_SECONDS=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$LAST_UPDATED" "+%s") + +if [ "$LAST_UPDATED_SECONDS" -gt "$(date -r "$TFVARS_FILE_NAME" +%s)" ] +then + echo "" + echo "" + echo "Error: Your local tfvars file is older than the remote!" + echo "" + echo "Ensure you have the latest tfvars by running:" + echo "" + echo " mv $TFVARS_FILE_NAME $TFVARS_FILE_NAME.old" + echo " az storage blob download \\" + echo " --file $TFVARS_FILE_NAME \\" + echo " --container-name $STORAGE_CONTAINER_NAME \\" + echo " --account-name $STORAGE_ACCOUNT_NAME \\" + echo " --name $TFVARS_FILE_NAME" + echo "" + echo "Or if you are sure your local tfvars are correct, just update the modified time by running:" + echo "" + echo " touch $TFVARS_FILE_NAME" + echo "" + exit 1 +fi diff --git a/tfvars-storage.tf b/tfvars-storage.tf new file mode 100644 index 0000000..a238f10 --- /dev/null +++ b/tfvars-storage.tf @@ -0,0 +1,47 @@ +resource "azurerm_storage_account" "tfvars" { + name = "${replace(local.resource_prefix, "-", "")}tfvars" + resource_group_name = local.resource_prefix + location = local.azure_location + account_tier = "Standard" + account_replication_type = "LRS" + min_tls_version = "TLS1_2" + enable_https_traffic_only = true + public_network_access_enabled = true + + tags = local.tags +} + +resource "azurerm_storage_container" "tfvars" { + name = "${local.resource_prefix}-tfvars" + storage_account_name = azurerm_storage_account.tfvars.name + container_access_type = "private" +} + +resource "azurerm_storage_blob" "tfvars" { + name = local.tfvars_filename + storage_account_name = azurerm_storage_account.tfvars.name + storage_container_name = azurerm_storage_container.tfvars.name + type = "Block" + source = local.tfvars_filename + content_md5 = filemd5(local.tfvars_filename) + access_tier = "Cool" +} + +resource "azurerm_storage_account_network_rules" "tfvars" { + storage_account_id = azurerm_storage_account.tfvars.id + default_action = length(local.tfvars_access_ipv4) > 0 ? "Deny" : "Allow" + bypass = [] + virtual_network_subnet_ids = [] + ip_rules = local.tfvars_access_ipv4 +} + +resource "null_resource" "tfvars" { + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + command = "./scripts/check-tfvars-against-remote.sh -c \"${azurerm_storage_container.tfvars.name}\" -a \"${azurerm_storage_account.tfvars.name}\" -f \"${local.tfvars_filename}\"" + } + + triggers = { + tfvar_file_md5 = filemd5(local.tfvars_filename) + } +} diff --git a/variables.tf b/variables.tf index 525513c..b73c185 100644 --- a/variables.tf +++ b/variables.tf @@ -186,3 +186,15 @@ variable "monitor_email_receivers" { type = list(string) default = [] } + +variable "tfvars_filename" { + description = "Name of the TF Vars file" + default = "terraform.tfvars" + type = string +} + +variable "tfvars_access_ipv4" { + description = "List of IPv4 Addresses that are permitted to access the tfvars Storage Account" + default = [] + type = list(string) +} diff --git a/versions.tf b/versions.tf index 8fb23f7..843f7c1 100644 --- a/versions.tf +++ b/versions.tf @@ -5,5 +5,9 @@ terraform { source = "hashicorp/azurerm" version = ">= 3.51.0" } + null = { + source = "hashicorp/null" + version = "3.2.2" + } } }