hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.6k stars 4.65k forks source link

Role Assignments from Custom Role data sources are always recreated. #8764

Open ndrone-kr opened 4 years ago

ndrone-kr commented 4 years ago

Community Note

Terraform (and AzureRM Provider) Version

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

Affected Resource(s)

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

ghost commented 4 years ago

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

masterphenix commented 4 years ago

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)
    }
matt-FFFFFF commented 3 years ago

This is also present in v2.38

kevinmatthews-kpmg commented 3 years ago

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

mikeclayton commented 3 years ago

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...

jdevalk2 commented 3 years ago

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.

massimolpc commented 3 years ago

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

ndrone-kr commented 3 years ago

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?

mukkadinesh64 commented 2 years ago

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.