Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_storage_account recurring drift network_rules #27252

Closed
1 task done
0Burg0 opened this issue Aug 30, 2024 · 1 comment
Closed
1 task done

azurerm_storage_account recurring drift network_rules #27252

0Burg0 opened this issue Aug 30, 2024 · 1 comment

Comments

@0Burg0
Copy link

0Burg0 commented Aug 30, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave comments along the lines of "+1", "me too" or "any updates", they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment and review the contribution guide to help.

Terraform Version

1.5.4

AzureRM Provider Version

4.0.1

Affected Resource(s)/Data Source(s)

azurerm_storage_account

Terraform Configuration Files

main.tf
--------------------------------------------------------------------


resource "azurerm_storage_account" "storeacc" {
  lifecycle {
    ignore_changes = [azure_files_authentication, blob_properties[0].restore_policy["days"]]
  }

  name                             = substr(format("sta%s%s", lower(replace(var.storage_account_name, "/[[:^alnum:]]/", "")), random_string.unique.result), 0, 24)
  resource_group_name              = local.resource_group_name
  location                         = var.location
  account_kind                     = var.account_kind
  account_tier                     = var.account_tier
  access_tier                      = var.access_tier
  account_replication_type         = var.account_replication_type
  cross_tenant_replication_enabled = false
  public_network_access_enabled    = true
  allow_nested_items_to_be_public  = false
  https_traffic_only_enabled       = true
  allowed_copy_scope               = var.allowed_copy_scope
  min_tls_version                  = "TLS1_2"
  tags                             = merge({ "ResourceName" = substr(format("sta%s%s", lower(replace(var.storage_account_name, "/[[:^alnum:]]/", "")), random_string.unique.result), 0, 24) }, var.tags, )

  # https://learn.microsoft.com/en-us/azure/storage/files/storage-files-active-directory-overview
  dynamic "azure_files_authentication" {
    for_each = var.files_authentication_directory_type != "" ? [1] : []
    content {
      directory_type = var.files_authentication_directory_type
    }
  }

  customer_managed_key {
    key_vault_key_id          = var.cmk_settings.key_vault_key_id
    user_assigned_identity_id = var.cmk_settings.key_vault_identity_id
  }

  dynamic "identity" {
    for_each = var.managed_identity_type != null ? [1] : []
    content {
      type = var.managed_identity_type
      # limitation of customer managed key can use only userassigned identities
      # identity_ids = var.managed_identity_type == "UserAssigned" || var.managed_identity_type == "SystemAssigned, UserAssigned" ? var.managed_identity_ids : null
      identity_ids = var.managed_identity_type == "UserAssigned" ? var.managed_identity_ids : null
    }
  }

  # Account only valid if account_kind = StorageV2
  queue_encryption_key_type = var.account_kind != "StorageV2" ? "Service" : "Account"
  table_encryption_key_type = var.account_kind != "StorageV2" ? "Service" : "Account"

  dynamic "blob_properties" {
    for_each = var.account_kind != "FileStorage" ? [1] : []
    content {
      delete_retention_policy {
        days = var.blob_soft_delete_retention_days
      }
      container_delete_retention_policy {
        days = var.container_soft_delete_retention_days
      }
      versioning_enabled       = var.enable_versioning
      last_access_time_enabled = var.last_access_time_enabled
      change_feed_enabled      = var.change_feed_enabled
    }
  }

  network_rules {
    default_action             = local.tag_foundation_baseline == "Palladium" ? "Deny" : length(var.network_rules.basic_ip_restriction) == 0 ? "Allow" : "Deny" #tfsec:ignore:azure-storage-default-action-deny
    bypass                     = ["AzureServices"]
    ip_rules                   = local.tag_foundation_baseline == "Palladium" ? ["IP", "IP"] : length(var.network_rules.basic_ip_restriction) == 0 ? [] : distinct(flatten([var.network_rules.basic_ip_restriction, ["1IP", "IP"]]))
    virtual_network_subnet_ids = local.tag_foundation_baseline == "Palladium" ? var.network_rules.subnet_ids : null
    dynamic "private_link_access" {
      for_each = var.manage_defender_storage.defender_storage_enabled && var.manage_defender_storage.malware_scanning_on_upload_enabled && var.account_kind != "FileStorage" ? [1] : []
      content {
        endpoint_resource_id = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/providers/Microsoft.Security/datascanners/StorageDataScanner"
        endpoint_tenant_id   = data.azurerm_subscription.current.tenant_id
      }
    }
  }

  dynamic "static_website" {
    for_each = var.enable_static_website == true ? [1] : []
    content {
      index_document     = var.static_website_index_document
      error_404_document = var.static_website_error_404_document
    }
  }
}

--------------------------------------------------------------------

var.tf

--------------------------------------------------------------------

variable "create_resource_group" {
  description = "Whether to create resource group and use it for all storage resources."
  default     = false
  type        = bool
}

variable "resource_group_name" {
  description = "The container for the storage resources."
  type        = string
}

variable "location" {
  description = "The region for the storage resources."
  default     = "switzerlandnorth"
  type        = string
}

variable "storage_account_name" {
  description = "The name of the storage account."
  type        = string
}

variable "account_tier" {
  description = "The tier of storage account. Valid options are Premium or Standard."
  default     = "Standard"
  type        = string
}

# Blobs with a tier of Premium are of account kind StorageV2.
variable "account_kind" {
  description = "The type of storage account. Valid options are BlobStorage (Premium), BlockBlobStorage (Premium), FileStorage (Premium), StorageV2 (Standard General Purpose)."
  default     = "StorageV2"
  type        = string
}

variable "account_replication_type" {
  description = "The Replication Types supported by Microsoft Azure Storage. Valid options are LRS (Premium and Standard), ZRS (Premium and Standard), GRS (Standard), GZRS (Standard), RAGRS (Standard), RAGZRS (Standard)."
  default     = "LRS"
  type        = string
}

variable "access_tier" {
  description = "Defines the access tier for BlobStorage, FileStorage and StorageV2 accounts. Valid options are Hot and Cool, defaults to Hot."
  default     = "Hot"
  type        = string
}

variable "blob_soft_delete_retention_days" {
  description = "Specifies the number of days that the blob should be retained, between `1` and `365` days. Defaults to `7`."
  default     = 7
  type        = number
}

variable "container_soft_delete_retention_days" {
  description = "Specifies the number of days that the blob should be retained, between `1` and `365` days. Defaults to `7`."
  default     = 7
  type        = number
}

variable "enable_versioning" {
  description = "Is Blob versioning enabled? Default to `false`."
  default     = false
  type        = bool
}

variable "last_access_time_enabled" {
  description = "Is the last access time based tracking enabled? Default to `false`."
  default     = false
  type        = bool
}

variable "change_feed_enabled" {
  description = "Is the blob service properties for change feed events enabled?"
  default     = false
  type        = bool
}

variable "manage_defender_storage" {
  description = "Manage Defender for Storage."
  type        = object({ defender_storage_enabled = bool, sensitive_data_discovery_enabled = optional(bool), malware_scanning_on_upload_enabled = optional(bool, false) })
  default = {
    defender_storage_enabled = false
  }
}

variable "enable_static_website" {
  description = "Boolean flag which controls if blob static website is enabled. For account_kind FileStorage/BlobStorage must be set to false."
  default     = false
  type        = bool
}

variable "static_website_index_document" {
  description = "The webpage that Azure Storage serves for requests to the root of a website or any subfolder. For example, index.html. The value is case-sensitive."
  default     = ""
  type        = string
}

variable "static_website_error_404_document" {
  description = "The absolute path to a custom webpage that should be used when a request is made which does not correspond to an existing file."
  default     = ""
  type        = string
}

variable "network_rules" {
  description = ""
  type = object({
    subnet_ids           = optional(list(string), [])
    basic_ip_restriction = optional(list(string), [])
  })
}

variable "lifecycles" {
  description = "Configure lifecycle for Append and Block Blobs."
  type        = list(object({ prefix_match = set(string), tier_to_cool_after_days = number, tier_to_archive_after_days = number, delete_after_days = number, snapshot_delete_after_days = number }))
  default     = []
}

variable "managed_identity_type" {
  description = "The type of Managed Identity which should be assigned to the Storage Account. Due to CMK limitation, the only possible values is `UserAssigned`."
  default     = null
  type        = string
}

variable "managed_identity_ids" {
  description = "A list of User Managed Identity ID's which should be assigned to the Storage Account."
  default     = null
  type        = list(string)
}

variable "tags" {
  description = "A map of tags to add to all resources."
  type        = map(string)
  default     = {}
}

variable "private_endpoint_spoke_subnet_id" {
  description = "Palladium only. The Spoke Subnet id where to create the Private Endpoint."
  type        = string
  default     = ""
}

variable "subresource_names" {
  description = "Palladium only. The name of the subresources to create Private Endpoint for. Can be one or more of ['file','blob','queue','table','web']."
  type        = list(string)
  default     = null
}

variable "cmk_settings" {
  type        = object({ key_vault_key_id = string, key_vault_identity_id = string })
  description = "Customer managed keys settings for storage account encryption."

  validation {
    condition     = var.cmk_settings.key_vault_key_id != "" && var.cmk_settings.key_vault_identity_id != ""
    error_message = "The cmk_settings are mandatory, please provide a valid key_vault_key_id and key_vault_identity_id."
  }
}

variable "files_authentication_directory_type" {
  type        = string
  description = "Specifies the directory service used for azure file share authentication. refer to: https://learn.microsoft.com/en-us/azure/storage/common/authorize-data-access"
  default     = ""
  validation {
    condition     = contains(["AADKERB", ""], var.files_authentication_directory_type)
    error_message = "The directory Type can only be 'AADKERB'."
  }
}

variable "allowed_copy_scope" {
  type        = string
  description = "Restrict copy to and from Storage Accounts within an AAD tenant or with Private Links to the same VNet. Possible values are AAD, PrivateLink or null."
  default     = "AAD"
}

Debug Output/Panic Output

# module.storage_standard.module.storage_common.azurerm_storage_account.storeacc will be updated in-place
  ~ resource "azurerm_storage_account" "storeacc" {
        id                                = "/subscriptions/subid/resourceGroups/rg-StorageAccountTesting/providers/Microsoft.Storage/storageAccounts/storageaccountname"        name                              = "storageaccountname"
        tags                              = {
            "ResourceName" = "storageaccountname"
        }
        # (41 unchanged attributes hidden)

      + network_rules {
          + bypass                     = [
              + "AzureServices",
            ]
          + default_action             = "Allow"
          + virtual_network_subnet_ids = (known after apply)
        }

        # (5 unchanged blocks hidden)
    }

Expected Behaviour

Terraform should write network config to state. You can reproduce this problem with all providers > 3.112.0.

Actual Behaviour

Existing network config in azure is not written into state. Terraform wants to create network config on every run but doesn't write to state.

Steps to Reproduce

  1. Deploy azurerm_storage_account with network_rules provider version 3.112.0.
  2. Upgrade provider to newer version - recurring drift starts

Important Factoids

Resource is created by module but you can reproduce with plain resource

References

This seems to be bug as the azurerm Provider just updated the storage account resource multiple times in the last few releases.
Provider Versions: 3.113.0; 3.114.0; 3.115.0
https://github.com/hashicorp/terraform-provider-azurerm/blob/main/CHANGELOG-v3.md

@magodo
Copy link
Collaborator

magodo commented Sep 2, 2024

@0Burg0 This duplicates to #26854. Please checkout #26854 for the details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants