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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_role_assignment must be replaced #4847

Open
rjfmachado opened this issue Nov 9, 2019 · 26 comments
Open

azurerm_role_assignment must be replaced #4847

rjfmachado opened this issue Nov 9, 2019 · 26 comments

Comments

@rjfmachado
Copy link

rjfmachado commented Nov 9, 2019

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

@derek-burdick
Copy link

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)
}

@darren-johnson
Copy link

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.

@darren-johnson

This comment has been minimized.

@SaundersB

This comment has been minimized.

@kensykora
Copy link

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.

@a30004736
Copy link

Facing exactly the issue. Was anybody able to resolve?

@darren-johnson
Copy link

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,
    ]
  }

@a30004736
Copy link

@darren-johnson : Thank you that helped.

@juanjojulian
Copy link
Contributor

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

@jibonilla03
Copy link

jibonilla03 commented Jul 6, 2020

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.

@juanjojulian
Copy link
Contributor

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.

@jibonilla03
Copy link

@juanjojulian Thanks downgrade to 2.15 works for me

@bernardmaltais
Copy link

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

@boillodmanuel
Copy link
Contributor

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

@juanjojulian
Copy link
Contributor

juanjojulian commented Dec 1, 2020

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.

@CarlosSardo

This comment has been minimized.

@MattGarner-N

This comment has been minimized.

@darren-johnson
Copy link

darren-johnson commented Mar 16, 2021

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.

@nikolaifa
Copy link

nikolaifa commented Apr 6, 2021

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.

@torbendury
Copy link

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

@chris92109
Copy link

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

@anarsen
Copy link

anarsen commented Mar 7, 2022

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.

@AdamSir2
Copy link

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

@julienym
Copy link

julienym commented Nov 6, 2023

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

@dcarvalh04
Copy link

dcarvalh04 commented Jan 29, 2024

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.

@mikeclayton
Copy link
Contributor

mikeclayton commented Apr 30, 2024

I just hit this issue again - I think there's multiple duplicate issues logged in this repo:

The root of the problem is that custom role definitions with multiple assignable scopes appear to have multiple valid resource ids - one for each assignable scope.

When you create an assignment you have to use the resource id of the definition that aligns with the scope of the assignment, otherwise the Azure API will silently "correct" the value on write, which means the next time you run a "terraform plan" the value retrieved from the Azure API doesn't match the expected value in the terraform state and you end up with eternal drift.

See my comment here for a hacky workaround that dynamically calculates the "correct" resource id based on the assignment scope and uses that in the resource assignment properties. It's truly awful and I'm not proud of it, but it stops the endless drift detection...

#8764 (comment)

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