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

Role Assignments from Custom Role data sources are always recreated.

Open ndrone-kr opened this issue 3 years ago • 9 comments

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

Terraform (and AzureRM Provider) Version

Terraform v0.13.4
+ provider registry.terraform.io/hashicorp/azurerm v2.23.0

Affected Resource(s)

  • azurerm_role_assignment
  • azurerm_role_definition

Terraform Configuration Files

data "azurerm_role_definition" "custom_subnet_join" {
  role_definition_id = "c15aac7b-a565-4d59-88a8-754593bb6e83"
}

resource "azurerm_role_assignment" "nonprod_databricks_public_subnet_join" {
  scope              = data.azurerm_subnet.nonprod_databricks_public.id
  role_definition_id = data.azurerm_role_definition.custom_subnet_join.id
  principal_id       = local.tf_deployer_oid
}

Debug Output

Panic Output

Expected Behavior

After applying, no change should be needed when doing a second apply or plan.

Actual Behavior

Terraform wants to recreate the role assignments on every apply.

Terraform will perform the following actions:

  # azurerm_role_assignment.nonprod_databricks_public_subnet_join must be replaced
-/+ resource "azurerm_role_assignment" "nonprod_databricks_public_subnet_join" {
      ~ id                               = "/subscriptions/000/resourceGroups/networking-eastus2/providers/Microsoft.Network/virtualNetworks/nonprod-eastus2-vnet/subnets/nonprod-eastus2-ws-public/providers/Microsoft.Authorization/roleAssignments/c650bfd3-2f36-7e98-04ff-ab1fdea2e684" -> (known after apply)
      ~ name                             = "c650bfd3-2f36-7e98-04ff-ab1fdea2e684" -> (known after apply)
        principal_id                     = "aec1ee1a-2041-48bd-a12e-1c1d008022c9"
      ~ principal_type                   = "ServicePrincipal" -> (known after apply)
      ~ role_definition_id               = "/subscriptions/000/providers/Microsoft.Authorization/roleDefinitions/c15aac7b-a565-4d59-88a8-754593bb6e83" -> "/providers/Microsoft.Authorization/roleDefinitions/c15aac7b-a565-4d59-88a8-754593bb6e83" # forces replacement
      ~ role_definition_name             = "Custom Service Endpoint Enable" -> (known after apply)
        scope                            = "/subscriptions/000/resourceGroups/networking-eastus2/providers/Microsoft.Network/virtualNetworks/nonprod-eastus2-vnet/subnets/nonprod-eastus2-ws-public"
      + skip_service_principal_aad_check = (known after apply)
    }

Steps to Reproduce

  1. terraform apply
  2. terraform plan

Important Factoids

References

  • #0000

ndrone-kr avatar Oct 06 '20 15:10 ndrone-kr

I have the same issue with version 2.33.0 of the azurerm provider.

ghost avatar Oct 28 '20 11:10 ghost

Hello,

I seem to have a similar issue with Terraform v0.13.5 and provider registry.terraform.io/hashicorp/azurerm v2.33.0 ; however, it is the principal_id that forces replacement, even if it has not changed :

  # module.xxx.azurerm_role_assignment.rg-contributor must be replaced
-/+ resource "azurerm_role_assignment" "rg-contributor" {
      ~ id                               = "/subscriptions/xxxxxxx-xxxx-xxx-xxx-xxxxxxxx/providers/Microsoft.Authorization/roleAssignments/xxxxxxxx-xxx-xxxx-xxxx-xxxxxxxxxxxx" -> (known after apply)
      ~ name                             = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx" -> (known after apply)
      ~ principal_id                     = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx" -> (known after apply) # forces replacement
      ~ principal_type                   = "ServicePrincipal" -> (known after apply)
      ~ role_definition_id               = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx" -> (known after apply)
        role_definition_name             = "Contributor"
        scope                            = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"
      + skip_service_principal_aad_check = (known after apply)
    }


masterphenix avatar Nov 02 '20 12:11 masterphenix

This is also present in v2.38

matt-FFFFFF avatar Dec 01 '20 09:12 matt-FFFFFF

Hi, we are seeing the same issue in provider v2.36 - any timeframes on a fix for this? We have 100's of role assignments being recreated on every build but with nothing changing.

Ours are not custom role assignments either, they are built in roles.

Thanks

kevinmatthews-kpmg avatar Dec 21 '20 10:12 kevinmatthews-kpmg

I've had a similar issue with custom role definitions, and I have a very kludgy workaround in case it helps anyone.

The Problem In my case, the problem was creating a role definition with multiple assignable scopes - e.g.

resource "azurerm_role_definition" "my_role_definition" {
  name        = "My CustomRole"
  scope       = "/subscriptions/xxxxxxxx"
  permissions {
    ... etc ...
  }
  assignable_scopes = [
    "/subscriptions/xxxxxxxx"
    "/subscriptions/yyyyyyyy"
    "/subscriptions/zzzzzzzz"
  ]
}

resource "azurerm_role_assignment" "my_role_assignment" {
  scope              = "/subscriptions/zzzzzzzz"
  role_definition_id = data.azurerm_role_definition.my_role_definition.id
  principal_id       = "<my_principal_id>"
}

The core of the issue is that data.azurerm_role_definition.my_role_definition.id refers to the id of the role definition in scope /subscriptions/xxxxxxxx where it was created, but it also has two other ids for the other assignable scopes. When you refer to the role definition in the azurerm_role_assignment resource you have to use the role definition id for the appropriate scope.

It seems that even though this creates an assignment perfectly fine:

resource "azurerm_role_assignment" "my_role_assignment" {
  scope              = "/subscriptions/zzzzzzzz"
  role_definition_id = "/subscriptions/xxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/aaaaaaaa"
  principal_id       = "<my_principal_id>"
}

the Azure API munges the role definition id internally so that what actually gets created is:

resource "azurerm_role_assignment" "my_role_assignment" {
  scope              = "/subscriptions/zzzzzzzz"
  role_definition_id = "/subscriptions/zzzzzzzz/providers/Microsoft.Authorization/roleDefinitions/aaaaaaaa"
  principal_id       = "<my_principal_id>"
}

and when you run the plan again there's a mismatch between the found (zzzzzzzz) and specified (xxxxxxxx) role definition ids which triggers the destroy / create sequence:

      ~ role_definition_id               = "/subscriptions/zzzzzzzz/providers/Microsoft.Authorization/roleDefinitions/aaaaaaaa" -> "/subscriptions/xxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/aaaaaaaa" # forces replacement

The (Hacky) Solution The somewhat hacky fix for me was to do the same munging in my terraform resource to make the resource definition id match the assignment's scope:

resource "azurerm_role_assignment" "my_role_assignment" {
  scope              = "/subscriptions/zzzzzzzz"
  role_definition_id = join("", [
      "/subscriptions/zzzzzzzz",
      "/providers/Microsoft.Authorization",
      "/roleDefinitions/${reverse(split("/", azurerm_role_definition.my_role_definition))[0]}"
    ])
  principal_id       = "<my_principal_id>"
}

Bascially, hand-craft the first part of the role definition id, then extract the guid from the end of the role definition resource and append that.

This still references the azurerm_role_definition.my_role_definition resource so the dependencies are intact, and it avoids the -/+ resource "azurerm_role_assignment" "my_role_assignment" on each tf apply.

It's not pretty, but it works...

mikeclayton avatar Jan 11 '21 14:01 mikeclayton

I noticed this not only relates to custom role definitions as a data source

When you set the "scope" as a resource group, and this resource group is declared as a datasource to get its ID, Then you will see the exact same recreate behavior. Role assignment is always recreated.

It seems like as soon as any input comes from a datasource it loses the ability to check for equality with what it already has.

This might be a new issue, but tagging it onto this one as it seems related.

jdevalk2 avatar Jul 30 '21 17:07 jdevalk2

I just had the same problem few days ago. If you see the plan message the problem is the scope of the role definition, this forces a destroy/create Fix by adding the scope to the azurerm_role_definition datasource

massimolpc avatar Sep 23 '21 15:09 massimolpc

I just had the same problem few days ago. If you see the plan message the problem is the scope of the role definition, this forces a destroy/create Fix by adding the scope to the azurerm_role_definition datasource

I'd admit I haven't had much time to dig into what you stated. Can you provide an example?

ndrone-kr avatar Sep 28 '21 15:09 ndrone-kr

I had same issue but this will work generate random uid and add it as name, it is optional but it will be created and not tracked in the state if we are not providing the value.

resource "random_uuid" "this" { for_each = toset(var.principal_ids) }

resource "azurerm_role_assignment" "role_assign" { for_each = toset(var.principal_ids) name = (random_uuid.this)[each.value].result description = var.description scope = var.scope role_definition_name = var.role_definition_name role_definition_id = var.role_definition_id principal_id = each.value condition = var.condition condition_version = var.condition_version }

see if this works for you.

mukkadinesh64 avatar Sep 22 '22 17:09 mukkadinesh64