terraform-provider-azurerm icon indicating copy to clipboard operation
terraform-provider-azurerm copied to clipboard

User Assigned Identity Removal Ineffective in VMSS Identity Block

Open saketh-cloudknox opened this issue 11 months ago • 1 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Community Note

  • Please vote on this issue by adding a :thumbsup: reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, 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.2.1

AzureRM Provider Version

3.93.0

Affected Resource(s)/Data Source(s)

azurerm_linux_virtual_machine_scale_set

Terraform Configuration Files

# Reference: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set

terraform {
  required_version = ">=0.12"
}

locals {
  first_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN [email protected]"
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "example" {
  name     = "example-resources"
  location = "West US"
}

resource "azurerm_virtual_network" "example" {
  name                = "example-network"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  address_space       = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "internal" {
  name                 = "internal"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_user_assigned_identity" "vmss_identity_1" {
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  name                = "test-vmss-identity-1"
}

resource "azurerm_user_assigned_identity" "vmss_identity_2" {
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  name                = "test-vmss-identity-2"
}


resource "azurerm_linux_virtual_machine_scale_set" "example" {
  name                = "example-vmss"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  sku                 = "Standard_F2"
  instances           = 1
  admin_username      = "adminuser"
  
  identity {
    type         = "UserAssigned"
    # I first created the identity_ids with 2 identities and it created the both and asssociated with the VMSS, then I ran it with just 1 to see if it will unassign 1 and it did not. 
    identity_ids = [azurerm_user_assigned_identity.vmss_identity_1.id , azurerm_user_assigned_identity.vmss_identity_2.id]
    # identity_ids = [azurerm_user_assigned_identity.vmss_identity_1.id]
  }

  admin_ssh_key {
    username   = "adminuser"
    public_key = local.first_public_key
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  os_disk {
    storage_account_type = "Standard_LRS"
    caching              = "ReadWrite"
  }

  network_interface {
    name    = "example"
    primary = true

    ip_configuration {
      name      = "internal"
      primary   = true
      subnet_id = azurerm_subnet.internal.id
    }
  }
}

Debug Output/Panic Output

https://gist.github.com/saketh-cloudknox/7b78170917f119c259cc16cc7a6428f6

Expected Behaviour

The user assigned identity that was removed from the identity block should have been removed from the VMSS.

Actual Behaviour

The user assigned identity that was removed from the identity block remained attached to the VMSS.

Steps to Reproduce

User Assigned Identity Removal Ineffective in VMSS Identity Block

Reproduction Steps

  1. Run terraform apply --auto-approve. Terraform shows that 2 User assigned identities will be assigned to the VMSS. This is reflected in the Azure Portal.
  2. Remove one of the identity_ids from the identity block under the azurerm_linux_virtual_machine_scale_set.
  3. Run terraform apply --auto-approve. Terraform shows that 1 User assigned identity will be unassigned from the VMSS as per the plan.
  4. Removal is not reflected in the azure portal or via CLI. VMSS still has BOTH user assigned identities associated with it.

Important Factoids

Azure Production

References

No response

saketh-cloudknox avatar Feb 27 '24 18:02 saketh-cloudknox

Azure VMSS Identity Update Terraform Issues

Hi all, I did some digging today into why this is happening and I think this should summarize the issue and the potential fix. Looking to get some opinions on it!

1. Problem Statement

The azurerm Terraform Provider does not properly remove identities from the VMSS when removing them from the Terraform configuration. The Terraform Provider tries to patch the VMSS using only the identities that are present in the Terraform configuration. When existing identities are patched with the same values, the API does not remove any identities not present in the PATCH request. The identities are passed in as a map where the key is the identity and identities to be removed have their value set to null, while identities to be kept/added are set to {}

1.1. REST API Behavior

This request will remove the userAssignedIdentity with the id /subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-vmss-identity-2. It will not remove any other identities that are present in the VMSS.

PATCH https://management.azure.com/subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachineScaleSets/example-vmss?api-version=2023-09-01
{
    "identity": {
        "type": "userassigned",
        "userAssignedIdentities": {
            "/subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-vmss-identity-2": null
        }
    }
}

This request will not remove any identities from the VMSS, even though the identity with the id /subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-vmss-identity-2 is not present in the PATCH request.

PATCH https://management.azure.com/subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachineScaleSets/example-vmss?api-version=2023-09-01
{
    "identity": {
        "type": "userassigned",
        "userAssignedIdentities": {
            "/subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-vmss-identity-1": {}
        }
    }
}

1.2 Terraform Provider Behavior

If the initial state of the VMSS looks like the following in terraform, then terraform will ensure that the VMSS has both idenitites attached to it.

resource "azurerm_linux_virtual_machine_scale_set" "example" {
  name                = "example-vmss"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  sku                 = "Standard_F2"
  instances           = 1
  admin_username      = "adminuser"
  
  identity {
    type         = "UserAssigned" 
    identity_ids = [azurerm_user_assigned_identity.vmss_identity_1.id , azurerm_user_assigned_identity.vmss_identity_2.id]
  }

  admin_ssh_key {
    username   = "adminuser"
    public_key = local.first_public_key
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  os_disk {
    storage_account_type = "Standard_LRS"
    caching              = "ReadWrite"
  }

  network_interface {
    name    = "example"
    primary = true

    ip_configuration {
      name      = "internal"
      primary   = true
      subnet_id = azurerm_subnet.internal.id
    }
  }
}

The user then removes the identity with the id azurerm_user_assigned_identity.vmss_identity_2.id from the VMSS configuration and runs terraform apply.

resource "azurerm_linux_virtual_machine_scale_set" "example" {
  name                = "example-vmss"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  sku                 = "Standard_F2"
  instances           = 1
  admin_username      = "adminuser"
  
  identity {
    type         = "UserAssigned" 
    identity_ids = [azurerm_user_assigned_identity.vmss_identity_1.id]
  }

  admin_ssh_key {
    username   = "adminuser"
    public_key = local.first_public_key
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  os_disk {
    storage_account_type = "Standard_LRS"
    caching              = "ReadWrite"
  }

  network_interface {
    name    = "example"
    primary = true

    ip_configuration {
      name      = "internal"
      primary   = true
      subnet_id = azurerm_subnet.internal.id
    }
  }
}

The terraform provider will then patch the VMSS with the following request (according to the debug logs).

PATCH /subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachineScaleSets/example-vmss?api-version=2023-03-01 HTTP/1.1
Host: management.azure.com
User-Agent: Go/go1.21.6 (amd64-darwin) go-autorest/v14.2.1 tombuildsstuff/kermit/v0.20240122.1123108 compute/2023-03-01 HashiCorp Terraform/1.2.1 (+https://www.terraform.io) Terraform Plugin SDK/2.10.1 terraform-provider-azurerm/3.93.0 pid-222c6c49-1b0a-5959-a213-6608f9eb8820
Content-Length: 446
Content-Type: application/json; charset=utf-8
X-Ms-Correlation-Request-Id: 0794a696-9d83-f3e7-07c6-d6569402a777
Accept-Encoding: gzip

{
	"identity": {
		"type": "UserAssigned",
		"userAssignedIdentities": {
			"/subscriptions/<azure-subscription-id>/resourceGroups/example-resources/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-vmss-identity-1": {}
		}
	},
	"properties": {
		"upgradePolicy": {
			"mode": "Manual"
		},
		"virtualMachineProfile": {
			"storageProfile": {
				"imageReference": {
					"offer": "0001-com-ubuntu-server-jammy",
					"publisher": "Canonical",
					"sku": "22_04-lts",
					"version": "latest"
				}
			}
		}
	}
}

Based on the API behvaior noted in section 1.1, the VMSS will still have the identity with the id azurerm_user_assigned_identity.vmss_identity_2.id attached to it since it did not set the identity to null in the PATCH request and only set the identity with the id azurerm_user_assigned_identity.vmss_identity_1.id to {}.

2. Proposed Solution

2.1. Update Terraform Provider

In /internal/services/compute/linux_virtual_machine_scale_set_resource.go the Update method should be updated to remove any identities that are not present in the Terraform configuration.

The Update method associated with identities currently has this code block:

if d.HasChange("identity") {
  identityRaw := d.Get("identity").([]interface{})
  identity, err := expandVirtualMachineScaleSetIdentity(identityRaw)
  if err != nil {
    return fmt.Errorf("expanding `identity`: %+v", err)
  }

  update.Identity = identity
}

It is not adding the entry in the identity map for the identities that need to be removed from the VMSS by setting their value to null. The expandVirtualMachineScaleSetIdentity function should be updated to add the identities that need to be removed to the map with a value of null. These identities are available via the variable existing.Identity.UserAssignedIdentities which will give the existing identities that can be compared against the identities returned by expandVirtualMachineScaleSetIdentity

saketh-cloudknox avatar Feb 28 '24 00:02 saketh-cloudknox

Hi @saketh-cloudknox, thank you for reporting this issue. However, it may not be fixed at once because I found in some situation the backend will reply an error for the identity with null value. We need to double confirm with Azure team about the reason.

PATCH
X-Ms-Correlation-Request-Id: e94fd18c-905c-a5e5-5c1c-566f79e19741
{
    "identity": {
        "type": "UserAssigned",
        "userAssignedIdentities": {
            "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/acctestRG-OVMSS-240305172229758653/providers/Microsoft.ManagedIdentity/userAssignedIdentities/acctest2oxyl2": null,
            "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/acctestRG-OVMSS-240305172229758653/providers/Microsoft.ManagedIdentity/userAssignedIdentities/acctestoxyl2": {}
        }
    },
    "properties": {
        "virtualMachineProfile": {
            "osProfile": {
                "linuxConfiguration": {}
            },
            "storageProfile": {
                "imageReference": {
                    "offer": "0001-com-ubuntu-server-jammy",
                    "publisher": "Canonical",
                    "sku": "22_04-lts",
                    "version": "latest"
                }
            }
        }
    }
}


RESPONSE
{
  "error": {
    "code": "InvalidParameter",
    "message": "ResourceIdentity userAssignedIdentities should not have keys with null values.",
    "target": "resourceIdentity.userAssignedIdentities"
  }
}

Currently, there is a tricky way to temporarily unblock this issue: remove azurerm_user_assigned_identity.vmss_identity_2 block with azurerm_user_assigned_identity.vmss_identity_2.id of azurerm_linux_virtual_machine_scale_set.example together.

ms-zhenhua avatar Mar 05 '24 09:03 ms-zhenhua

@ms-zhenhua

I found in some situation the backend will reply an error for the identity with null value.

This is the expected API behaviour FWIW, you must specify an empty payload rather than null - it's a consistent behaviour across multiple Azure Services

tombuildsstuff avatar Mar 05 '24 17:03 tombuildsstuff