hashicorp / terraform-provider-azuread

Terraform provider for Azure Active Directory
https://registry.terraform.io/providers/hashicorp/azuread/latest/docs
Mozilla Public License 2.0
426 stars 293 forks source link

azuread_directory_role_eligibility_schedule_request resources disappear and can't be recreated #1306

Open garretth9 opened 8 months ago

garretth9 commented 8 months ago

Community Note

Terraform (and AzureAD Provider) Version

Terraform v1.5.7 on darwin_arm64

Affected Resource(s)

Terraform Configuration Files

data "azuread_user" "example" {
  user_principal_name = "jdoe@hashicorp.com"
}

resource "azuread_directory_role" "example" {
  display_name = "Application Administrator"
}

resource "azuread_directory_role_eligibility_schedule_request" "example" {
  role_definition_id = azuread_directory_role.example.template_id
  principal_id       = azuread_user.example.object_id
  directory_scope_id = "/"
  justification      = "Example"
}

Debug Output

Panic Output

Expected Behavior

The role eligibility assignment should be created, and in the absence of any other changes being made should remain indefinitely.

Actual Behavior

The role eligibility assignment is created but within a month or two (not positive on the exact duration), the Schedule Request seems to automatically expire and disappear. This leads terraform to attempt to recreate it on the next run, but as the actual role assignment still exists it results in an error similar to the below

β”‚ Error: Eligibility schedule request for role "45xxx3c5-c802-45c6-b32a-1d70xxx1e86e" to principal "8e2xxe56-bacf-4c8d-87e6-5de5xxx8a453", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with azuread_directory_role_eligibility_schedule_request.example,
β”‚   on groups.tf line 70, in resource "azuread_directory_role_eligibility_schedule_request" "example":
β”‚   70: resource "azuread_directory_role_eligibility_schedule_request" "example" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.

Steps to Reproduce

  1. terraform apply
  2. wait a month or two
  3. terraform apply

I'm not sure this is an issue with the resource itself per se, but given that the behavior of Entra seems to be

  1. User creates an eligibility schedule request
  2. An eligible role assignment is created FROM this schedule request
  3. The request eventually rolls off leaving the assignment behind

I do wonder whether it's even useful to have this as a resource in the provider. Perhaps there's a way to skip step 1 and create the schedule request directly? or alternately once the request is created in terraform have it tie the resource ID to the actual eligible role assignment somehow instead of tying it to the schedule request, which seems to not be a permanent resource?

Important Factoids

References

nbaju1 commented 8 months ago

Did a test of this and the eligibility schedule request was created with no end time, i.e. "End Time -> Permanent" in "PIM -> Eligible assignments" in the Azure portal.

Given that I don't see how this can be an issue with the TF provider. Most likely the request was removed manually.

garretth9 commented 8 months ago

Perhaps I wasn’t clear on the issue. All of my role aaasignments are still present, the problem is that when terraform refreshed the state it no longer sees the eligibility REQUESTS in our tenant, so it tries to recreate the resources. This then fails because the role assignment the request is trying to create already exists.

There doesn’t seem to be a direct correlation between the eligibility request resource terraform is creating and the actual eligible role assignment in Entra.

What I THINK is happening is that the role eligibility schedule request is the equivalent of the form you fill out on the portal saying β€œI want to assign eligibility for this role for this duration”, and creating that resource causes Entra to create a separate resource for the actual role assignment. Eventually Entra cleans up those old requests (for us it took a couple months), but the actual role assignments remained since I requested they be permanent.

On Fri, Feb 9, 2024 at 5:44β€―AM Anders Julton @.***> wrote:

Did a test of this and the eligibility schedule request was created with no end time, i.e. "End Time -> Permanent" in "PIM -> Eligible assignments" in the Azure portal.

Given that I don't see how this can be an issue with the TF provider. Most likely the request was removed manually.

β€” Reply to this email directly, view it on GitHub https://github.com/hashicorp/terraform-provider-azuread/issues/1306#issuecomment-1935697217, or unsubscribe https://github.com/notifications/unsubscribe-auth/AL5CX62MP2D6QAL2ZBPHFFDYSX4ZDAVCNFSM6AAAAABC73CHDGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSMZVGY4TOMRRG4 . You are receiving this because you authored the thread.Message ID: @.***>

garretth9 commented 8 months ago

To add some more detail, i believe the issue here has to do with the distinction between a roleEligibilitySchedule and a roleEligibilityScheduleRequest. It looks like when you create an eligible role assignment using an eligibilityScheduleRequest the Schedule itself is created with the permanent lifespan that was requested, but the eligibilityScheduleRequest itself appears to have a separate lifecycle.

When i search with powershell using the Get-MgRoleManagementDirectoryRoleEligibilitySchedule cmdlet i still see my eligible role assignments with a status of "provisioned", however when i search with the Get-MgRoleManagementDirectoryRoleEligibilityScheduleRequest the requests all show a status of "Revoked" The same problem happened after roughly the same duration in both our production and test tenants. I'm the only one managing role managements in our test tenant so i know for a fact no manual action was done to remove them.

The eligible assignments were deployed using terraform near the end of December, and i was deploying new eligible role assignments with this template successfully until a couple weeks ago. Then when i tried to update it again yesterday the requests had all rolled off and terraform wanted to recreate them all. So i suspect the requests have a one month lifecycle, but i'm not positive.

Unfortunately that means if you want to try and replicate it i think you'll have to deploy the resources and wait a month or so then try generating a new plan, you should see that the role assignments are still there but terraform thinks the schedule requests need to be recreated, which will fail.

nbaju1 commented 7 months ago

I agree, seems like Entra has separate resources for the request and the actual schedule. Checked in our test tenant and most requests were Revoked, while the schedules were Provisioned. I'm not savvy enough in Go or Terraform to understand how the check to see if a request needs to be recreated is made, but if the status field on the roleEligibilityScheduleRequests object is the one being checked, then this will cause unnecessary attempts to recreate the resource when it automatically changes to Revoked. Note that I haven't tested myself to see if it automatically changes, but from what @garretth9 reports and the status in our tentant this seems likely.

The only way, using the MS Graph API at least, to create a schedule is to create the request. There is no POST method for the schedule resource.

A solution might be to change the read resource method in the provider to look at the schedule rather than the request to determine if the request must be recreated.

Refs: Request: https://learn.microsoft.com/en-us/graph/api/resources/unifiedroleeligibilityschedulerequest?view=graph-rest-1.0 Schedule: https://learn.microsoft.com/en-us/graph/api/resources/unifiedroleeligibilityschedule?view=graph-rest-1.0

garretth9 commented 7 months ago

Looking in my environment today i see that all of the "revoked" schedule requests have now completely vanished. It really seems like the only path forward to make this resource even viable for use is to somehow link it to the created schedule instead of the request.

In fact, i'd go a step further and say it might be more appropriate to

  1. remove the azuread_directory_role_eligibility_schedule_request resource entirely
  2. replace it with an azuread_directory_role_eligibility_schedule resource
  3. have the provider logic manage the steps of creating a schedule request and linking the terraform resource to the id of the created schedule instead of the schedule request so we can actually see if the schedule needs to be recreated

In any case it doesn't seem particularly useful to have the terraform state pointing to a resource that is going to self-destruct automatically

Darkfogel commented 6 months ago

Just to give this some additionnal light, I also have the same issue as mentionned here. The request actually expires but the assignment is still active.

kenchan0130 commented 6 months ago
  1. remove the azuread_directory_role_eligibility_schedule_request resource entirely
  2. replace it with an azuread_directory_role_eligibility_schedule resource
  3. have the provider logic manage the steps of creating a schedule request and linking the terraform resource to the id of the created schedule instead of the schedule request so we can actually see if the schedule needs to be recreated

I agree with these proposed changes. First, we need to add client implementation related to eligibility schedules in the Go Graph API SDK (Hamilton).

Darkfogel commented 4 months ago

@manicminer Would that issue be the same one that was happening in the azurerm provider that you fixed in PR #25956?

Darkfogel commented 2 months ago

@kenchan0130 Any news on this? Just checking out as I would really like to be able to keep all my resources in the state. I actually have to remove them after 45 days as a workaround.

paul-hugill commented 1 month ago

To add another bit of information, if you manually delete the assignment in the Azure Portal, Terraform does not see any changes on the next run and does not try to recreate it, so state and actual no longer match.

leuthelt commented 1 month ago

I've same behavior like @Darkfogel mentioned. After 45 days our drift detection shows creation of all Terraform created permanent "Eligible assignments". Because Terraform cannot match the state with real world anymore, my workaround was also manual removal of these assignments and execution of our Terraform workflow again.

Azure Portal:

image

All assignments were created on 8th of July 2024. And today (23rd of August) Terraform cannot match state to real world. So date diff is 46 days, means the issue starts after 45 days.

Drift detection shows:

image

Terraform plan:

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Application Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "cd2d9f08-7f84-4479-be4b-8124e7f6fa28"
      + role_definition_id = "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Conditional Access Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "e97be21a-4e3b-48b3-b8e2-459ab7704817"
      + role_definition_id = "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Global Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "bbaff55c-102b-45ea-bbb3-859eea0225bc"
      + role_definition_id = "62e90394-69f5-4237-9190-012177145e10"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Groups Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "e6a73948-a522-49a8-bc26-af134a1bf4a3"
      + role_definition_id = "fdd7a751-b60b-444a-984c-02652fe8fa1c"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Guest Inviter"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "fb6db157-993d-49ce-9ae7-46f61bf29481"
      + role_definition_id = "95e79109-95c0-4d8e-aee3-d01accf2d47b"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Identity Governance Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "433787dc-758f-4265-a81c-60141325e18a"
      + role_definition_id = "45d8d3c5-c802-45c6-b32a-1d70b5e1e86e"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Intune Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "31d2d388-02e7-43f7-b1b4-1bfbb6b83700"
      + role_definition_id = "3a2c62db-5318-420d-8d74-23affee5d9d5"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["License Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "0c30a4ff-6e07-447c-b65e-09c2d4adf422"
      + role_definition_id = "4d6ac14f-3453-41d0-bef9-a3e0c569773a"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Network Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "7ec21459-8a60-4e62-bec1-80a1a269f3d4"
      + role_definition_id = "d37c8bed-0711-4417-ba38-b4abe66ce4c2"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Privileged Role Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "b5e4[520](https://github.com/bmwblueteam/t0-launchpad/actions/runs/10519683985/job/29147449755#step:18:521)e-ca63-48b9-b066-a11615d68cad"
      + role_definition_id = "e8611ab8-c189-46e8-94e1-60213ab1f814"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["Security Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "7097dbe3-179f-481e-9fc9-5c1abc7faba0"
      + role_definition_id = "194ae4cb-b126-40b2-bd5b-6091b380977d"
    }

  # module.blueprint_iam_entra_roles.module.pim_entra_roles["User Administrator"].azuread_directory_role_eligibility_schedule_request.this will be created
  + resource "azuread_directory_role_eligibility_schedule_request" "this" {
      + directory_scope_id = "/"
      + id                 = (known after apply)
      + justification      = "Terraform created assignment"
      + principal_id       = "119e2[534](https://github.com/bmwblueteam/t0-launchpad/actions/runs/10519683985/job/29147449755#step:18:535)-f88b-4a37-b476-84a128bb1733"
      + role_definition_id = "fe930be7-5e62-47db-91af-98c3a49a38b1"
    }

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

Now, when I run Terraform workflow it fails because it's saying that the same resources already exists:

module.blueprint_iam_entra_roles.module.pim_entra_roles["Guest Inviter"].azuread_directory_role_eligibility_schedule_request.this: Creating...
β•·
β”‚ Error: Eligibility schedule request for role "fdd7a751-b60b-444a-984c-02652fe8fa1c" to principal "e6a73948-a522-49a8-bc26-af134a1bf4a3", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["Groups Administrator"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "45d8d3c5-c802-45c6-b32a-1d70b5e1e86e" to principal "43[37](https://github.com/bmwblueteam/t0-launchpad/actions/runs/10521504706/job/29152440577#step:14:38)87dc-758f-4265-a81c-60141325e18a", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "4d6ac14f-3453-41d0-bef9-a3e0c569773a" to principal "0c30a4ff-6e07-447c-b65e-09c2d4adf422", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["License Administrator"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "e8611ab8-c189-46e8-94e1-60213ab1f814" to principal "b5e4520e-ca63-48b9-b066-a11615d68cad", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["Privileged Role Administrator"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "95e79109-95c0-4d8e-aee3-d01accf2d47b" to principal "fb6db157-993d-49ce-9ae7-46f61bf29481", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["Guest Inviter"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "62e90394-69f5-4237-9190-012177145e10" to principal "bbaff55c-102b-45ea-bbb3-859eea0225bc", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["Global Administrator"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "194ae4cb-b126-40b2-bd5b-6091b[38](https://github.com/bmwblueteam/t0-launchpad/actions/runs/10521504706/job/29152440577#step:14:39)0977d" to principal "7097dbe3-179f-481e-9fc9-5c1abc7faba0", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status [40](https://github.com/bmwblueteam/t0-launchpad/actions/runs/10521504706/job/29152440577#step:14:41)0 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["Security Administrator"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
β•·
β”‚ Error: Eligibility schedule request for role "9b895d92-2cd3-[44](https://github.com/bmwblueteam/t0-launchpad/actions/runs/10521504706/job/29152440577#step:14:45)c7-9d02-a6ac2d5ea5c3" to principal "cd2d9f08-7f84-4479-be4b-8124e7f6fa28", received 400 with error: RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status 400 with OData error: RoleAssignmentExists: The Role assignment already exists.
β”‚ 
β”‚   with module.blueprint_iam_entra_roles.module.pim_entra_roles["Application Administrator"].azuread_directory_role_eligibility_schedule_request.this,
β”‚   on ../../modules/pim_entra_roles/v1.2.0/main.tf line 34, in resource "azuread_directory_role_eligibility_schedule_request" "this":
β”‚   34: resource "azuread_directory_role_eligibility_schedule_request" "this" {
β”‚ 
β”‚ RoleEligibilityScheduleRequestClient.BaseClient.Post(): unexpected status
β”‚ 400 with OData error: RoleAssignmentExists: The Role assignment already
β”‚ exists.
β•΅
Releasing state lock. This may take a few moments...
Error: Process completed with exit code 1.

Are there any news regarding this topic?