hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager

Home Page:https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

azurerm_role_assignment must be replaced

rjfmachado opened this issue Β· 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.12.13

  • provider.azuread v0.6.0
  • provider.azurerm v1.36.1

Affected Resource(s)

azurerm_role_assignment

Terraform Configuration Files

terraform {
  required_version = ">= 0.12"
}

provider "azurerm" {
  version = "~> 1.36.0"
}

provider "azuread" {
  version = "~> 0.6.0"
}

data "azurerm_client_config" "current" {
}

# Get the tenant root Management Group
data "azurerm_management_group" "mgTenantRoot" {
  group_id = data.azurerm_client_config.current.tenant_id
}

# Create a Management Group as a child to the tenant root
resource "azurerm_management_group" "test239857" {
  display_name               = "test239857"
  parent_management_group_id = data.azurerm_management_group.mgTenantRoot.id
  group_id                   = "test239857"
  subscription_ids = [
    data.azurerm_client_config.current.subscription_id //move the subscription under the role definition scope
  ]
}

# Create a custom role scoped to a management group
resource "azurerm_role_definition" "test239857" {
  name        = "test239857"
  scope       = azurerm_management_group.test239857.id
  description = "Role scoped to a management group"

  permissions {
    actions = [
      "Microsoft.DeploymentManager/*/read"
    ]
    not_actions = []
  }

  assignable_scopes = [
    azurerm_management_group.test239857.id
  ]
}

# create a target for the role assignment
resource "azuread_group" "test239857" {
  name = "test239857"
}

# Get the current subscription
data "azurerm_subscription" "currentSubscription" {
}

# Assignment of a Management Group scoped role to a subscription
resource "azurerm_role_assignment" "test239857" {
  scope              = data.azurerm_subscription.currentSubscription.id
  role_definition_id = azurerm_role_definition.test239857.id
  principal_id       = azuread_group.test239857.id
}

Debug Output

https://gist.github.com/rjfmachado/b8dd89e4e89fd88391e26e27ab0ff3f2

Expected Behavior

Terraform plan should have no changes after apply

Actual Behavior

Terraform wants to recreate the Role Assignment

Steps to Reproduce

  1. Terraform apply
  2. Terraform plan

Important Factoids

User should have global tenant admin role on Azure AD and Owner on the subscription

References

#3450 Also hitting this when destroying

Hi, I get the same behavior for built in roles, using azurerm_builtin_role_definition or azurerm_role_definition.

data azurerm_builtin_role_definition contributor {
name = "Contributor"
}

OR

data azurerm_role_definition contributor {
name = "Contributor"
}

Here is the output from plan/apply

module.service.azurerm_role_assignment.app-role must be replaced

-/+ resource "azurerm_role_assignment" "app-role" {
~ id = "/subscriptions/1234/resourceGroups/service-nexus-test1-westus2-dev/providers/Microsoft.Authorization/roleAssignments/6fea8cf5-5113-299f-aa9e-5e4be190fc2f" -> (known after apply)
~ name = "6fea8cf5-5113-299f-aa9e-5e4be190fc2f" -> (known after apply)
principal_id = "7f16a9bb-a75d-4953-8c50-61b7ae694bb4"
~ principal_type = "ServicePrincipal" -> (known after apply)
~ role_definition_id = "/subscriptions/1234/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" -> "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" # forces replacement
~ role_definition_name = "Contributor" -> (known after apply)
scope = "/subscriptions/1234/resourceGroups/service-test1-westus2-dev"
+ skip_service_principal_aad_check = (known after apply)
}

Still having the same issue today 23/01/2020. It looks like it keeps wanting to switch between:

/subscriptions/GUID/providers/Microsoft.Authorization/roleDefinitions/GUID" -> "/providers/Microsoft.Authorization/roleDefinitions/GUID"

Every time it is also creating a new role_definition_id GUID.

Hi I can confirm this issue still happens with terraform 0.12.21 and AzureRM 2.0

Same issue with Terraform v0.12.23

  • provider.azurerm v2.1.0

I am having a similar issue that has to deal with multiple subscriptions in the same tenant. Here is my scenario:

resource "azurerm_role_definition" "resource-provider-registration" {
  name        = "Register Azure Resource Providers"
  scope       = "/subscriptions/${local.subscriptions.prod}"
  description = "Can register Azure resource providers"
  permissions {
    actions = ["*/register/action"]
  }
  assignable_scopes = [
    "/subscriptions/${local.subscriptions.prod}",
    "/subscriptions/${local.subscriptions.uat}",
    "/subscriptions/${local.subscriptions.dev}"
  ]

  provider = azurerm.prod
}

resource "azurerm_role_assignment" "ado-service-principal-provider-registration-prod" {
  scope              = "/subscriptions/${local.subscriptions.prod}"
  role_definition_id = azurerm_role_definition.resource-provider-registration.id
  principal_id       = local.azure_dev_ops_service_principals.prod.object_id

  provider = azurerm.prod
}

resource "azurerm_role_assignment" "ado-service-principal-provider-registration-uat" {
  scope              = "/subscriptions/${local.subscriptions.uat}"
  role_definition_id = azurerm_role_definition.resource-provider-registration.id
  principal_id       = local.azure_dev_ops_service_principals.uat.object_id

  provider = azurerm.uat
}

resource "azurerm_role_assignment" "ado-service-principal-provider-registration-dev" {
  scope              = "/subscriptions/${local.subscriptions.dev}"
  role_definition_id = azurerm_role_definition.resource-provider-registration.id
  principal_id       = local.azure_dev_ops_service_principals.dev.object_id

  provider = azurerm.dev
}

Subsequent runs of this produces the following terraform plans (wants to replace them due to the ID changing, when it hasn't changed.

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # azurerm_role_assignment.ado-service-principal-provider-registration-dev must be replaced
-/+ resource "azurerm_role_assignment" "ado-service-principal-provider-registration-dev" {
      ~ id                               = "/subscriptions/{Dev Subscription ID}/providers/Microsoft.Authorization/roleAssignments/2c2691ea-877f-cfe2-326e-6133d1ec73b9" -> (known after apply)
      ~ name                             = "2c2691ea-877f-cfe2-326e-6133d1ec73b9" -> (known after apply)
        principal_id                     = "2da99d69-b8a3-419f-a885-f6e1e5314524"
      ~ principal_type                   = "ServicePrincipal" -> (known after apply)
      ~ role_definition_id               = "/subscriptions/{Dev Subscription ID}/providers/Microsoft.Authorization/roleDefinitions/75262d2f-6665-aada-da4e-2bcafd5b2bc5" -> "/subscriptions/{Production Subscription ID}/providers/Microsoft.Authorization/roleDefinitions/75262d2f-6665-aada-da4e-2bcafd5b2bc5" # forces replacement
      ~ role_definition_name             = "Register Azure Resource Providers" -> (known after apply)
        scope                            = "/subscriptions/{Dev Subscription ID}"
      + skip_service_principal_aad_check = (known after apply)
    }

  # azurerm_role_assignment.ado-service-principal-provider-registration-uat must be replaced
-/+ resource "azurerm_role_assignment" "ado-service-principal-provider-registration-uat" {
      ~ id                               = "/subscriptions/{UAT Subscription ID}/providers/Microsoft.Authorization/roleAssignments/faf86950-c49d-061d-fd5e-142132c44441" -> (known after apply)
      ~ name                             = "faf86950-c49d-061d-fd5e-142132c44441" -> (known after apply)
        principal_id                     = "3defe365-2eb3-41c7-a025-55649d591ede"
      ~ principal_type                   = "ServicePrincipal" -> (known after apply)
      ~ role_definition_id               = "/subscriptions/{UAT Subscription ID}/providers/Microsoft.Authorization/roleDefinitions/75262d2f-6665-aada-da4e-2bcafd5b2bc5" -> "/subscriptions/{Production Subscription ID}/providers/Microsoft.Authorization/roleDefinitions/75262d2f-6665-aada-da4e-2bcafd5b2bc5" # forces replacement
      ~ role_definition_name             = "Register Azure Resource Providers" -> (known after apply)
        scope                            = "/subscriptions/{UAT Subscription ID}"
      + skip_service_principal_aad_check = (known after apply)
    }

Plan: 2 to add, 0 to change, 2 to destroy.

Facing exactly the issue. Was anybody able to resolve?

Yes I did literally yesterday! If you add a lifecycle ignore-changes block it stops happening.

I hope this helps.

  lifecycle {
    ignore_changes = [
      role_definition_id,
    ]
  }

@darren-johnson : Thank you that helped.

Same situation here, "ignore_changes" is a patch that works but this should be investigated and solved. In my case this happens only with custom roles, once we assign them it works but It wants to modify the resource each time we re-apply:

~ role_definition_id = "/subscriptions/SUBSCRIPTION_ID/providers/Microsoft.Authorization/roleDefinitions/d7e9b9d6-a3d1-9518-0e6b-ff17d47c9078" -> "/providers/Microsoft.Authorization/roleDefinitions/d7e9b9d6-a3d1-9518-0e6b-ff17d47c9078" # forces replacement

I have same problem for a custom role, it always try to replace the role definition even when I add the ignore changes, can anyone assist ?

Terraform v0.12.28

  • provider.azurerm v2.17.0
locals {
  role_definition_id = uuid()
}
resource "azurerm_role_definition" "custom" {
  role_definition_id = local.role_definition_id
  name               = var.role_definition_name
  scope              = data.azurerm_management_group.gbs_cloud.id
  description        = var.role_definition_description

  permissions {
    actions     = var.role_actions
    not_actions = var.role_not_actions
  }

  assignable_scopes = [
    data.azurerm_management_group.gbs_cloud.id, 
  ]

   lifecycle {
    ignore_changes = [
      role_definition_id,
    ]
  }
}
Terraform will perform the following actions:
-/+ resource "azurerm_role_definition" "custom" {
        assignable_scopes  = [
            "/providers/Microsoft.Management/managementGroups/GBSCloud",
        ]
        description        = "This is a custom role created via Terraform"
      ~ id                 = "/providers/Microsoft.Authorization/roleDefinitions/709c011c-46c1-c5bd-5431-f5b12058399b" -> (known after apply)
        name               = "Custom Log Analytics"
      ~ role_definition_id = "709c011c-46c1-c5bd-5431-f5b12058399b" -> (known after apply)
      + scope              = "/providers/Microsoft.Management/managementGroups/GBSCloud" # forces replacement

      ~ permissions {
            actions          = [
                "*/read",
                "Microsoft.OperationalInsights/workspaces/analytics/query/action",
                "Microsoft.OperationalInsights/workspaces/search/action",
                "Microsoft.Insights/AlertRules/Throttled/Action",
                "Microsoft.Insights/AlertRules/Resolved/Action",
                "Microsoft.Insights/AlertRules/Activated/Action",
                "Microsoft.Insights/AlertRules/Read",
                "Microsoft.Insights/AlertRules/Delete",
                "Microsoft.Insights/AlertRules/Write",
            ]
          - data_actions     = [] -> null
            not_actions      = [
                "Microsoft.OperationalInsights/workspaces/sharedKeys/read",
            ]
          - not_data_actions = [] -> null
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.

I have same problem for a custom role, it always try to replace the role definition even when I add the ignore changes, can anyone assist ?

Terraform v0.12.28

  • provider.azurerm v2.17.0
locals {
  role_definition_id = uuid()
}
resource "azurerm_role_definition" "custom" {
  role_definition_id = local.role_definition_id
  name               = var.role_definition_name
  scope              = data.azurerm_management_group.gbs_cloud.id
  description        = var.role_definition_description

  permissions {
    actions     = var.role_actions
    not_actions = var.role_not_actions
  }

  assignable_scopes = [
    data.azurerm_management_group.gbs_cloud.id, 
  ]

   lifecycle {
    ignore_changes = [
      role_definition_id,
    ]
  }
}
Terraform will perform the following actions:
-/+ resource "azurerm_role_definition" "custom" {
        assignable_scopes  = [
            "/providers/Microsoft.Management/managementGroups/GBSCloud",
        ]
        description        = "This is a custom role created via Terraform"
      ~ id                 = "/providers/Microsoft.Authorization/roleDefinitions/709c011c-46c1-c5bd-5431-f5b12058399b" -> (known after apply)
        name               = "Custom Log Analytics"
      ~ role_definition_id = "709c011c-46c1-c5bd-5431-f5b12058399b" -> (known after apply)
      + scope              = "/providers/Microsoft.Management/managementGroups/GBSCloud" # forces replacement

      ~ permissions {
            actions          = [
                "*/read",
                "Microsoft.OperationalInsights/workspaces/analytics/query/action",
                "Microsoft.OperationalInsights/workspaces/search/action",
                "Microsoft.Insights/AlertRules/Throttled/Action",
                "Microsoft.Insights/AlertRules/Resolved/Action",
                "Microsoft.Insights/AlertRules/Activated/Action",
                "Microsoft.Insights/AlertRules/Read",
                "Microsoft.Insights/AlertRules/Delete",
                "Microsoft.Insights/AlertRules/Write",
            ]
          - data_actions     = [] -> null
            not_actions      = [
                "Microsoft.OperationalInsights/workspaces/sharedKeys/read",
            ]
          - not_data_actions = [] -> null
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Your problem is related to an issue in azurerm_role_definition that was introduced in version 2.16, downgrade to version 2.15 and it will work as expected. Please don't forget to post in issue #7549 to let the developers know that there is more people affected.

@juanjojulian Thanks downgrade to 2.15 works for me

@juanjojulian 2.15 still gives the error in my case ;-(

Hi,
I found a workaround for role_definition_id that should be applied in some situations only:

  • Built-in role: role_definition_name is used
  • Custom role:
    • applied at management group level: use role_definition_id = azurerm_role_definition.example.id as in terraform example
    • applied at subscription level: use the workaround by prefixing the role definition id with the subscription id role_definition_id = "${data.azurerm_subscription.primary.id}${ azurerm_role_definition.example.id}"

Manuel

Thanks for the tip @boillodmanuel , in my case it doesn't work, there must be some kind of mess in azurerm_role_assignment resource in my version combination:

Terraform v0.13.5
+ provider registry.terraform.io/hashicorp/azuread v1.1.1
+ provider registry.terraform.io/hashicorp/azurerm v2.38.0

For instance, if I write:

role_definition_id = azurerm_role_definition.customRole.id

Terraform wants to set role_definition_id to:

"/providers/Microsoft.Authorization/roleDefinitions/1b7485d0-f713-e168-5a5b-0816ba0b0646|/providers/Microsoft.Management/managementGroups/myManagementGroupName"

Which according to Documentation should be the output for "role_definition_id" not for "id"

But then if I make use of role_definition_resource_id

role_definition_id = azurerm_role_definition.customRole.role_definition_resource_id

Terraform wants to set role_definition_id to:

"/providers/Microsoft.Authorization/roleDefinitions/1b7485d0-f713-e168-5a5b-0816ba0b0646"

Which initially works but in subsequent applies Terraform will want to change it again and again:

 "/subscriptions/6hsbdka-d4cf-98e6-ff0c-3114fjkds8dbhnb43/providers/Microsoft.Authorization/roleDefinitions/1b7485d0-f713-e168-5a5b-0816ba0b0646" -> "/providers/Microsoft.Authorization/roleDefinitions/1b7485d0-f713-e168-5a5b-0816ba0b0646"

So the (ugly) workaround that works for me its:

role_definition_id = "${data.azurerm_subscription.mySubscription.id}/providers/Microsoft.Authorization/roleDefinitions/${ azurerm_role_definition.customRole.role_definition_id}"

I really cannot understand what's going on with this resource in azurerm, this issue has been open for one year now.

Facing the same issue, when using Terraform v0.14.2 and azurerm provider v2.36.0

This is still happening

Quick update. I have just tested this with @thedevopscat using Terraform v0.14.8 & azurerm v2.51.0

Using the role_definition_name instead of role_definition_id does not cause the issue to occur, however you cannot use a custom role_definition_name as a datasource just by specifying the name.

It would be great if this can be fixed.

My understanding suggests that a role definition object is created within each scope specified within the azurerm_role_definition resource. The object referred to in azurerm_role_assignment -> role_definition_id should be the one created for the scope set in azurerm_role_assignment -> role_definition_id.

The provider tries to use the object existing in its own subscription (the one specified in the provider configuration).

The (ugly) workaround suggested by @juanjojulian worked for me.

To avoid confusion between azurerm_role_definition and azurerm_role_assignment the input should be named role_definition_resource_id, not role_definition_id.

How is this more than 2 yrs old and still not fixed?

We face the same problem with TF 1.0.9 and AzureRM 2.71.0.

I ended up setting role_definition_name instead of the role_definition_id when assigning custom roles. Since custom role definition names are unique across a tenant this will work.

commented

@darren-johnson is right there is no role definition name in the data source - which I find very inconsistent. I would have this problem would have lead to a name reference being created...

I noticed that if my data source is missing the scope it would be missing the /sub.../XXX prefix for the role_definition_id

data "azurerm_role_definition" "this" {
  name  = "ABC"
  # scope = var.role_subscription_id
}

resource "azurerm_role_assignment" "this" {
  scope              = azurerm_storage_account.this.id
...
  role_definition_id = data.azurerm_role_definition.this.id
}

PLAN:
-/+ resource "azurerm_role_assignment" "this" {
...
      ~ role_definition_id               = "/subscriptions/XXX/providers/Microsoft.Authorization/roleDefinitions/YYY" -> "/providers/Microsoft.Authorization/roleDefinitions/YYY" # forces replacement

Issue still occurs with
source = "hashicorp/azurerm" version "3.89.0" and terraform 1.7

data "azurerm_role_definition" "builtin_owner" {
  name = "Owner"
}

resource "azurerm_pim_eligible_role_assignment" "mg_finops_owner" {
  scope              = azurerm_management_group.xxxxx.id
  role_definition_id = data.azurerm_role_definition.builtin_owner.id
  principal_id       = azuread_group.xxxxx.object_id
}

Each terraform plan leads to:

Terraform will perform the following actions:

  # azurerm_pim_eligible_role_assignment.mg_finops_owner must be replaced
-/+ resource "azurerm_pim_eligible_role_assignment" "xxxxxxx" {
      ~ id                 = "/providers/Microsoft.Management/managementGroups/xxxxx|/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635|xxxxx" -> (known after apply)
      ~ principal_type     = "Group" -> (known after apply)
        # (3 unchanged attributes hidden)

      - schedule { # forces replacement
          - start_date_time = "2024-01-29T15:46:49.0788364+00:00" -> null

          - expiration {
              - duration_days  = 0 -> null
              - duration_hours = 0 -> null
            }
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Fixed by adding:

lifecycle {
    ignore_changes = [
      schedule,
    ]
  }

The resource was originally created without any schedule as it's optional.